serveDevTools method
Future<HttpServer?>
serveDevTools({
- bool enableStdinCommands = true,
- bool machineMode = false,
- bool debugMode = false,
- bool launchBrowser = false,
- bool enableNotifications = false,
- bool allowEmbedding = true,
- bool headlessMode = false,
- bool verboseMode = false,
- bool printDtdUri = false,
- String? hostname,
- String? customDevToolsPath,
- int port = 0,
- int numPortsToTry = defaultTryPorts,
- Handler? handler,
- String? serviceProtocolUri,
- String? profileFilename,
- String? appSizeBase,
- String? appSizeTest,
- String? dtdUri,
Serves DevTools.
handler
is the shelf.Handler
that the server will use for all requests.
If null, defaultHandler
will be used. Defaults to null.
customDevToolsPath
is a path to a directory containing a pre-built
DevTools application.
Implementation
// Note: this method is used by the Dart CLI and by package:dwds.
Future<HttpServer?> serveDevTools({
bool enableStdinCommands = true,
bool machineMode = false,
bool debugMode = false,
bool launchBrowser = false,
bool enableNotifications = false,
bool allowEmbedding = true,
bool headlessMode = false,
bool verboseMode = false,
bool printDtdUri = false,
String? hostname,
String? customDevToolsPath,
int port = 0,
int numPortsToTry = defaultTryPorts,
shelf.Handler? handler,
String? serviceProtocolUri,
String? profileFilename,
String? appSizeBase,
String? appSizeTest,
String? dtdUri,
}) async {
hostname ??= 'localhost';
// Collect profiling information.
if (profileFilename != null && serviceProtocolUri != null) {
final Uri? vmServiceUri = Uri.tryParse(serviceProtocolUri);
if (vmServiceUri != null) {
await _hookupMemoryProfiling(
vmServiceUri,
profileFilename,
verboseMode,
);
}
return null;
}
if (machineMode) {
assert(
enableStdinCommands,
'machineMode only works with enableStdinCommands.',
);
}
clientManager = ClientManager(
requestNotificationPermissions: enableNotifications,
);
String? dtdSecret;
if (dtdUri == null) {
final (:uri, :secret) = await startDtd(
machineMode: machineMode,
printDtdUri: printDtdUri,
);
dtdUri = uri;
dtdSecret = secret;
}
handler ??= await defaultHandler(
buildDir: customDevToolsPath!,
clientManager: clientManager,
dtd: (uri: dtdUri, secret: dtdSecret),
devtoolsExtensionsManager: ExtensionsManager(),
);
HttpServer? server;
SocketException? ex;
while (server == null && numPortsToTry >= 0) {
// If we have tried [numPortsToTry] ports and still have not been able to
// connect, try port 0 to find a random available port.
if (numPortsToTry == 0) port = 0;
try {
server = await HttpMultiServer.bind(hostname, port);
} on SocketException catch (e) {
ex = e;
numPortsToTry--;
port++;
}
}
// Re-throw the last exception if we failed to bind.
if (server == null && ex != null) {
throw ex;
}
// Type promote server.
server!;
if (allowEmbedding) {
server.defaultResponseHeaders.remove('x-frame-options', 'SAMEORIGIN');
// The origin-agent-cluster header is required to support the embedding of
// Dart DevTools in Chrome DevTools.
server.defaultResponseHeaders.add('origin-agent-cluster', '?1');
}
// Ensure browsers don't cache older versions of the app.
server.defaultResponseHeaders.add(
HttpHeaders.cacheControlHeader,
'no-store',
);
// Add the headers required to serve with wasm.
server.defaultResponseHeaders
..add('Cross-Origin-Embedder-Policy', 'credentialless')
..add('Cross-Origin-Opener-Policy', 'same-origin')
..add('Cross-Origin-Resource-Policy', 'cross-origin');
// Serve requests in an error zone to prevent failures
// when running from another error zone.
runZonedGuarded(
() => shelf.serveRequests(server!, handler!),
(e, _) => print('Error serving requests: $e'),
);
final devToolsUrl = 'http://${server.address.host}:${server.port}';
if (launchBrowser) {
if (serviceProtocolUri != null) {
serviceProtocolUri =
normalizeVmServiceUri(serviceProtocolUri).toString();
}
final queryParameters = {
if (serviceProtocolUri != null) 'uri': serviceProtocolUri,
if (appSizeBase != null) 'appSizeBase': appSizeBase,
if (appSizeTest != null) 'appSizeTest': appSizeTest,
};
String url = Uri.parse(devToolsUrl)
.replace(queryParameters: queryParameters)
.toString();
// If app size parameters are present, open to the standalone `app-size`
// page, regardless if there is a vm service uri specified. We only check
// for the presence of [appSizeBase] here because [appSizeTest] may or may
// not be specified (it should only be present for diffs). If [appSizeTest]
// is present without [appSizeBase], we will ignore the parameter.
if (appSizeBase != null) {
final startQueryParamIndex = url.indexOf('?');
if (startQueryParamIndex != -1) {
url = '${url.substring(0, startQueryParamIndex)}'
'/#/app-size'
'${url.substring(startQueryParamIndex)}';
}
}
try {
await Chrome.start([url]);
} catch (e) {
print('Unable to launch Chrome: $e\n');
}
}
if (enableStdinCommands) {
String message = '''Serving DevTools at $devToolsUrl.
Hit ctrl-c to terminate the server.''';
if (!machineMode && debugMode) {
// Add bold to help find the correct url to open.
message = ConsoleUtils.bold('$message\n');
}
DevToolsUtils.printOutput(
message,
{
'event': 'server.started',
// TODO(dantup): Remove this `method` field when we're sure VS Code
// users are all on a newer version that uses `event`. We incorrectly
// used `method` for the original releases.
'method': 'server.started',
'params': {
'host': server.address.host,
'port': server.port,
'pid': pid,
'protocolVersion': protocolVersion,
}
},
machineMode: machineMode,
);
if (machineMode) {
_machineModeCommandHandler = MachineModeCommandHandler(server: this);
await _machineModeCommandHandler!.initialize(
devToolsUrl: devToolsUrl,
headlessMode: headlessMode,
);
}
}
return server;
}