blob: e166503f6f18daf6515b0e86ee892c163a0ef946 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:build_daemon/data/build_status.dart' as daemon;
import 'package:dds/devtools_server.dart';
import 'package:dwds/data/build_result.dart';
import 'package:dwds/dwds.dart';
import 'package:dwds/sdk_configuration.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
import 'package:http_multi_server/http_multi_server.dart';
import 'package:logging/logging.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_proxy/shelf_proxy.dart';
import '../command/configuration.dart';
import '../util.dart';
import 'chrome.dart';
import 'handlers/favicon_handler.dart';
import 'utils.dart' show findPackageConfigFilePath;
Logger _logger = Logger('WebDevServer');
class ServerOptions {
final Configuration configuration;
final int port;
final String target;
final int daemonPort;
ServerOptions(
this.configuration,
this.port,
this.target,
this.daemonPort,
);
}
class WebDevServer {
final HttpServer _server;
final http.Client _client;
final String _protocol;
final Stream<BuildResult> buildResults;
/// Can be null if client.js injection is disabled.
final Dwds? dwds;
final ExpressionCompilerService? ddcService;
final String target;
WebDevServer._(
this.target,
this._server,
this._client,
this._protocol,
this.buildResults,
bool autoRun, {
this.dwds,
this.ddcService,
}) {
if (autoRun) {
dwds?.connectedApps.listen((connection) {
connection.runMain();
});
}
}
String get host => _server.address.host;
int get port => _server.port;
String get protocol => _protocol;
Future<void> stop() async {
await dwds?.stop();
await ddcService?.stop();
await _server.close(force: true);
_client.close();
}
static Future<WebDevServer> start(
ServerOptions options, Stream<daemon.BuildResults> buildResults) async {
var pipeline = const Pipeline();
if (options.configuration.logRequests) {
pipeline = pipeline.addMiddleware(logRequests());
}
pipeline = pipeline.addMiddleware(interceptFavicon);
// Only provide relevant build results
final filteredBuildResults = buildResults.asyncMap<BuildResult>((results) {
final result = results.results
.firstWhere((result) => result.target == options.target);
switch (result.status) {
case daemon.BuildStatus.started:
return BuildResult((b) => b.status = BuildStatus.started);
case daemon.BuildStatus.failed:
return BuildResult((b) => b.status = BuildStatus.failed);
case daemon.BuildStatus.succeeded:
return BuildResult((b) => b.status = BuildStatus.succeeded);
default:
break;
}
throw StateError('Unexpected Daemon build result: $result');
});
var cascade = Cascade();
final client = IOClient(HttpClient()
..maxConnectionsPerHost = 200
..idleTimeout = const Duration(seconds: 30)
..connectionTimeout = const Duration(seconds: 30));
final assetHandler = proxyHandler(
'http://localhost:${options.daemonPort}/${options.target}/',
client: client);
Dwds? dwds;
ExpressionCompilerService? ddcService;
if (options.configuration.enableInjectedClient) {
final assetReader = ProxyServerAssetReader(
options.daemonPort,
root: options.target,
);
// TODO(https://github.com/flutter/devtools/issues/5350): Figure out how
// to determine the build settings from the build.
// Can we save build metadata in build_web_compilers and and read it in
// the load strategy?
final buildSettings = BuildSettings(
appEntrypoint:
Uri.parse('org-dartlang-app:///${options.target}/main.dart'),
canaryFeatures: options.configuration.canaryFeatures,
isFlutterApp: false,
experiments: options.configuration.experiments,
);
final loadStrategy = BuildRunnerRequireStrategyProvider(
assetHandler,
options.configuration.reload,
assetReader,
buildSettings,
packageConfigPath: findPackageConfigFilePath(),
).strategy;
if (options.configuration.enableExpressionEvaluation) {
ddcService = ExpressionCompilerService(
options.configuration.hostname,
options.port,
verbose: options.configuration.verbose,
sdkConfigurationProvider: const DefaultSdkConfigurationProvider(),
);
}
final shouldServeDevTools =
options.configuration.debug || options.configuration.debugExtension;
final debugSettings = DebugSettings(
enableDebugExtension: options.configuration.debugExtension,
enableDebugging: options.configuration.debug,
spawnDds: !options.configuration.disableDds,
expressionCompiler: ddcService,
devToolsLauncher: shouldServeDevTools
? (String hostname) async {
final server = await DevToolsServer().serveDevTools(
hostname: hostname,
enableStdinCommands: false,
customDevToolsPath: devToolsPath,
);
return DevTools(server!.address.host, server.port, server);
}
: null,
);
final appMetadata = AppMetadata(
hostname: options.configuration.hostname,
);
final toolConfiguration = ToolConfiguration(
loadStrategy: loadStrategy,
debugSettings: debugSettings,
appMetadata: appMetadata);
dwds = await Dwds.start(
toolConfiguration: toolConfiguration,
assetReader: assetReader,
buildResults: filteredBuildResults,
chromeConnection: () async =>
(await Chrome.connectedInstance).chromeConnection,
);
pipeline = pipeline.addMiddleware(dwds.middleware);
cascade = cascade.add(dwds.handler);
cascade = cascade.add(assetHandler);
} else {
cascade = cascade.add(assetHandler);
}
final hostname = options.configuration.hostname;
final tlsCertChain = options.configuration.tlsCertChain ?? '';
final tlsCertKey = options.configuration.tlsCertKey ?? '';
HttpServer server;
final protocol =
(tlsCertChain.isNotEmpty && tlsCertKey.isNotEmpty) ? 'https' : 'http';
if (protocol == 'https') {
final serverContext = SecurityContext()
..useCertificateChain(tlsCertChain)
..usePrivateKey(tlsCertKey);
server = await HttpMultiServer.bindSecure(
hostname, options.port, serverContext);
} else {
server = await HttpMultiServer.bind(hostname, options.port);
}
serveHttpRequests(server, pipeline.addHandler(cascade.handler), (e, s) {
_logger.warning('Error serving requests', e, s);
});
return WebDevServer._(
options.target,
server,
client,
protocol,
filteredBuildResults,
options.configuration.autoRun,
dwds: dwds,
ddcService: ddcService,
);
}
}