From d30edcdef69b6b1f1cd7e92556dc4e5cf3c406ec Mon Sep 17 00:00:00 2001 From: thosakwe Date: Tue, 15 Aug 2017 20:01:31 -0400 Subject: [PATCH] 1.2.5 --- CHANGELOG.md | 7 ++++ lib/src/cache.dart | 16 +++++--- lib/src/virtual_directory.dart | 70 ++++++++++++++++++++++++++++++---- pubspec.yaml | 2 +- test/all_test.dart | 27 ++++++++++++- 5 files changed, 108 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e29576be..569e8fed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.2.5 +* Fixed a bug where `onlyInProduction` was not properly adhered to. +* Fixed another bug where `Accept-Encoding` was not properly adhered to. +* Setting `maxAge` to `null` will now prevent a `CachingVirtualDirectory` from sending an `Expires` header. +* Pre-built assets can now be mass-deleted with `VirtualDirectory.cleanFromDisk()`. +Resolves [#22](https://github.com/angel-dart/static/issues/22). + # 1.2.4+1 Fixed a bug where `Accept-Encoding` was not properly adhered to. diff --git a/lib/src/cache.dart b/lib/src/cache.dart index 8be15fbc..f0187e19 100644 --- a/lib/src/cache.dart +++ b/lib/src/cache.dart @@ -62,6 +62,8 @@ class CachingVirtualDirectory extends VirtualDirectory { final bool useWeakEtags; /// The `max-age` for `Cache-Control`. + /// + /// Set this to `null` to leave no `Expires` header on responses. final int maxAge; CachingVirtualDirectory( @@ -91,7 +93,7 @@ class CachingVirtualDirectory extends VirtualDirectory { @override Future serveFile( File file, FileStat stat, RequestContext req, ResponseContext res) { - if (onlyInProduction == true && req.app.isProduction == true) { + if (onlyInProduction == true && req.app.isProduction != true) { return super.serveFile(file, stat, req, res); } @@ -154,7 +156,8 @@ class CachingVirtualDirectory extends VirtualDirectory { generateEtag(buf, weak: useWeakEtags != false, hash: hash); res.headers ..[HttpHeaders.ETAG] = etag - ..[HttpHeaders.CONTENT_TYPE] = lookupMimeType(file.path) ?? 'application/octet-stream'; + ..[HttpHeaders.CONTENT_TYPE] = + lookupMimeType(file.path) ?? 'application/octet-stream'; setCachedHeaders(stat.modified, req, res); if (useWeakEtags == false) { @@ -174,18 +177,21 @@ class CachingVirtualDirectory extends VirtualDirectory { void setCachedHeaders( DateTime modified, RequestContext req, ResponseContext res) { var privacy = accessLevelToString(accessLevel ?? CacheAccessLevel.PUBLIC); - var expiry = new DateTime.now().add(new Duration(seconds: maxAge ?? 0)); res.headers ..[HttpHeaders.CACHE_CONTROL] = '$privacy, max-age=${maxAge ?? 0}' - ..[HttpHeaders.EXPIRES] = formatDateForHttp(expiry) ..[HttpHeaders.LAST_MODIFIED] = formatDateForHttp(modified); + + if (maxAge != null) { + var expiry = new DateTime.now().add(new Duration(seconds: maxAge ?? 0)); + res.headers[HttpHeaders.EXPIRES] = formatDateForHttp(expiry); + } } @override Future serveAsset( FileInfo fileInfo, RequestContext req, ResponseContext res) { - if (onlyInProduction == true && req.app.isProduction == true) { + if (onlyInProduction == true && req.app.isProduction != true) { return super.serveAsset(fileInfo, req, res); } diff --git a/lib/src/virtual_directory.dart b/lib/src/virtual_directory.dart index 871c6d3a..e26ecf0a 100644 --- a/lib/src/virtual_directory.dart +++ b/lib/src/virtual_directory.dart @@ -284,8 +284,17 @@ class VirtualDirectory implements AngelPlugin { ? file.openRead().transform(GZIP.encoder) : file.openRead(); await stream.pipe(res.io); - } else - await res.sendFile(file); + } else { + if (_acceptsGzip(req)) { + res.io.headers + ..set(HttpHeaders.CONTENT_TYPE, + lookupMimeType(file.path) ?? 'application/octet-stream') + ..set(HttpHeaders.CONTENT_ENCODING, 'gzip'); + await file.openRead().transform(GZIP.encoder).forEach(res.buffer.add); + res.end(); + } else + await res.sendFile(file); + } return false; } @@ -313,7 +322,11 @@ class VirtualDirectory implements AngelPlugin { : file.content; await stream.pipe(res.io); } else { - await file.content.forEach(res.buffer.add); + if (_acceptsGzip(req)) { + res.io.headers.set(HttpHeaders.CONTENT_ENCODING, 'gzip'); + await file.content.transform(GZIP.encoder).forEach(res.buffer.add); + } else + await file.content.forEach(res.buffer.add); } return false; @@ -330,8 +343,8 @@ class VirtualDirectory implements AngelPlugin { String originalName = file.filename; for (var transformer in _transformers) { if (++iterations >= 100) { - print( - 'VirtualDirectory has tried 100 times to compile ${file.filename}. Perhaps one of your transformers is not changing the output file\'s extension.'); + print('VirtualDirectory has tried 100 times to compile ${file + .filename}. Perhaps one of your transformers is not changing the output file\'s extension.'); throw new AngelHttpException(new StackOverflowError(), statusCode: 500); } else if (iterations < 100) iterations++; @@ -363,8 +376,8 @@ class VirtualDirectory implements AngelPlugin { var compiled = await compileAsset(asset); if (compiled == null) p.finish( - message: - '"${entity.absolute.path}" did not require compilation; skipping it.'); + message: '"${entity.absolute + .path}" did not require compilation; skipping it.'); else { var outFile = new File(compiled.filename); if (!await outFile.exists()) await outFile.create(recursive: true); @@ -386,4 +399,47 @@ class VirtualDirectory implements AngelPlugin { print('Build of assets in "${source.absolute.path}" complete.'); } + + /// Deletes any pre-built assets. + Future cleanFromDisk() async { + var l = new cli.Logger.standard(); + print('Cleaning assets in "${source.absolute.path}"...'); + + await for (var entity in source.list(recursive: true)) { + if (entity is File) { + var p = l.progress('Checking "${entity.absolute.path}"'); + + try { + var asset = new FileInfo.fromFile(entity); + var compiled = await compileAsset(asset); + if (compiled == null) + p.finish( + message: '"${entity.absolute + .path}" did not require compilation; skipping it.'); + else { + var outFile = new File(compiled.filename); + if (await outFile.exists()) { + await outFile.delete(); + p.finish( + message: 'Deleted "${compiled + .filename}", which was the output of "${entity.absolute + .path}".', + showTiming: true); + } else { + p.finish( + message: + 'Output "${compiled.filename}" of "${entity.absolute.path}" does not exist.'); + } + } + } on AngelHttpException { + // Ignore 500 + } catch (e, st) { + p.finish(message: 'Failed to delete "${entity.absolute.path}".'); + stderr..writeln(e)..writeln(st); + } + } + } + + print('Purge of assets in "${source.absolute.path}" complete.'); + } } diff --git a/pubspec.yaml b/pubspec.yaml index c21dbc0b..36ffcfbc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ environment: sdk: ">=1.19.0" homepage: https://github.com/angel-dart/static author: Tobe O -version: 1.2.4+1 +version: 1.2.5 dependencies: angel_framework: ^1.0.0-dev cli_util: ^0.1.1 diff --git a/test/all_test.dart b/test/all_test.dart index 892007de..b74b3de2 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -12,7 +12,7 @@ main() { Client client = new Client(); setUp(() async { - app = new Angel(debug: true); + app = new Angel(); await app.configure(new VirtualDirectory( debug: true, @@ -23,6 +23,7 @@ main() { await app.configure(new VirtualDirectory( debug: true, source: testDir, + streamToIO: true, indexFileNames: ['index.php', 'index.txt'])); app.after.add('Fallback'); @@ -72,4 +73,28 @@ main() { }); expect(response.body, equals("index!")); }); + + test('can gzip: just gzip', () async { + var response = await client + .get("$url/sample.txt", headers: {HttpHeaders.ACCEPT_ENCODING: 'gzip'}); + expect(response.body, equals("Hello world")); + expect(response.headers[HttpHeaders.CONTENT_TYPE], contains("text/plain")); + expect(response.headers[HttpHeaders.CONTENT_ENCODING], 'gzip'); + }); + + test('can gzip: wildcard', () async { + var response = await client + .get("$url/sample.txt", headers: {HttpHeaders.ACCEPT_ENCODING: 'foo, *'}); + expect(response.body, equals("Hello world")); + expect(response.headers[HttpHeaders.CONTENT_TYPE], contains("text/plain")); + expect(response.headers[HttpHeaders.CONTENT_ENCODING], 'gzip'); + }); + + test('can gzip: gzip and friends', () async { + var response = await client + .get("$url/sample.txt", headers: {HttpHeaders.ACCEPT_ENCODING: 'gzip, deflate, br'}); + expect(response.body, equals("Hello world")); + expect(response.headers[HttpHeaders.CONTENT_TYPE], contains("text/plain")); + expect(response.headers[HttpHeaders.CONTENT_ENCODING], 'gzip'); + }); }