This commit is contained in:
thosakwe 2017-08-15 20:01:31 -04:00
parent 2ac66a4877
commit d30edcdef6
5 changed files with 108 additions and 14 deletions

View file

@ -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 # 1.2.4+1
Fixed a bug where `Accept-Encoding` was not properly adhered to. Fixed a bug where `Accept-Encoding` was not properly adhered to.

View file

@ -62,6 +62,8 @@ class CachingVirtualDirectory extends VirtualDirectory {
final bool useWeakEtags; final bool useWeakEtags;
/// The `max-age` for `Cache-Control`. /// The `max-age` for `Cache-Control`.
///
/// Set this to `null` to leave no `Expires` header on responses.
final int maxAge; final int maxAge;
CachingVirtualDirectory( CachingVirtualDirectory(
@ -91,7 +93,7 @@ class CachingVirtualDirectory extends VirtualDirectory {
@override @override
Future<bool> serveFile( Future<bool> serveFile(
File file, FileStat stat, RequestContext req, ResponseContext res) { 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); return super.serveFile(file, stat, req, res);
} }
@ -154,7 +156,8 @@ class CachingVirtualDirectory extends VirtualDirectory {
generateEtag(buf, weak: useWeakEtags != false, hash: hash); generateEtag(buf, weak: useWeakEtags != false, hash: hash);
res.headers res.headers
..[HttpHeaders.ETAG] = etag ..[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); setCachedHeaders(stat.modified, req, res);
if (useWeakEtags == false) { if (useWeakEtags == false) {
@ -174,18 +177,21 @@ class CachingVirtualDirectory extends VirtualDirectory {
void setCachedHeaders( void setCachedHeaders(
DateTime modified, RequestContext req, ResponseContext res) { DateTime modified, RequestContext req, ResponseContext res) {
var privacy = accessLevelToString(accessLevel ?? CacheAccessLevel.PUBLIC); var privacy = accessLevelToString(accessLevel ?? CacheAccessLevel.PUBLIC);
var expiry = new DateTime.now().add(new Duration(seconds: maxAge ?? 0));
res.headers res.headers
..[HttpHeaders.CACHE_CONTROL] = '$privacy, max-age=${maxAge ?? 0}' ..[HttpHeaders.CACHE_CONTROL] = '$privacy, max-age=${maxAge ?? 0}'
..[HttpHeaders.EXPIRES] = formatDateForHttp(expiry)
..[HttpHeaders.LAST_MODIFIED] = formatDateForHttp(modified); ..[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 @override
Future<bool> serveAsset( Future<bool> serveAsset(
FileInfo fileInfo, RequestContext req, ResponseContext res) { 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); return super.serveAsset(fileInfo, req, res);
} }

View file

@ -284,8 +284,17 @@ class VirtualDirectory implements AngelPlugin {
? file.openRead().transform(GZIP.encoder) ? file.openRead().transform(GZIP.encoder)
: file.openRead(); : file.openRead();
await stream.pipe(res.io); await stream.pipe(res.io);
} 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 } else
await res.sendFile(file); await res.sendFile(file);
}
return false; return false;
} }
@ -313,6 +322,10 @@ class VirtualDirectory implements AngelPlugin {
: file.content; : file.content;
await stream.pipe(res.io); await stream.pipe(res.io);
} else { } else {
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); await file.content.forEach(res.buffer.add);
} }
@ -330,8 +343,8 @@ class VirtualDirectory implements AngelPlugin {
String originalName = file.filename; String originalName = file.filename;
for (var transformer in _transformers) { for (var transformer in _transformers) {
if (++iterations >= 100) { if (++iterations >= 100) {
print( print('VirtualDirectory has tried 100 times to compile ${file
'VirtualDirectory has tried 100 times to compile ${file.filename}. Perhaps one of your transformers is not changing the output file\'s extension.'); .filename}. Perhaps one of your transformers is not changing the output file\'s extension.');
throw new AngelHttpException(new StackOverflowError(), throw new AngelHttpException(new StackOverflowError(),
statusCode: 500); statusCode: 500);
} else if (iterations < 100) iterations++; } else if (iterations < 100) iterations++;
@ -363,8 +376,8 @@ class VirtualDirectory implements AngelPlugin {
var compiled = await compileAsset(asset); var compiled = await compileAsset(asset);
if (compiled == null) if (compiled == null)
p.finish( p.finish(
message: message: '"${entity.absolute
'"${entity.absolute.path}" did not require compilation; skipping it.'); .path}" did not require compilation; skipping it.');
else { else {
var outFile = new File(compiled.filename); var outFile = new File(compiled.filename);
if (!await outFile.exists()) await outFile.create(recursive: true); 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.'); 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.');
}
} }

View file

@ -4,7 +4,7 @@ environment:
sdk: ">=1.19.0" sdk: ">=1.19.0"
homepage: https://github.com/angel-dart/static homepage: https://github.com/angel-dart/static
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
version: 1.2.4+1 version: 1.2.5
dependencies: dependencies:
angel_framework: ^1.0.0-dev angel_framework: ^1.0.0-dev
cli_util: ^0.1.1 cli_util: ^0.1.1

View file

@ -12,7 +12,7 @@ main() {
Client client = new Client(); Client client = new Client();
setUp(() async { setUp(() async {
app = new Angel(debug: true); app = new Angel();
await app.configure(new VirtualDirectory( await app.configure(new VirtualDirectory(
debug: true, debug: true,
@ -23,6 +23,7 @@ main() {
await app.configure(new VirtualDirectory( await app.configure(new VirtualDirectory(
debug: true, debug: true,
source: testDir, source: testDir,
streamToIO: true,
indexFileNames: ['index.php', 'index.txt'])); indexFileNames: ['index.php', 'index.txt']));
app.after.add('Fallback'); app.after.add('Fallback');
@ -72,4 +73,28 @@ main() {
}); });
expect(response.body, equals("index!")); 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');
});
} }