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 { ...@@ -190,6 +190,7 @@ class WebAssetServer implements AssetReader {
platform: globals.platform, platform: globals.platform,
flutterRoot: Cache.flutterRoot, flutterRoot: Cache.flutterRoot,
webBuildDirectory: getWebBuildDirectory(), webBuildDirectory: getWebBuildDirectory(),
basePath: server.basePath,
); );
shelf.serveRequests(httpServer, releaseAssetServer.handle); shelf.serveRequests(httpServer, releaseAssetServer.handle);
return server; return server;
...@@ -316,30 +317,28 @@ class WebAssetServer implements AssetReader { ...@@ -316,30 +317,28 @@ class WebAssetServer implements AssetReader {
@visibleForTesting @visibleForTesting
Uint8List getMetadata(String path) => _metadataFiles[path]; 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. // handle requests for JavaScript source, dart sources maps, or asset files.
@visibleForTesting @visibleForTesting
Future<shelf.Response> handleRequest(shelf.Request request) async { Future<shelf.Response> handleRequest(shelf.Request request) async {
String requestPath = request.url.path; final String requestPath = _stripBasePath(request.url.path, basePath);
while (requestPath.startsWith('/')) {
requestPath = requestPath.substring(1); 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 the response is `/`, then we are requesting the index file.
if (request.url.path == '/' || request.url.path.isEmpty) { if (requestPath == '/' || requestPath.isEmpty) {
final File indexFile = globals.fs.currentDirectory return _serveIndex();
.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);
}
} }
final Map<String, String> headers = <String, String>{};
// Track etag headers for better caching of resources. // Track etag headers for better caching of resources.
final String ifNoneMatch = request.headers[HttpHeaders.ifNoneMatchHeader]; final String ifNoneMatch = request.headers[HttpHeaders.ifNoneMatchHeader];
headers[HttpHeaders.cacheControlHeader] = 'max-age=0, must-revalidate'; headers[HttpHeaders.cacheControlHeader] = 'max-age=0, must-revalidate';
...@@ -407,7 +406,7 @@ class WebAssetServer implements AssetReader { ...@@ -407,7 +406,7 @@ class WebAssetServer implements AssetReader {
} }
if (!file.existsSync()) { if (!file.existsSync()) {
return shelf.Response.notFound(''); return _serveIndex();
} }
// For real files, use a serialized file stat plus path as a revision. // For real files, use a serialized file stat plus path as a revision.
...@@ -536,6 +535,23 @@ class WebAssetServer implements AssetReader { ...@@ -536,6 +535,23 @@ class WebAssetServer implements AssetReader {
/// Whether to use the cavaskit SDK for rendering. /// Whether to use the cavaskit SDK for rendering.
bool canvasKitRendering = false; 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. // Attempt to resolve `path` to a dart file.
File _resolveDartFile(String path) { File _resolveDartFile(String path) {
// Return the actual file objects so that local engine changes are automatically picked up. // Return the actual file objects so that local engine changes are automatically picked up.
...@@ -907,6 +923,7 @@ class ReleaseAssetServer { ...@@ -907,6 +923,7 @@ class ReleaseAssetServer {
@required String webBuildDirectory, @required String webBuildDirectory,
@required String flutterRoot, @required String flutterRoot,
@required Platform platform, @required Platform platform,
this.basePath = '',
}) : _fileSystem = fileSystem, }) : _fileSystem = fileSystem,
_platform = platform, _platform = platform,
_flutterRoot = flutterRoot, _flutterRoot = flutterRoot,
...@@ -920,6 +937,12 @@ class ReleaseAssetServer { ...@@ -920,6 +937,12 @@ class ReleaseAssetServer {
final FileSystemUtils _fileSystemUtils; final FileSystemUtils _fileSystemUtils;
final Platform _platform; 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. // Locations where source files, assets, or source maps may be located.
List<Uri> _searchPaths() => <Uri>[ List<Uri> _searchPaths() => <Uri>[
_fileSystem.directory(_webBuildDirectory).uri, _fileSystem.directory(_webBuildDirectory).uri,
...@@ -931,11 +954,17 @@ class ReleaseAssetServer { ...@@ -931,11 +954,17 @@ class ReleaseAssetServer {
Future<shelf.Response> handle(shelf.Request request) async { Future<shelf.Response> handle(shelf.Request request) async {
Uri fileUri; Uri fileUri;
final String requestPath = _stripBasePath(request.url.path, basePath);
if (requestPath == null) {
return shelf.Response.notFound('');
}
if (request.url.toString() == 'main.dart') { if (request.url.toString() == 'main.dart') {
fileUri = entrypoint; fileUri = entrypoint;
} else { } else {
for (final Uri uri in _searchPaths()) { 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( if (potential == null || !_fileSystem.isFileSync(
potential.toFilePath(windows: _platform.isWindows))) { potential.toFilePath(windows: _platform.isWindows))) {
continue; continue;
...@@ -955,13 +984,11 @@ class ReleaseAssetServer { ...@@ -955,13 +984,11 @@ class ReleaseAssetServer {
'Content-Type': mimeType, 'Content-Type': mimeType,
}); });
} }
if (request.url.path == '') {
final File file = _fileSystem.file(_fileSystem.path.join(_webBuildDirectory, 'index.html')); final File file = _fileSystem.file(_fileSystem.path.join(_webBuildDirectory, 'index.html'));
return shelf.Response.ok(file.readAsBytesSync(), headers: <String, String>{ return shelf.Response.ok(file.readAsBytesSync(), headers: <String, String>{
'Content-Type': 'text/html', 'Content-Type': 'text/html',
}); });
}
return shelf.Response.notFound('');
} }
} }
...@@ -975,3 +1002,19 @@ Future<Directory> _loadDwdsDirectory(FileSystem fileSystem, Logger logger) async ...@@ -975,3 +1002,19 @@ Future<Directory> _loadDwdsDirectory(FileSystem fileSystem, Logger logger) async
); );
return fileSystem.directory(packageConfig['dwds'].packageUriRoot); 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() { ...@@ -160,6 +160,61 @@ void main() {
expect((await response.read().toList()).first, source.readAsBytesSync()); 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 { test('serves JavaScript files from in memory cache not from manifest', () => testbed.run(() async {
webAssetServer.writeFile('foo.js', 'main() {}'); webAssetServer.writeFile('foo.js', 'main() {}');
...@@ -191,25 +246,18 @@ void main() { ...@@ -191,25 +246,18 @@ void main() {
expect(await cachedResponse.read().toList(), isEmpty); expect(await cachedResponse.read().toList(), isEmpty);
})); }));
test('handles missing JavaScript files from in memory cache', () => testbed.run(() async { test('serves index.html when path is unknown', () => testbed.run(() async {
final File source = globals.fs.file('source') const String htmlContent = '<html><head></head><body id="test"></body></html>';
..writeAsStringSync('main() {}'); final Directory webDir = globals.fs.currentDirectory
final File sourcemap = globals.fs.file('sourcemap') .childDirectory('web')
..writeAsStringSync('{}'); ..createSync();
final File metadata = globals.fs.file('metadata') webDir.childFile('index.html').writeAsStringSync(htmlContent);
..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);
final Response response = await webAssetServer 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 { test('serves default index.html', () => testbed.run(() async {
...@@ -333,13 +381,6 @@ void main() { ...@@ -333,13 +381,6 @@ void main() {
Platform: () => linux, 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 { 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')) final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'foo.png'))
..createSync(recursive: true) ..createSync(recursive: true)
...@@ -397,20 +438,6 @@ void main() { ...@@ -397,20 +438,6 @@ void main() {
expect(etag.runes, everyElement(predicate((int char) => char < 255))); 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 ' test('serves /packages/<package>/<path> files as if they were '
'package:<package>/<path> uris', () => testbed.run(() async { 'package:<package>/<path> uris', () => testbed.run(() async {
final Uri expectedUri = packages.resolve( 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