blob: 214d5a753395a4616f10ea242a55732a3e6f61fc [file] [log] [blame]
// Copyright (c) 2015, 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.
library services.common_server_impl;
import 'dart:async';
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'package:pedantic/pedantic.dart';
import 'package:protobuf/protobuf.dart' as $pb;
import '../version.dart';
import 'analysis_servers.dart';
import 'common.dart';
import 'compiler.dart';
import 'protos/dart_services.pb.dart' as proto;
import 'sdk.dart';
import 'server_cache.dart';
const Duration _standardExpiration = Duration(hours: 1);
final Logger log = Logger('common_server');
class BadRequest implements Exception {
String cause;
BadRequest(this.cause);
}
abstract class ServerContainer {
String get version;
}
class CommonServerImplProxy implements CommonServerImpl {
const CommonServerImplProxy(this._proxyTarget);
final String _proxyTarget;
final bool _nullSafety = false;
Future<R> _postToProxy<R extends $pb.GeneratedMessage>(
String url, $pb.GeneratedMessage request, R responseProto) async {
final proxyResponse = http.post(url,
headers: {'Content-Type': 'application/json; charset=utf-8'},
body: _jsonEncoder.convert(request.toProto3Json()));
return proxyResponse.then((response) async {
if (response.statusCode == 200) {
return responseProto
..mergeFromProto3Json(JsonDecoder().convert(response.body));
} else {
final err = proto.BadRequest.create()
..mergeFromProto3Json(JsonDecoder().convert(response.body));
throw BadRequest(err.error.message);
}
});
}
@override
Future<proto.AnalysisResults> analyze(proto.SourceRequest request) {
final url = '${_proxyTarget}api/dartservices/v2/analyze';
return _postToProxy(url, request, proto.AnalysisResults.create());
}
@override
Future<proto.AssistsResponse> assists(proto.SourceRequest request) {
final url = '${_proxyTarget}api/dartservices/v2/assists';
return _postToProxy(url, request, proto.AssistsResponse.create());
}
@override
Future<proto.CompileResponse> compile(proto.CompileRequest request) {
final url = '${_proxyTarget}api/dartservices/v2/compile';
return _postToProxy(url, request, proto.CompileResponse.create());
}
@override
Future<proto.CompileDDCResponse> compileDDC(proto.CompileDDCRequest request) {
final url = '${_proxyTarget}api/dartservices/v2/compileDDC';
return _postToProxy(url, request, proto.CompileDDCResponse.create());
}
@override
Future<proto.CompleteResponse> complete(proto.SourceRequest request) {
final url = '${_proxyTarget}api/dartservices/v2/complete';
return _postToProxy(url, request, proto.CompleteResponse.create());
}
@override
Future<proto.DocumentResponse> document(proto.SourceRequest request) {
final url = '${_proxyTarget}api/dartservices/v2/document';
return _postToProxy(url, request, proto.DocumentResponse.create());
}
@override
Future<proto.FixesResponse> fixes(proto.SourceRequest request) {
final url = '${_proxyTarget}api/dartservices/v2/fixes';
return _postToProxy(url, request, proto.FixesResponse.create());
}
@override
Future<proto.FormatResponse> format(proto.SourceRequest request) {
final url = '${_proxyTarget}api/dartservices/v2/format';
return _postToProxy(url, request, proto.FormatResponse.create());
}
@override
Future<proto.VersionResponse> version(proto.VersionRequest request) {
final url = '${_proxyTarget}api/dartservices/v2/version';
return _postToProxy(url, request, proto.VersionResponse.create());
}
@override
Future<String> _checkCache(String query) => null;
@override
Future<proto.CompileDDCResponse> _compileDDC(String source) => null;
@override
Future<proto.CompileResponse> _compileDart2js(String source,
{bool returnSourceMap = false}) =>
null;
@override
Future<void> _setCache(String query, String result) => null;
@override
bool get analysisServersRunning => true;
@override
Future<void> init() => null;
@override
bool get isHealthy => true;
@override
bool get isRestarting => false;
@override
Future<dynamic> shutdown() => null;
@override
AnalysisServersWrapper get _analysisServers => null;
@override
Compiler get _compiler => null;
@override
ServerCache get _cache => null;
@override
ServerContainer get _container => null;
@override
set _analysisServers(AnalysisServersWrapper analysisServers) => null;
@override
set _compiler(Compiler compiler) => null;
}
final JsonEncoder _jsonEncoder = const JsonEncoder.withIndent(' ');
class CommonServerImpl {
final ServerContainer _container;
final ServerCache _cache;
final bool _nullSafety;
Compiler _compiler;
AnalysisServersWrapper _analysisServers;
// Restarting and health status of the two Analysis Servers
bool get analysisServersRunning => _analysisServers.running;
bool get isRestarting => _analysisServers.isRestarting;
bool get isHealthy => _analysisServers.isHealthy;
CommonServerImpl(
this._container,
this._cache,
this._nullSafety,
) {
hierarchicalLoggingEnabled = true;
log.level = Level.ALL;
}
Future<void> init() async {
log.info('Beginning CommonServer init().');
_analysisServers = AnalysisServersWrapper(_nullSafety);
_compiler = Compiler(Sdk(), _nullSafety);
await _compiler.warmup();
await _analysisServers.warmup();
}
Future<dynamic> shutdown() {
return Future.wait(<Future<dynamic>>[
_analysisServers.shutdown(),
_compiler.dispose(),
Future<dynamic>.sync(_cache.shutdown)
]).timeout(const Duration(minutes: 1));
}
Future<proto.AnalysisResults> analyze(proto.SourceRequest request) {
if (!request.hasSource()) {
throw BadRequest('Missing parameter: \'source\'');
}
return _analysisServers.analyze(request.source);
}
Future<proto.CompileResponse> compile(proto.CompileRequest request) {
if (!request.hasSource()) {
throw BadRequest('Missing parameter: \'source\'');
}
return _compileDart2js(request.source,
returnSourceMap: request.returnSourceMap ?? false);
}
Future<proto.CompileDDCResponse> compileDDC(proto.CompileDDCRequest request) {
if (!request.hasSource()) {
throw BadRequest('Missing parameter: \'source\'');
}
return _compileDDC(request.source);
}
Future<proto.CompleteResponse> complete(proto.SourceRequest request) {
if (!request.hasSource()) {
throw BadRequest('Missing parameter: \'source\'');
}
if (!request.hasOffset()) {
throw BadRequest('Missing parameter: \'offset\'');
}
return _analysisServers.complete(request.source, request.offset);
}
Future<proto.FixesResponse> fixes(proto.SourceRequest request) {
if (!request.hasSource()) {
throw BadRequest('Missing parameter: \'source\'');
}
if (!request.hasOffset()) {
throw BadRequest('Missing parameter: \'offset\'');
}
return _analysisServers.getFixes(request.source, request.offset);
}
Future<proto.AssistsResponse> assists(proto.SourceRequest request) {
if (!request.hasSource()) {
throw BadRequest('Missing parameter: \'source\'');
}
if (!request.hasOffset()) {
throw BadRequest('Missing parameter: \'offset\'');
}
return _analysisServers.getAssists(request.source, request.offset);
}
Future<proto.FormatResponse> format(proto.SourceRequest request) {
if (!request.hasSource()) {
throw BadRequest('Missing parameter: \'source\'');
}
return _analysisServers.format(request.source, request.offset ?? 0);
}
Future<proto.DocumentResponse> document(proto.SourceRequest request) async {
if (!request.hasSource()) {
throw BadRequest('Missing parameter: \'source\'');
}
if (!request.hasOffset()) {
throw BadRequest('Missing parameter: \'offset\'');
}
return proto.DocumentResponse()
..info.addAll(
await _analysisServers.dartdoc(request.source, request.offset) ??
<String, String>{});
}
Future<proto.VersionResponse> version(proto.VersionRequest _) {
final sdk = Sdk();
return Future<proto.VersionResponse>.value(
proto.VersionResponse()
..sdkVersion = sdk.version
..sdkVersionFull = sdk.versionFull
..runtimeVersion = vmVersion
..servicesVersion = servicesVersion
..appEngineVersion = _container.version
..flutterDartVersion = sdk.version
..flutterDartVersionFull = sdk.versionFull
..flutterVersion = sdk.flutterVersion,
);
}
Future<proto.CompileResponse> _compileDart2js(
String source, {
bool returnSourceMap = false,
}) async {
try {
final sourceHash = _hashSource(source);
final memCacheKey = '%%COMPILE:v0'
':returnSourceMap:$returnSourceMap:source:$sourceHash';
final result = await _checkCache(memCacheKey);
if (result != null) {
log.info('CACHE: Cache hit for compileDart2js');
final resultObj = const JsonDecoder().convert(result);
final response = proto.CompileResponse()
..result = resultObj['compiledJS'] as String;
if (resultObj['sourceMap'] != null) {
response.sourceMap = resultObj['sourceMap'] as String;
}
return response;
}
log.info('CACHE: MISS for compileDart2js');
final watch = Stopwatch()..start();
final results =
await _compiler.compile(source, returnSourceMap: returnSourceMap);
if (results.hasOutput) {
final lineCount = source.split('\n').length;
final outputSize = (results.compiledJS.length / 1024).ceil();
final ms = watch.elapsedMilliseconds;
log.info('PERF: Compiled $lineCount lines of Dart into '
'${outputSize}kb of JavaScript in ${ms}ms using dart2js.');
final sourceMap = returnSourceMap ? results.sourceMap : null;
final cachedResult = const JsonEncoder().convert(<String, String>{
'compiledJS': results.compiledJS,
'sourceMap': sourceMap,
});
// Don't block on cache set.
unawaited(_setCache(memCacheKey, cachedResult));
final compileResponse = proto.CompileResponse();
compileResponse.result = results.compiledJS;
if (sourceMap != null) {
compileResponse.sourceMap = sourceMap;
}
return compileResponse;
} else {
final problems = results.problems;
final errors = problems.map(_printCompileProblem).join('\n');
throw BadRequest(errors);
}
} catch (e, st) {
if (e is! BadRequest) {
log.severe('Error during compile (dart2js) on "$source"', e, st);
}
rethrow;
}
}
Future<proto.CompileDDCResponse> _compileDDC(String source) async {
try {
final sourceHash = _hashSource(source);
final memCacheKey = '%%COMPILE_DDC:v0:source:$sourceHash';
final result = await _checkCache(memCacheKey);
if (result != null) {
log.info('CACHE: Cache hit for compileDDC');
final resultObj = const JsonDecoder().convert(result);
return proto.CompileDDCResponse()
..result = resultObj['compiledJS'] as String
..modulesBaseUrl = resultObj['modulesBaseUrl'] as String;
}
log.info('CACHE: MISS for compileDDC');
final watch = Stopwatch()..start();
final results = await _compiler.compileDDC(source);
if (results.hasOutput) {
final lineCount = source.split('\n').length;
final outputSize = (results.compiledJS.length / 1024).ceil();
final ms = watch.elapsedMilliseconds;
log.info('PERF: Compiled $lineCount lines of Dart into '
'${outputSize}kb of JavaScript in ${ms}ms using DDC.');
final cachedResult = const JsonEncoder().convert(<String, String>{
'compiledJS': results.compiledJS,
'modulesBaseUrl': results.modulesBaseUrl,
});
// Don't block on cache set.
unawaited(_setCache(memCacheKey, cachedResult));
return proto.CompileDDCResponse()
..result = results.compiledJS
..modulesBaseUrl = results.modulesBaseUrl;
} else {
final problems = results.problems;
final errors = problems.map(_printCompileProblem).join('\n');
throw BadRequest(errors);
}
} catch (e, st) {
if (e is! BadRequest) {
log.severe('Error during compile (DDC) on "$source"', e, st);
}
rethrow;
}
}
Future<String> _checkCache(String query) => _cache.get(query);
Future<void> _setCache(String query, String result) =>
_cache.set(query, result, expiration: _standardExpiration);
}
String _printCompileProblem(CompilationProblem problem) => problem.message;
String _hashSource(String str) {
return sha1.convert(str.codeUnits).toString();
}