mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[flutter_tools] adds etag/cache control header to debug asset server (#51143)
This commit is contained in:
parent
c5dd3ec47a
commit
bb74a328b5
@ -137,6 +137,10 @@ class WebAssetServer implements AssetReader {
|
||||
return shelf.Response.notFound('');
|
||||
}
|
||||
|
||||
// Track etag headers for better caching of resources.
|
||||
final String ifNoneMatch = request.headers[HttpHeaders.ifNoneMatchHeader];
|
||||
headers[HttpHeaders.cacheControlHeader] = 'max-age=0, must-revalidate';
|
||||
|
||||
// NOTE: shelf removes leading `/` for some reason.
|
||||
final String requestPath = request.url.path.startsWith('/')
|
||||
? request.url.path
|
||||
@ -146,16 +150,29 @@ class WebAssetServer implements AssetReader {
|
||||
// Attempt to look up the file by URI.
|
||||
if (_files.containsKey(requestPath)) {
|
||||
final List<int> bytes = getFile(requestPath);
|
||||
// Use the underlying buffer hashCode as a revision string. This buffer is
|
||||
// replaced whenever the frontend_server produces new output files, which
|
||||
// will also change the hashCode.
|
||||
final String etag = bytes.hashCode.toString();
|
||||
if (ifNoneMatch == etag) {
|
||||
return shelf.Response.notModified();
|
||||
}
|
||||
headers[HttpHeaders.contentLengthHeader] = bytes.length.toString();
|
||||
headers[HttpHeaders.contentTypeHeader] = 'application/javascript';
|
||||
headers[HttpHeaders.etagHeader] = etag;
|
||||
return shelf.Response.ok(bytes, headers: headers);
|
||||
}
|
||||
// If this is a sourcemap file, then it might be in the in-memory cache.
|
||||
// Attempt to lookup the file by URI.
|
||||
if (_sourcemaps.containsKey(requestPath)) {
|
||||
final List<int> bytes = getSourceMap(requestPath);
|
||||
final String etag = bytes.hashCode.toString();
|
||||
if (ifNoneMatch == etag) {
|
||||
return shelf.Response.notModified();
|
||||
}
|
||||
headers[HttpHeaders.contentLengthHeader] = bytes.length.toString();
|
||||
headers[HttpHeaders.contentTypeHeader] = 'application/json';
|
||||
headers[HttpHeaders.etagHeader] = etag;
|
||||
return shelf.Response.ok(bytes, headers: headers);
|
||||
}
|
||||
|
||||
@ -172,6 +189,13 @@ class WebAssetServer implements AssetReader {
|
||||
if (!file.existsSync()) {
|
||||
return shelf.Response.notFound('');
|
||||
}
|
||||
|
||||
// For real files, use a serialized file stat as a revision
|
||||
final String etag = file.lastModifiedSync().toIso8601String();
|
||||
if (ifNoneMatch == etag) {
|
||||
return shelf.Response.notModified();
|
||||
}
|
||||
|
||||
final int length = file.lengthSync();
|
||||
// Attempt to determine the file's mime type. if this is not provided some
|
||||
// browsers will refuse to render images/show video et cetera. If the tool
|
||||
@ -186,6 +210,7 @@ class WebAssetServer implements AssetReader {
|
||||
mimeType ??= _kDefaultMimeType;
|
||||
headers[HttpHeaders.contentLengthHeader] = length.toString();
|
||||
headers[HttpHeaders.contentTypeHeader] = mimeType;
|
||||
headers[HttpHeaders.etagHeader] = etag;
|
||||
return shelf.Response.ok(file.openRead(), headers: headers);
|
||||
}
|
||||
|
||||
|
@ -87,8 +87,9 @@ void main() {
|
||||
.handleRequest(Request('GET', Uri.parse('http://foobar/foo.js')));
|
||||
|
||||
expect(response.headers, allOf(<Matcher>[
|
||||
containsPair('content-length', source.lengthSync().toString()),
|
||||
containsPair('content-type', 'application/javascript'),
|
||||
containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
|
||||
containsPair(HttpHeaders.contentTypeHeader, 'application/javascript'),
|
||||
containsPair(HttpHeaders.etagHeader, isNotNull)
|
||||
]));
|
||||
expect((await response.read().toList()).first, source.readAsBytesSync());
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -102,12 +103,30 @@ void main() {
|
||||
.handleRequest(Request('GET', Uri.parse('http://foobar/foo.js')));
|
||||
|
||||
expect(response.headers, allOf(<Matcher>[
|
||||
containsPair('content-length', '9'),
|
||||
containsPair('content-type', 'application/javascript'),
|
||||
containsPair(HttpHeaders.contentLengthHeader, '9'),
|
||||
containsPair(HttpHeaders.contentTypeHeader, 'application/javascript'),
|
||||
containsPair(HttpHeaders.etagHeader, isNotNull),
|
||||
containsPair(HttpHeaders.cacheControlHeader, 'max-age=0, must-revalidate')
|
||||
]));
|
||||
expect((await response.read().toList()).first, utf8.encode('main() {}'));
|
||||
}));
|
||||
|
||||
test('Returns notModified when the ifNoneMatch header matches the etag', () => testbed.run(() async {
|
||||
webAssetServer.writeFile('/foo.js', 'main() {}');
|
||||
|
||||
final Response response = await webAssetServer
|
||||
.handleRequest(Request('GET', Uri.parse('http://foobar/foo.js')));
|
||||
final String etag = response.headers[HttpHeaders.etagHeader];
|
||||
|
||||
final Response cachedResponse = await webAssetServer
|
||||
.handleRequest(Request('GET', Uri.parse('http://foobar/foo.js'), headers: <String, String>{
|
||||
HttpHeaders.ifNoneMatchHeader: etag
|
||||
}));
|
||||
|
||||
expect(cachedResponse.statusCode, HttpStatus.notModified);
|
||||
expect(await cachedResponse.read().toList(), isEmpty);
|
||||
}));
|
||||
|
||||
test('handles missing JavaScript files from in memory cache', () => testbed.run(() async {
|
||||
final File source = globals.fs.file('source')
|
||||
..writeAsStringSync('main() {}');
|
||||
@ -141,8 +160,10 @@ void main() {
|
||||
.handleRequest(Request('GET', Uri.parse('http://localhost/foo.js')));
|
||||
|
||||
expect(response.headers, allOf(<Matcher>[
|
||||
containsPair('content-length', source.lengthSync().toString()),
|
||||
containsPair('content-type', 'application/javascript'),
|
||||
containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
|
||||
containsPair(HttpHeaders.contentTypeHeader, 'application/javascript'),
|
||||
containsPair(HttpHeaders.etagHeader, isNotNull),
|
||||
containsPair(HttpHeaders.cacheControlHeader, 'max-age=0, must-revalidate')
|
||||
]));
|
||||
expect((await response.read().toList()).first, source.readAsBytesSync());
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -157,8 +178,10 @@ void main() {
|
||||
.handleRequest(Request('GET', Uri.parse('http://foobar/assets/abcd%25E8%25B1%25A1%25E5%25BD%25A2%25E5%25AD%2597.png')));
|
||||
|
||||
expect(response.headers, allOf(<Matcher>[
|
||||
containsPair('content-length', source.lengthSync().toString()),
|
||||
containsPair('content-type', 'image/png'),
|
||||
containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
|
||||
containsPair(HttpHeaders.contentTypeHeader, 'image/png'),
|
||||
containsPair(HttpHeaders.etagHeader, isNotNull),
|
||||
containsPair(HttpHeaders.cacheControlHeader, 'max-age=0, must-revalidate')
|
||||
]));
|
||||
expect((await response.read().toList()).first, source.readAsBytesSync());
|
||||
}));
|
||||
@ -171,8 +194,10 @@ void main() {
|
||||
.handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo.png')));
|
||||
|
||||
expect(response.headers, allOf(<Matcher>[
|
||||
containsPair('content-length', source.lengthSync().toString()),
|
||||
containsPair('content-type', 'image/png'),
|
||||
containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
|
||||
containsPair(HttpHeaders.contentTypeHeader, 'image/png'),
|
||||
containsPair(HttpHeaders.etagHeader, isNotNull),
|
||||
containsPair(HttpHeaders.cacheControlHeader, 'max-age=0, must-revalidate')
|
||||
]));
|
||||
expect((await response.read().toList()).first, source.readAsBytesSync());
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -187,7 +212,7 @@ void main() {
|
||||
final Response response = await webAssetServer
|
||||
.handleRequest(Request('GET', Uri.parse('http://foobar/foo.dart')));
|
||||
|
||||
expect(response.headers, containsPair('content-length', source.lengthSync().toString()));
|
||||
expect(response.headers, containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()));
|
||||
expect((await response.read().toList()).first, source.readAsBytesSync());
|
||||
}, overrides: <Type, Generator>{
|
||||
Platform: () => linux,
|
||||
@ -209,8 +234,8 @@ void main() {
|
||||
.handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo.png')));
|
||||
|
||||
expect(response.headers, allOf(<Matcher>[
|
||||
containsPair('content-length', source.lengthSync().toString()),
|
||||
containsPair('content-type', 'image/png'),
|
||||
containsPair(HttpHeaders.contentLengthHeader, source.lengthSync().toString()),
|
||||
containsPair(HttpHeaders.contentTypeHeader, 'image/png'),
|
||||
]));
|
||||
expect((await response.read().toList()).first, source.readAsBytesSync());
|
||||
}));
|
||||
@ -224,8 +249,8 @@ void main() {
|
||||
.handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo')));
|
||||
|
||||
expect(response.headers, allOf(<Matcher>[
|
||||
containsPair('content-length', '100'),
|
||||
containsPair('content-type', 'application/octet-stream'),
|
||||
containsPair(HttpHeaders.contentLengthHeader, '100'),
|
||||
containsPair(HttpHeaders.contentTypeHeader, 'application/octet-stream'),
|
||||
]));
|
||||
expect((await response.read().toList()).first, source.readAsBytesSync());
|
||||
}));
|
||||
@ -239,8 +264,8 @@ void main() {
|
||||
.handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo')));
|
||||
|
||||
expect(response.headers, allOf(<Matcher>[
|
||||
containsPair('content-length', '3'),
|
||||
containsPair('content-type', 'application/octet-stream'),
|
||||
containsPair(HttpHeaders.contentLengthHeader, '3'),
|
||||
containsPair(HttpHeaders.contentTypeHeader, 'application/octet-stream'),
|
||||
]));
|
||||
expect((await response.read().toList()).first, source.readAsBytesSync());
|
||||
}));
|
||||
@ -264,8 +289,8 @@ void main() {
|
||||
.handleRequest(Request('GET', Uri.parse('http:///packages/flutter_tools/foo.dart')));
|
||||
|
||||
expect(response.headers, allOf(<Matcher>[
|
||||
containsPair('content-length', '3'),
|
||||
containsPair('content-type', 'application/octet-stream'),
|
||||
containsPair(HttpHeaders.contentLengthHeader, '3'),
|
||||
containsPair(HttpHeaders.contentTypeHeader, 'application/octet-stream'),
|
||||
]));
|
||||
expect((await response.read().toList()).first, source.readAsBytesSync());
|
||||
}));
|
||||
|
Loading…
Reference in New Issue
Block a user