[flutter_tools] retry sever socket setup (and port selection if port is unspecified) (#69351)
Fixes #69348 If the web development server fails to bind, then retry up to 5 times. If a port was not provided, select a new free port each time.
This commit is contained in:
parent
44d0e52d20
commit
a0860f6e87
@ -136,6 +136,8 @@ class WebAssetServer implements AssetReader {
|
|||||||
final Map<String, String> _modules;
|
final Map<String, String> _modules;
|
||||||
final Map<String, String> _digests;
|
final Map<String, String> _digests;
|
||||||
|
|
||||||
|
int get selectedPort => _httpServer.port;
|
||||||
|
|
||||||
void performRestart(List<String> modules) {
|
void performRestart(List<String> modules) {
|
||||||
for (final String module in modules) {
|
for (final String module in modules) {
|
||||||
// We skip computing the digest by using the hashCode of the underlying buffer.
|
// We skip computing the digest by using the hashCode of the underlying buffer.
|
||||||
@ -171,113 +173,124 @@ class WebAssetServer implements AssetReader {
|
|||||||
bool testMode = false,
|
bool testMode = false,
|
||||||
DwdsLauncher dwdsLauncher = Dwds.start,
|
DwdsLauncher dwdsLauncher = Dwds.start,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
InternetAddress address;
|
||||||
InternetAddress address;
|
if (hostname == 'any') {
|
||||||
if (hostname == 'any') {
|
address = InternetAddress.anyIPv4;
|
||||||
address = InternetAddress.anyIPv4;
|
} else {
|
||||||
} else {
|
address = (await InternetAddress.lookup(hostname)).first;
|
||||||
address = (await InternetAddress.lookup(hostname)).first;
|
|
||||||
}
|
|
||||||
final HttpServer httpServer = await HttpServer.bind(address, port);
|
|
||||||
// Allow rendering in a iframe.
|
|
||||||
httpServer.defaultResponseHeaders.remove('x-frame-options', 'SAMEORIGIN');
|
|
||||||
|
|
||||||
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
|
|
||||||
globals.fs.file(buildInfo.packagesPath),
|
|
||||||
logger: globals.logger,
|
|
||||||
);
|
|
||||||
final Map<String, String> digests = <String, String>{};
|
|
||||||
final Map<String, String> modules = <String, String>{};
|
|
||||||
final WebAssetServer server = WebAssetServer(
|
|
||||||
httpServer,
|
|
||||||
packageConfig,
|
|
||||||
address,
|
|
||||||
modules,
|
|
||||||
digests,
|
|
||||||
buildInfo,
|
|
||||||
);
|
|
||||||
if (testMode) {
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In release builds deploy a simpler proxy server.
|
|
||||||
if (buildInfo.mode != BuildMode.debug) {
|
|
||||||
final ReleaseAssetServer releaseAssetServer = ReleaseAssetServer(
|
|
||||||
entrypoint,
|
|
||||||
fileSystem: globals.fs,
|
|
||||||
platform: globals.platform,
|
|
||||||
flutterRoot: Cache.flutterRoot,
|
|
||||||
webBuildDirectory: getWebBuildDirectory(),
|
|
||||||
basePath: server.basePath,
|
|
||||||
);
|
|
||||||
shelf.serveRequests(httpServer, releaseAssetServer.handle);
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a version string for all active modules. This is populated
|
|
||||||
// along with the `moduleProvider` update logic.
|
|
||||||
Future<Map<String, String>> _digestProvider() async => digests;
|
|
||||||
|
|
||||||
// Ensure dwds is present and provide middleware to avoid trying to
|
|
||||||
// load the through the isolate APIs.
|
|
||||||
final Directory directory =
|
|
||||||
await _loadDwdsDirectory(globals.fs, globals.logger);
|
|
||||||
final shelf.Middleware middleware =
|
|
||||||
(FutureOr<shelf.Response> Function(shelf.Request) innerHandler) {
|
|
||||||
return (shelf.Request request) async {
|
|
||||||
if (request.url.path.endsWith('dwds/src/injected/client.js')) {
|
|
||||||
final Uri uri = directory.uri.resolve('src/injected/client.js');
|
|
||||||
final String result =
|
|
||||||
await globals.fs.file(uri.toFilePath()).readAsString();
|
|
||||||
return shelf.Response.ok(result, headers: <String, String>{
|
|
||||||
HttpHeaders.contentTypeHeader: 'application/javascript'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return innerHandler(request);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
logging.Logger.root.onRecord.listen((logging.LogRecord event) {
|
|
||||||
globals.printTrace('${event.loggerName}: ${event.message}');
|
|
||||||
});
|
|
||||||
|
|
||||||
// In debug builds, spin up DWDS and the full asset server.
|
|
||||||
final Dwds dwds = await dwdsLauncher(
|
|
||||||
assetReader: server,
|
|
||||||
enableDebugExtension: true,
|
|
||||||
buildResults: const Stream<BuildResult>.empty(),
|
|
||||||
chromeConnection: () async {
|
|
||||||
final Chromium chromium = await chromiumLauncher.connectedInstance;
|
|
||||||
return chromium.chromeConnection;
|
|
||||||
},
|
|
||||||
hostname: hostname,
|
|
||||||
urlEncoder: urlTunneller,
|
|
||||||
enableDebugging: true,
|
|
||||||
useSseForDebugProxy: useSseForDebugProxy,
|
|
||||||
useSseForDebugBackend: useSseForDebugBackend,
|
|
||||||
serveDevTools: false,
|
|
||||||
loadStrategy: FrontendServerRequireStrategyProvider(
|
|
||||||
ReloadConfiguration.none, server, _digestProvider)
|
|
||||||
.strategy,
|
|
||||||
expressionCompiler: expressionCompiler,
|
|
||||||
spawnDds: true);
|
|
||||||
shelf.Pipeline pipeline = const shelf.Pipeline();
|
|
||||||
if (enableDwds) {
|
|
||||||
pipeline = pipeline.addMiddleware(middleware);
|
|
||||||
pipeline = pipeline.addMiddleware(dwds.middleware);
|
|
||||||
}
|
|
||||||
final shelf.Handler dwdsHandler =
|
|
||||||
pipeline.addHandler(server.handleRequest);
|
|
||||||
final shelf.Cascade cascade =
|
|
||||||
shelf.Cascade().add(dwds.handler).add(dwdsHandler);
|
|
||||||
shelf.serveRequests(httpServer, cascade.handler);
|
|
||||||
server.dwds = dwds;
|
|
||||||
return server;
|
|
||||||
} on SocketException catch (err) {
|
|
||||||
throwToolExit('Failed to bind web development server:\n$err');
|
|
||||||
}
|
}
|
||||||
assert(false);
|
HttpServer httpServer;
|
||||||
return null;
|
dynamic lastError;
|
||||||
|
for (int i = 0; i < 5; i += 1) {
|
||||||
|
try {
|
||||||
|
httpServer = await HttpServer.bind(address, port ?? await globals.os.findFreePort());
|
||||||
|
break;
|
||||||
|
} on SocketException catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (httpServer == null) {
|
||||||
|
throwToolExit('Failed to bind web development server:\n$lastError');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow rendering in a iframe.
|
||||||
|
httpServer.defaultResponseHeaders.remove('x-frame-options', 'SAMEORIGIN');
|
||||||
|
|
||||||
|
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
|
||||||
|
globals.fs.file(buildInfo.packagesPath),
|
||||||
|
logger: globals.logger,
|
||||||
|
);
|
||||||
|
final Map<String, String> digests = <String, String>{};
|
||||||
|
final Map<String, String> modules = <String, String>{};
|
||||||
|
final WebAssetServer server = WebAssetServer(
|
||||||
|
httpServer,
|
||||||
|
packageConfig,
|
||||||
|
address,
|
||||||
|
modules,
|
||||||
|
digests,
|
||||||
|
buildInfo,
|
||||||
|
);
|
||||||
|
if (testMode) {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In release builds deploy a simpler proxy server.
|
||||||
|
if (buildInfo.mode != BuildMode.debug) {
|
||||||
|
final ReleaseAssetServer releaseAssetServer = ReleaseAssetServer(
|
||||||
|
entrypoint,
|
||||||
|
fileSystem: globals.fs,
|
||||||
|
platform: globals.platform,
|
||||||
|
flutterRoot: Cache.flutterRoot,
|
||||||
|
webBuildDirectory: getWebBuildDirectory(),
|
||||||
|
basePath: server.basePath,
|
||||||
|
);
|
||||||
|
shelf.serveRequests(httpServer, releaseAssetServer.handle);
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a version string for all active modules. This is populated
|
||||||
|
// along with the `moduleProvider` update logic.
|
||||||
|
Future<Map<String, String>> _digestProvider() async => digests;
|
||||||
|
|
||||||
|
// Ensure dwds is present and provide middleware to avoid trying to
|
||||||
|
// load the through the isolate APIs.
|
||||||
|
final Directory directory =
|
||||||
|
await _loadDwdsDirectory(globals.fs, globals.logger);
|
||||||
|
final shelf.Middleware middleware =
|
||||||
|
(FutureOr<shelf.Response> Function(shelf.Request) innerHandler) {
|
||||||
|
return (shelf.Request request) async {
|
||||||
|
if (request.url.path.endsWith('dwds/src/injected/client.js')) {
|
||||||
|
final Uri uri = directory.uri.resolve('src/injected/client.js');
|
||||||
|
final String result =
|
||||||
|
await globals.fs.file(uri.toFilePath()).readAsString();
|
||||||
|
return shelf.Response.ok(result, headers: <String, String>{
|
||||||
|
HttpHeaders.contentTypeHeader: 'application/javascript'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return innerHandler(request);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
logging.Logger.root.onRecord.listen((logging.LogRecord event) {
|
||||||
|
globals.printTrace('${event.loggerName}: ${event.message}');
|
||||||
|
});
|
||||||
|
|
||||||
|
// In debug builds, spin up DWDS and the full asset server.
|
||||||
|
final Dwds dwds = await dwdsLauncher(
|
||||||
|
assetReader: server,
|
||||||
|
enableDebugExtension: true,
|
||||||
|
buildResults: const Stream<BuildResult>.empty(),
|
||||||
|
chromeConnection: () async {
|
||||||
|
final Chromium chromium = await chromiumLauncher.connectedInstance;
|
||||||
|
return chromium.chromeConnection;
|
||||||
|
},
|
||||||
|
hostname: hostname,
|
||||||
|
urlEncoder: urlTunneller,
|
||||||
|
enableDebugging: true,
|
||||||
|
useSseForDebugProxy: useSseForDebugProxy,
|
||||||
|
useSseForDebugBackend: useSseForDebugBackend,
|
||||||
|
serveDevTools: false,
|
||||||
|
loadStrategy: FrontendServerRequireStrategyProvider(
|
||||||
|
ReloadConfiguration.none,
|
||||||
|
server,
|
||||||
|
_digestProvider,
|
||||||
|
).strategy,
|
||||||
|
expressionCompiler: expressionCompiler,
|
||||||
|
spawnDds: true,
|
||||||
|
);
|
||||||
|
shelf.Pipeline pipeline = const shelf.Pipeline();
|
||||||
|
if (enableDwds) {
|
||||||
|
pipeline = pipeline.addMiddleware(middleware);
|
||||||
|
pipeline = pipeline.addMiddleware(dwds.middleware);
|
||||||
|
}
|
||||||
|
final shelf.Handler dwdsHandler =
|
||||||
|
pipeline.addHandler(server.handleRequest);
|
||||||
|
final shelf.Cascade cascade =
|
||||||
|
shelf.Cascade().add(dwds.handler).add(dwdsHandler);
|
||||||
|
shelf.serveRequests(httpServer, cascade.handler);
|
||||||
|
server.dwds = dwds;
|
||||||
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
final BuildInfo _buildInfo;
|
final BuildInfo _buildInfo;
|
||||||
@ -709,7 +722,7 @@ class WebDevFS implements DevFS {
|
|||||||
/// server.
|
/// server.
|
||||||
WebDevFS({
|
WebDevFS({
|
||||||
@required this.hostname,
|
@required this.hostname,
|
||||||
@required this.port,
|
@required int port,
|
||||||
@required this.packagesFilePath,
|
@required this.packagesFilePath,
|
||||||
@required this.urlTunneller,
|
@required this.urlTunneller,
|
||||||
@required this.useSseForDebugProxy,
|
@required this.useSseForDebugProxy,
|
||||||
@ -721,11 +734,10 @@ class WebDevFS implements DevFS {
|
|||||||
@required this.chromiumLauncher,
|
@required this.chromiumLauncher,
|
||||||
@required this.nullAssertions,
|
@required this.nullAssertions,
|
||||||
this.testMode = false,
|
this.testMode = false,
|
||||||
});
|
}) : _port = port;
|
||||||
|
|
||||||
final Uri entrypoint;
|
final Uri entrypoint;
|
||||||
final String hostname;
|
final String hostname;
|
||||||
final int port;
|
|
||||||
final String packagesFilePath;
|
final String packagesFilePath;
|
||||||
final UrlTunneller urlTunneller;
|
final UrlTunneller urlTunneller;
|
||||||
final bool useSseForDebugProxy;
|
final bool useSseForDebugProxy;
|
||||||
@ -736,6 +748,7 @@ class WebDevFS implements DevFS {
|
|||||||
final ExpressionCompiler expressionCompiler;
|
final ExpressionCompiler expressionCompiler;
|
||||||
final ChromiumLauncher chromiumLauncher;
|
final ChromiumLauncher chromiumLauncher;
|
||||||
final bool nullAssertions;
|
final bool nullAssertions;
|
||||||
|
final int _port;
|
||||||
|
|
||||||
WebAssetServer webAssetServer;
|
WebAssetServer webAssetServer;
|
||||||
|
|
||||||
@ -800,7 +813,7 @@ class WebDevFS implements DevFS {
|
|||||||
webAssetServer = await WebAssetServer.start(
|
webAssetServer = await WebAssetServer.start(
|
||||||
chromiumLauncher,
|
chromiumLauncher,
|
||||||
hostname,
|
hostname,
|
||||||
port,
|
_port,
|
||||||
urlTunneller,
|
urlTunneller,
|
||||||
useSseForDebugProxy,
|
useSseForDebugProxy,
|
||||||
useSseForDebugBackend,
|
useSseForDebugBackend,
|
||||||
@ -810,15 +823,16 @@ class WebDevFS implements DevFS {
|
|||||||
expressionCompiler,
|
expressionCompiler,
|
||||||
testMode: testMode,
|
testMode: testMode,
|
||||||
);
|
);
|
||||||
|
final int selectedPort = webAssetServer.selectedPort;
|
||||||
if (buildInfo.dartDefines.contains('FLUTTER_WEB_AUTO_DETECT=true')) {
|
if (buildInfo.dartDefines.contains('FLUTTER_WEB_AUTO_DETECT=true')) {
|
||||||
webAssetServer.webRenderer = WebRendererMode.autoDetect;
|
webAssetServer.webRenderer = WebRendererMode.autoDetect;
|
||||||
} else if (buildInfo.dartDefines.contains('FLUTTER_WEB_USE_SKIA=true')) {
|
} else if (buildInfo.dartDefines.contains('FLUTTER_WEB_USE_SKIA=true')) {
|
||||||
webAssetServer.webRenderer = WebRendererMode.canvaskit;
|
webAssetServer.webRenderer = WebRendererMode.canvaskit;
|
||||||
}
|
}
|
||||||
if (hostname == 'any') {
|
if (hostname == 'any') {
|
||||||
_baseUri = Uri.http('localhost:$port', '');
|
_baseUri = Uri.http('localhost:$selectedPort', '');
|
||||||
} else {
|
} else {
|
||||||
_baseUri = Uri.http('$hostname:$port', '');
|
_baseUri = Uri.http('$hostname:$selectedPort', '');
|
||||||
}
|
}
|
||||||
return _baseUri;
|
return _baseUri;
|
||||||
}
|
}
|
||||||
|
@ -483,11 +483,6 @@ class _ResidentWebRunner extends ResidentWebRunner {
|
|||||||
'Launching ${globals.fsUtils.getDisplayPath(target)} '
|
'Launching ${globals.fsUtils.getDisplayPath(target)} '
|
||||||
'on ${device.device.name} in $modeName mode...',
|
'on ${device.device.name} in $modeName mode...',
|
||||||
);
|
);
|
||||||
final String effectiveHostname = debuggingOptions.hostname ?? 'localhost';
|
|
||||||
final int hostPort = debuggingOptions.port == null
|
|
||||||
? await globals.os.findFreePort()
|
|
||||||
: int.tryParse(debuggingOptions.port);
|
|
||||||
|
|
||||||
if (device.device is ChromiumDevice) {
|
if (device.device is ChromiumDevice) {
|
||||||
_chromiumLauncher = (device.device as ChromiumDevice).chromeLauncher;
|
_chromiumLauncher = (device.device as ChromiumDevice).chromeLauncher;
|
||||||
}
|
}
|
||||||
@ -498,10 +493,11 @@ class _ResidentWebRunner extends ResidentWebRunner {
|
|||||||
debuggingOptions.webEnableExpressionEvaluation
|
debuggingOptions.webEnableExpressionEvaluation
|
||||||
? WebExpressionCompiler(device.generator)
|
? WebExpressionCompiler(device.generator)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
device.devFS = WebDevFS(
|
device.devFS = WebDevFS(
|
||||||
hostname: effectiveHostname,
|
hostname: debuggingOptions.hostname ?? 'localhost',
|
||||||
port: hostPort,
|
port: debuggingOptions.port != null
|
||||||
|
? int.tryParse(debuggingOptions.port)
|
||||||
|
: null,
|
||||||
packagesFilePath: packagesFilePath,
|
packagesFilePath: packagesFilePath,
|
||||||
urlTunneller: urlTunneller,
|
urlTunneller: urlTunneller,
|
||||||
useSseForDebugProxy: debuggingOptions.webUseSseForDebugProxy,
|
useSseForDebugProxy: debuggingOptions.webUseSseForDebugProxy,
|
||||||
|
@ -699,7 +699,7 @@ void main() {
|
|||||||
contains('GENERATED'));
|
contains('GENERATED'));
|
||||||
|
|
||||||
// served on localhost
|
// served on localhost
|
||||||
expect(uri, Uri.http('localhost:0', ''));
|
expect(uri.host, 'localhost');
|
||||||
|
|
||||||
await webDevFS.destroy();
|
await webDevFS.destroy();
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
@ -813,7 +813,7 @@ void main() {
|
|||||||
contains('GENERATED'));
|
contains('GENERATED'));
|
||||||
|
|
||||||
// served on localhost
|
// served on localhost
|
||||||
expect(uri, Uri.http('localhost:0', ''));
|
expect(uri.host, 'localhost');
|
||||||
|
|
||||||
await webDevFS.destroy();
|
await webDevFS.destroy();
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
@ -859,7 +859,7 @@ void main() {
|
|||||||
|
|
||||||
final Uri uri = await webDevFS.create();
|
final Uri uri = await webDevFS.create();
|
||||||
|
|
||||||
expect(uri, Uri.http('localhost:0', ''));
|
expect(uri.host, 'localhost');
|
||||||
await webDevFS.destroy();
|
await webDevFS.destroy();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user