Index: lib/src/barback/barback_server.dart |
diff --git a/lib/src/barback/barback_server.dart b/lib/src/barback/barback_server.dart |
index b6f5d5766d02c8a355edef9c30d4b8f529bde9c7..a159b642d72a484ed10e74e18412dbab1cd4a835 100644 |
--- a/lib/src/barback/barback_server.dart |
+++ b/lib/src/barback/barback_server.dart |
@@ -3,9 +3,11 @@ |
// BSD-style license that can be found in the LICENSE file. |
import 'dart:async'; |
+import 'dart:convert'; |
import 'dart:io'; |
import 'package:barback/barback.dart'; |
+import "package:crypto/crypto.dart"; |
import 'package:mime/mime.dart'; |
import 'package:path/path.dart' as path; |
import 'package:shelf/shelf.dart' as shelf; |
@@ -89,7 +91,7 @@ class BarbackServer extends BaseServer<BarbackServerResult> { |
} |
/// Handles an HTTP request. |
- handleRequest(shelf.Request request) { |
+ Future<shelf.Response> handleRequest(shelf.Request request) async { |
if (request.method != "GET" && request.method != "HEAD") { |
return methodNotAllowed(request); |
} |
@@ -154,14 +156,39 @@ class BarbackServer extends BaseServer<BarbackServerResult> { |
} |
/// Returns the body of [asset] as a response to [request]. |
- Future<shelf.Response> _serveAsset(shelf.Request request, Asset asset) { |
- return validateStream(asset.read()).then((stream) { |
- addResult(new BarbackServerResult._success(request.url, asset.id)); |
- var headers = {}; |
- var mimeType = lookupMimeType(asset.id.path); |
- if (mimeType != null) headers['Content-Type'] = mimeType; |
- return new shelf.Response.ok(stream, headers: headers); |
- }).catchError((error, trace) { |
+ Future<shelf.Response> _serveAsset(shelf.Request request, Asset asset) async { |
+ try { |
+ var pair = tee(await validateStream(asset.read())); |
+ var responseStream = pair.first; |
+ var hashStream = pair.last; |
+ |
+ // Allow the asset to be cached based on its content hash. |
+ var sha = new SHA1(); |
+ await hashStream.forEach((chunk) { |
+ sha.add(chunk); |
+ }); |
+ |
+ var assetSha = BASE64.encode(sha.close()); |
+ var previousSha = request.headers["if-none-match"]; |
+ |
+ var headers = { |
+ // Enabled browser caching of the asset. |
+ "Cache-Control": "max-age=3600", |
+ "ETag": assetSha |
+ }; |
+ |
+ if (assetSha == previousSha) { |
+ // We're requesting an unchanged asset so don't push its body down the |
+ // wire again. |
+ addResult(new BarbackServerResult._cached(request.url, asset.id)); |
+ return new shelf.Response.notModified(headers: headers); |
+ } else { |
+ addResult(new BarbackServerResult._success(request.url, asset.id)); |
+ var mimeType = lookupMimeType(asset.id.path); |
+ if (mimeType != null) headers['Content-Type'] = mimeType; |
+ return new shelf.Response.ok(responseStream, headers: headers); |
+ } |
+ } catch (error, trace) { |
addResult(new BarbackServerResult._failure(request.url, asset.id, error)); |
// If we couldn't read the asset, handle the error gracefully. |
@@ -176,7 +203,7 @@ class BarbackServer extends BaseServer<BarbackServerResult> { |
// Otherwise, it's some internal error. |
return new shelf.Response.internalServerError(body: error.toString()); |
- }); |
+ } |
} |
} |
@@ -199,11 +226,20 @@ class BarbackServerResult { |
/// Whether the request was served successfully. |
bool get isSuccess => error == null; |
+ /// Whether the request was for a previously cached asset. |
+ final bool isCached; |
+ |
/// Whether the request was served unsuccessfully. |
bool get isFailure => !isSuccess; |
BarbackServerResult._success(this.url, this.id) |
- : error = null; |
+ : error = null, |
+ isCached = false; |
+ |
+ BarbackServerResult._cached(this.url, this.id) |
+ : error = null, |
+ isCached = true; |
- BarbackServerResult._failure(this.url, this.id, this.error); |
+ BarbackServerResult._failure(this.url, this.id, this.error) |
+ : isCached = false; |
} |