diff --git a/packages/flutter_tools/lib/src/build_runner/web_fs.dart b/packages/flutter_tools/lib/src/build_runner/web_fs.dart index 8b33d80889..3d83be8250 100644 --- a/packages/flutter_tools/lib/src/build_runner/web_fs.dart +++ b/packages/flutter_tools/lib/src/build_runner/web_fs.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:typed_data'; import 'package:archive/archive.dart'; import 'package:build_daemon/client.dart'; @@ -17,6 +18,7 @@ import 'package:meta/meta.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf_proxy/shelf_proxy.dart'; +import 'package:mime/mime.dart' as mime; import '../artifacts.dart'; import '../asset.dart'; @@ -501,7 +503,14 @@ class DebugAssetServer extends AssetServer { final String assetPath = request.url.path.replaceFirst('assets/', ''); final File file = fs.file(fs.path.join(getAssetBuildDirectory(), assetPath)); if (file.existsSync()) { - return Response.ok(file.readAsBytesSync()); + final Uint8List bytes = file.readAsBytesSync(); + // Fallback to "application/octet-stream" on null which + // makes no claims as to the structure of the data. + final String mimeType = mime.lookupMimeType(file.path, headerBytes: bytes) + ?? 'application/octet-stream'; + return Response.ok(bytes, headers: { + 'Content-Type': mimeType, + }); } else { return Response.notFound(''); } diff --git a/packages/flutter_tools/test/general.shard/web/asset_server_test.dart b/packages/flutter_tools/test/general.shard/web/asset_server_test.dart index 3a85425277..bb154ce9e8 100644 --- a/packages/flutter_tools/test/general.shard/web/asset_server_test.dart +++ b/packages/flutter_tools/test/general.shard/web/asset_server_test.dart @@ -10,6 +10,14 @@ import 'package:shelf/shelf.dart'; import '../../src/common.dart'; import '../../src/testbed.dart'; +const List kTransparentImage = [ + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, + 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, + 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, + 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D, + 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, +]; + void main() { Testbed testbed; AssetServer assetServer; @@ -22,6 +30,12 @@ void main() { fs.file(fs.path.join('web', 'index.html')) ..createSync(recursive: true) ..writeAsStringSync('hello'); + fs.file(fs.path.join('build', 'flutter_assets', 'foo.png')) + ..createSync(recursive: true) + ..writeAsBytesSync(kTransparentImage); + fs.file(fs.path.join('build', 'flutter_assets', 'bar')) + ..createSync(recursive: true) + ..writeAsBytesSync([1, 2, 3]); assetServer = DebugAssetServer(FlutterProject.current(), fs.path.join('main')); } ); @@ -38,6 +52,26 @@ void main() { expect(await response.readAsString(), 'hello'); })); + test('can serve an asset with a png content type', () => testbed.run(() async { + final Response response = await assetServer + .handle(Request('GET', Uri.parse('http://localhost:8080/assets/foo.png'))); + + expect(response.headers, { + 'Content-Type': 'image/png', + 'content-length': '64', + }); + })); + + test('can fallback to application/octet-stream', () => testbed.run(() async { + final Response response = await assetServer + .handle(Request('GET', Uri.parse('http://localhost:8080/assets/bar'))); + + expect(response.headers, { + 'Content-Type': 'application/octet-stream', + 'content-length': '3', + }); + })); + test('handles a missing html file from the web directory', () => testbed.run(() async { final Response response = await assetServer .handle(Request('GET', Uri.parse('http://localhost:8080/foobar.html')));