Unverified Commit 31254fbe authored by Mouad Debbar's avatar Mouad Debbar Committed by GitHub

[web] Change the web server to support path url strategy (#66606)

parent eb543889
......@@ -190,6 +190,7 @@ class WebAssetServer implements AssetReader {
platform: globals.platform,
flutterRoot: Cache.flutterRoot,
webBuildDirectory: getWebBuildDirectory(),
basePath: server.basePath,
);
shelf.serveRequests(httpServer, releaseAssetServer.handle);
return server;
......@@ -316,30 +317,28 @@ class WebAssetServer implements AssetReader {
@visibleForTesting
Uint8List getMetadata(String path) => _metadataFiles[path];
@visibleForTesting
/// The base path to serve from.
///
/// It should have no leading or trailing slashes.
String basePath = '';
// handle requests for JavaScript source, dart sources maps, or asset files.
@visibleForTesting
Future<shelf.Response> handleRequest(shelf.Request request) async {
String requestPath = request.url.path;
while (requestPath.startsWith('/')) {
requestPath = requestPath.substring(1);
final String requestPath = _stripBasePath(request.url.path, basePath);
if (requestPath == null) {
return shelf.Response.notFound('');
}
final Map<String, String> headers = <String, String>{};
// If the response is `/`, then we are requesting the index file.
if (request.url.path == '/' || request.url.path.isEmpty) {
final File indexFile = globals.fs.currentDirectory
.childDirectory('web')
.childFile('index.html');
if (indexFile.existsSync()) {
headers[HttpHeaders.contentTypeHeader] = 'text/html';
headers[HttpHeaders.contentLengthHeader] = indexFile.lengthSync().toString();
return shelf.Response.ok(indexFile.openRead(), headers: headers);
} else {
headers[HttpHeaders.contentTypeHeader] = 'text/html';
headers[HttpHeaders.contentLengthHeader] = _kDefaultIndex.length.toString();
return shelf.Response.ok(_kDefaultIndex, headers: headers);
}
if (requestPath == '/' || requestPath.isEmpty) {
return _serveIndex();
}
final Map<String, String> headers = <String, String>{};
// Track etag headers for better caching of resources.
final String ifNoneMatch = request.headers[HttpHeaders.ifNoneMatchHeader];
headers[HttpHeaders.cacheControlHeader] = 'max-age=0, must-revalidate';
......@@ -407,7 +406,7 @@ class WebAssetServer implements AssetReader {
}
if (!file.existsSync()) {
return shelf.Response.notFound('');
return _serveIndex();
}
// For real files, use a serialized file stat plus path as a revision.
......@@ -536,6 +535,23 @@ class WebAssetServer implements AssetReader {
/// Whether to use the cavaskit SDK for rendering.
bool canvasKitRendering = false;
shelf.Response _serveIndex() {
final Map<String, String> headers = <String, String>{
HttpHeaders.contentTypeHeader: 'text/html',
};
final File indexFile = globals.fs.currentDirectory
.childDirectory('web')
.childFile('index.html');
if (indexFile.existsSync()) {
headers[HttpHeaders.contentLengthHeader] = indexFile.lengthSync().toString();
return shelf.Response.ok(indexFile.openRead(), headers: headers);
}
headers[HttpHeaders.contentLengthHeader] = _kDefaultIndex.length.toString();
return shelf.Response.ok(_kDefaultIndex, headers: headers);
}
// Attempt to resolve `path` to a dart file.
File _resolveDartFile(String path) {
// Return the actual file objects so that local engine changes are automatically picked up.
......@@ -907,6 +923,7 @@ class ReleaseAssetServer {
@required String webBuildDirectory,
@required String flutterRoot,
@required Platform platform,
this.basePath = '',
}) : _fileSystem = fileSystem,
_platform = platform,
_flutterRoot = flutterRoot,
......@@ -920,6 +937,12 @@ class ReleaseAssetServer {
final FileSystemUtils _fileSystemUtils;
final Platform _platform;
@visibleForTesting
/// The base path to serve from.
///
/// It should have no leading or trailing slashes.
final String basePath;
// Locations where source files, assets, or source maps may be located.
List<Uri> _searchPaths() => <Uri>[
_fileSystem.directory(_webBuildDirectory).uri,
......@@ -931,11 +954,17 @@ class ReleaseAssetServer {
Future<shelf.Response> handle(shelf.Request request) async {
Uri fileUri;
final String requestPath = _stripBasePath(request.url.path, basePath);
if (requestPath == null) {
return shelf.Response.notFound('');
}
if (request.url.toString() == 'main.dart') {
fileUri = entrypoint;
} else {
for (final Uri uri in _searchPaths()) {
final Uri potential = uri.resolve(request.url.path);
final Uri potential = uri.resolve(requestPath);
if (potential == null || !_fileSystem.isFileSync(
potential.toFilePath(windows: _platform.isWindows))) {
continue;
......@@ -955,13 +984,11 @@ class ReleaseAssetServer {
'Content-Type': mimeType,
});
}
if (request.url.path == '') {
final File file = _fileSystem.file(_fileSystem.path.join(_webBuildDirectory, 'index.html'));
return shelf.Response.ok(file.readAsBytesSync(), headers: <String, String>{
'Content-Type': 'text/html',
});
}
return shelf.Response.notFound('');
final File file = _fileSystem.file(_fileSystem.path.join(_webBuildDirectory, 'index.html'));
return shelf.Response.ok(file.readAsBytesSync(), headers: <String, String>{
'Content-Type': 'text/html',
});
}
}
......@@ -975,3 +1002,19 @@ Future<Directory> _loadDwdsDirectory(FileSystem fileSystem, Logger logger) async
);
return fileSystem.directory(packageConfig['dwds'].packageUriRoot);
}
String _stripBasePath(String path, String basePath) {
while (path.startsWith('/')) {
path = path.substring(1);
}
if (path.startsWith(basePath)) {
path = path.substring(basePath.length);
} else {
// The given path isn't under base path, return null to indicate that.
return null;
}
while (path.startsWith('/')) {
path = path.substring(1);
}
return path;
}
......@@ -160,6 +160,61 @@ void main() {
expect((await response.read().toList()).first, source.readAsBytesSync());
}));
test('takes base path into account when serving', () => testbed.run(() async {
webAssetServer.basePath = 'base/path';
globals.fs.file('foo.png').createSync();
globals.fs.currentDirectory = globals.fs.directory('project_directory')
..createSync();
final File source = globals.fs.file(globals.fs.path.join('web', 'foo.png'))
..createSync(recursive: true)
..writeAsBytesSync(kTransparentImage);
final Response response =
await webAssetServer.handleRequest(
Request('GET', Uri.parse('http://foobar/base/path/foo.png')),
);
expect(response.headers, allOf(<Matcher>[
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());
}));
test('serves index.html at the base path', () => testbed.run(() async {
webAssetServer.basePath = 'base/path';
const String htmlContent = '<html><head></head><body id="test"></body></html>';
final Directory webDir = globals.fs.currentDirectory
.childDirectory('web')
..createSync();
webDir.childFile('index.html').writeAsStringSync(htmlContent);
final Response response = await webAssetServer
.handleRequest(Request('GET', Uri.parse('http://foobar/base/path/')));
expect(response.statusCode, HttpStatus.ok);
expect(await response.readAsString(), htmlContent);
}));
test('does not serve outside the base path', () => testbed.run(() async {
webAssetServer.basePath = 'base/path';
const String htmlContent = '<html><head></head><body id="test"></body></html>';
final Directory webDir = globals.fs.currentDirectory
.childDirectory('web')
..createSync();
webDir.childFile('index.html').writeAsStringSync(htmlContent);
final Response response = await webAssetServer
.handleRequest(Request('GET', Uri.parse('http://foobar/')));
expect(response.statusCode, HttpStatus.notFound);
}));
test('serves JavaScript files from in memory cache not from manifest', () => testbed.run(() async {
webAssetServer.writeFile('foo.js', 'main() {}');
......@@ -191,25 +246,18 @@ void main() {
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() {}');
final File sourcemap = globals.fs.file('sourcemap')
..writeAsStringSync('{}');
final File metadata = globals.fs.file('metadata')
..writeAsStringSync('{}');
final File manifest = globals.fs.file('manifest')
..writeAsStringSync(json.encode(<String, Object>{'/foo.js': <String, Object>{
'code': <int>[0, source.lengthSync()],
'sourcemap': <int>[0, 2],
'metadata': <int>[0, 2],
}}));
webAssetServer.write(source, manifest, sourcemap, metadata);
test('serves index.html when path is unknown', () => testbed.run(() async {
const String htmlContent = '<html><head></head><body id="test"></body></html>';
final Directory webDir = globals.fs.currentDirectory
.childDirectory('web')
..createSync();
webDir.childFile('index.html').writeAsStringSync(htmlContent);
final Response response = await webAssetServer
.handleRequest(Request('GET', Uri.parse('http://foobar/bar.js')));
.handleRequest(Request('GET', Uri.parse('http://foobar/bar/baz')));
expect(response.statusCode, HttpStatus.notFound);
expect(response.statusCode, HttpStatus.ok);
expect(await response.readAsString(), htmlContent);
}));
test('serves default index.html', () => testbed.run(() async {
......@@ -333,13 +381,6 @@ void main() {
Platform: () => linux,
}));
test('Handles missing Dart files from filesystem', () => testbed.run(() async {
final Response response = await webAssetServer
.handleRequest(Request('GET', Uri.parse('http://foobar/foo.dart')));
expect(response.statusCode, HttpStatus.notFound);
}));
test('serves asset files from in filesystem with known mime type', () => testbed.run(() async {
final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'foo.png'))
..createSync(recursive: true)
......@@ -397,20 +438,6 @@ void main() {
expect(etag.runes, everyElement(predicate((int char) => char < 255)));
}));
test('handles serving missing asset file', () => testbed.run(() async {
final Response response = await webAssetServer
.handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo')));
expect(response.statusCode, HttpStatus.notFound);
}));
test('handles serving unresolvable package file', () => testbed.run(() async {
final Response response = await webAssetServer
.handleRequest(Request('GET', Uri.parse('http://foobar/packages/notpackage/file')));
expect(response.statusCode, HttpStatus.notFound);
}));
test('serves /packages/<package>/<path> files as if they were '
'package:<package>/<path> uris', () => testbed.run(() async {
final Uri expectedUri = packages.resolve(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment