serveDevTools function
Future<HttpServer?>
serveDevTools({
- bool enableStdinCommands = true,
- bool machineMode = false,
- bool debugMode = false,
- bool launchBrowser = false,
- bool enableNotifications = false,
- bool allowEmbedding = false,
- bool headlessMode = false,
- bool verboseMode = false,
- String? hostname,
- String? customDevToolsPath,
- int port = 0,
- int numPortsToTry = defaultTryPorts,
- Handler? handler,
- String? serviceProtocolUri,
- String? profileFilename,
- String? appSizeBase,
- String? appSizeTest,
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. If not provided, the pre-built DevTools application
shipped via pub will be used.
Implementation
// Note: this method is used by the Dart CLI, Flutter 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 = false,
bool headlessMode = false,
bool verboseMode = false,
String? hostname,
String? customDevToolsPath,
int port = 0,
int numPortsToTry = defaultTryPorts,
shelf.Handler? handler,
String? serviceProtocolUri,
String? profileFilename,
String? appSizeBase,
String? appSizeTest,
}) 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.');
}
clients = ClientManager(enableNotifications);
handler ??= await defaultHandler(
clients,
customDevToolsPath: customDevToolsPath,
debugMode: debugMode,
);
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;
}
final _server = server!;
if (allowEmbedding) {
_server.defaultResponseHeaders.remove('x-frame-options', 'SAMEORIGIN');
}
// Ensure browsers don't cache older versions of the app.
_server.defaultResponseHeaders
.add(HttpHeaders.cacheControlHeader, 'max-age=900');
// 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 `appsize`
// 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)}'
'/#/appsize'
'${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.\n'
'\n'
'Hit ctrl-c to terminate the server.';
if (!machineMode && debugMode) {
// Add bold to help find the correct url to open.
message = '\u001b[1m$message\u001b[0m\n';
}
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,
);
// TODO: Refactor machine mode out into a separate class.
if (machineMode) {
final Stream<Map<String, dynamic>> _stdinCommandStream = stdin
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.where((String line) => line.startsWith('{') && line.endsWith('}'))
.map<Map<String, dynamic>>((String line) {
return json.decode(line) as Map<String, dynamic>;
});
// Example input:
// {
// "id":0,
// "method":"vm.register",
// "params":{
// "uri":"<vm-service-uri-here>",
// }
// }
_stdinCommandStream.listen((Map<String, dynamic> json) async {
// ID can be String, int or null
final dynamic id = json['id'];
final Map<String, dynamic> params = json['params'];
switch (json['method']) {
case 'vm.register':
await _handleVmRegister(
id,
params,
machineMode,
headlessMode,
devToolsUrl,
);
break;
case 'devTools.launch':
await _handleDevToolsLaunch(
id,
params,
machineMode,
headlessMode,
devToolsUrl,
);
break;
case 'client.list':
await _handleClientsList(id, params, machineMode);
break;
case 'devTools.survey':
_devToolsUsage ??= DevToolsUsage();
final String surveyRequest = params['surveyRequest'];
final String value = params['value'];
switch (surveyRequest) {
case 'copyAndCreateDevToolsFile':
// Backup and delete ~/.devtools file.
if (backupAndCreateDevToolsStore()) {
_devToolsUsage = null;
printOutput(
'DevTools Survey',
{
'id': id,
'result': {
'sucess': true,
},
},
machineMode: machineMode,
);
}
break;
case 'restoreDevToolsFile':
_devToolsUsage = null;
final content = restoreDevToolsStore();
if (content != null) {
printOutput(
'DevTools Survey',
{
'id': id,
'result': {
'sucess': true,
'content': content,
},
},
machineMode: machineMode,
);
_devToolsUsage = null;
}
break;
case apiSetActiveSurvey:
_devToolsUsage!.activeSurvey = value;
printOutput(
'DevTools Survey',
{
'id': id,
'result': {
'sucess': _devToolsUsage!.activeSurvey == value,
'activeSurvey': _devToolsUsage!.activeSurvey,
},
},
machineMode: machineMode,
);
break;
case apiGetSurveyActionTaken:
printOutput(
'DevTools Survey',
{
'id': id,
'result': {
'activeSurvey': _devToolsUsage!.activeSurvey,
'surveyActionTaken': _devToolsUsage!.surveyActionTaken,
},
},
machineMode: machineMode,
);
break;
case apiSetSurveyActionTaken:
_devToolsUsage!.surveyActionTaken = jsonDecode(value);
printOutput(
'DevTools Survey',
{
'id': id,
'result': {
'activeSurvey': _devToolsUsage!.activeSurvey,
'surveyActionTaken': _devToolsUsage!.surveyActionTaken,
},
},
machineMode: machineMode,
);
break;
case apiGetSurveyShownCount:
printOutput(
'DevTools Survey',
{
'id': id,
'result': {
'activeSurvey': _devToolsUsage!.activeSurvey,
'surveyShownCount': _devToolsUsage!.surveyShownCount,
},
},
machineMode: machineMode,
);
break;
case apiIncrementSurveyShownCount:
_devToolsUsage!.incrementSurveyShownCount();
printOutput(
'DevTools Survey',
{
'id': id,
'result': {
'activeSurvey': _devToolsUsage!.activeSurvey,
'surveyShownCount': _devToolsUsage!.surveyShownCount,
},
},
machineMode: machineMode,
);
break;
default:
printOutput(
'Unknown DevTools Survey Request $surveyRequest',
{
'id': id,
'result': {
'activeSurvey': _devToolsUsage!.activeSurvey,
'surveyActionTaken': _devToolsUsage!.surveyActionTaken,
'surveyShownCount': _devToolsUsage!.surveyShownCount,
},
},
machineMode: machineMode,
);
}
break;
default:
printOutput(
'Unknown method ${json['method']}',
{
'id': id,
'error': 'Unknown method ${json['method']}',
},
machineMode: machineMode,
);
}
});
}
}
return server;
}