| // Copyright (c) 2014, 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. |
| |
| /// A dev-time only server. |
| library services_dev; |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:args/args.dart'; |
| import 'package:logging/logging.dart'; |
| import 'package:rpc/rpc.dart'; |
| import 'package:shelf/shelf.dart'; |
| import 'package:shelf/shelf_io.dart' as shelf; |
| |
| import 'src/common.dart'; |
| import 'src/common_server.dart'; |
| import 'src/common_server_impl.dart'; |
| import 'src/common_server_proto.dart'; |
| import 'src/flutter_web.dart'; |
| import 'src/sdk_manager.dart'; |
| import 'src/server_cache.dart'; |
| import 'src/shelf_cors.dart' as shelf_cors; |
| |
| final Logger _logger = Logger('services'); |
| |
| void main(List<String> args) { |
| final parser = ArgParser(); |
| parser.addOption('port', abbr: 'p', defaultsTo: '8080'); |
| parser.addFlag('discovery'); |
| parser.addFlag('relay'); |
| parser.addOption('server-url', defaultsTo: 'http://localhost'); |
| |
| final result = parser.parse(args); |
| final port = int.tryParse(result['port'] as String); |
| if (port == null) { |
| stdout.writeln( |
| 'Could not parse port value "${result['port']}" into a number.'); |
| exit(1); |
| } |
| |
| final sdk = sdkPath; |
| |
| void printExit(String doc) { |
| print(doc); |
| exit(0); |
| } |
| |
| if (result['discovery'] as bool) { |
| final serverUrl = result['server-url'] as String; |
| EndpointsServer.generateDiscovery(SdkManager.flutterSdk, serverUrl) |
| .then(printExit); |
| |
| return; |
| } |
| |
| Logger.root.level = Level.FINER; |
| Logger.root.onRecord.listen((LogRecord record) { |
| print(record); |
| if (record.stackTrace != null) print(record.stackTrace); |
| }); |
| |
| EndpointsServer.serve(sdk, port).then((EndpointsServer server) { |
| _logger.info('Listening on port ${server.port}'); |
| }); |
| } |
| |
| class EndpointsServer { |
| static Future<EndpointsServer> serve(String sdkPath, int port) { |
| final endpointsServer = EndpointsServer._(sdkPath, port); |
| |
| return shelf |
| .serve(endpointsServer.handler, InternetAddress.anyIPv4, port) |
| .then((HttpServer server) { |
| endpointsServer.server = server; |
| return endpointsServer; |
| }); |
| } |
| |
| static Future<String> generateDiscovery( |
| FlutterSdk flutterSdk, String serverUrl) async { |
| final flutterWebManager = FlutterWebManager(flutterSdk); |
| final commonServerImpl = CommonServerImpl( |
| sdkPath, |
| flutterWebManager, |
| _ServerContainer(), |
| _Cache(), |
| ); |
| final commonServer = CommonServer(commonServerImpl); |
| await commonServerImpl.init(); |
| final apiServer = ApiServer(apiPrefix: '/api', prettyPrint: true) |
| ..addApi(commonServer); |
| apiServer.enableDiscoveryApi(); |
| |
| final uri = Uri.parse('/api/discovery/v1/apis/dartservices/v1/rest'); |
| final request = HttpApiRequest('GET', uri, <String, dynamic>{}, |
| Stream<List<int>>.fromIterable(<List<int>>[])); |
| final response = await apiServer.handleHttpApiRequest(request); |
| return utf8.decode(await response.body.first); |
| } |
| |
| final int port; |
| HttpServer server; |
| |
| Pipeline pipeline; |
| Handler handler; |
| |
| ApiServer apiServer; |
| bool discoveryEnabled; |
| CommonServer commonServer; |
| CommonServerProto commonServerProto; |
| FlutterWebManager flutterWebManager; |
| |
| EndpointsServer._(String sdkPath, this.port) { |
| discoveryEnabled = false; |
| |
| flutterWebManager = FlutterWebManager(SdkManager.flutterSdk); |
| final commonServerImpl = CommonServerImpl( |
| sdkPath, |
| flutterWebManager, |
| _ServerContainer(), |
| _Cache(), |
| ); |
| commonServer = CommonServer(commonServerImpl); |
| commonServerProto = CommonServerProto(commonServerImpl); |
| commonServerImpl.init(); |
| |
| apiServer = ApiServer(apiPrefix: '/api', prettyPrint: true) |
| ..addApi(commonServer); |
| |
| pipeline = Pipeline() |
| .addMiddleware(logRequests()) |
| .addMiddleware(_createCustomCorsHeadersMiddleware()); |
| |
| handler = pipeline.addHandler((request) { |
| if (request.requestedUri.path.startsWith(PROTO_API_URL_PREFIX)) { |
| return commonServerProto.router.handler(request); |
| } |
| return _apiHandler(request); |
| }); |
| } |
| |
| Future<Response> _apiHandler(Request request) { |
| if (!discoveryEnabled) { |
| apiServer.enableDiscoveryApi(); |
| discoveryEnabled = true; |
| } |
| |
| // NOTE: We could read in the request body here and parse it similar to the |
| // _parseRequest method to determine content-type and dispatch to e.g. a |
| // plain text handler if we want to support that. |
| final apiRequest = HttpApiRequest( |
| request.method, request.requestedUri, request.headers, request.read()); |
| |
| // Promote text/plain requests to application/json. |
| if (apiRequest.headers['content-type'] == 'text/plain; charset=utf-8') { |
| apiRequest.headers['content-type'] = 'application/json; charset=utf-8'; |
| } |
| |
| return apiServer |
| .handleHttpApiRequest(apiRequest) |
| .then((HttpApiResponse apiResponse) { |
| // TODO(jcollins-g): use sendApiResponse helper? |
| return Response(apiResponse.status, |
| body: apiResponse.body, |
| headers: Map<String, String>.from(apiResponse.headers)); |
| }); |
| } |
| |
| Response printUsage(Request request, dynamic e, StackTrace stackTrace) { |
| return Response.ok(''' |
| Dart Services server |
| |
| View the available API calls at /api/discovery/v1/apis/dartservices/v1/rest. |
| |
| Error: $e |
| Stack Trace: ${stackTrace.toString()} |
| '''); |
| } |
| |
| Middleware _createCustomCorsHeadersMiddleware() { |
| return shelf_cors.createCorsHeadersMiddleware(corsHeaders: <String, String>{ |
| 'Access-Control-Allow-Origin': '*', |
| 'Access-Control-Allow-Methods': 'POST, OPTIONS', |
| 'Access-Control-Allow-Headers': |
| 'Origin, X-Requested-With, Content-Type, Accept, x-goog-api-client' |
| }); |
| } |
| } |
| |
| class _ServerContainer implements ServerContainer { |
| @override |
| String get version => '1.0'; |
| } |
| |
| class _Cache implements ServerCache { |
| @override |
| Future<String> get(String key) => Future<String>.value(null); |
| |
| @override |
| Future<void> set(String key, String value, {Duration expiration}) => |
| Future<void>.value(); |
| |
| @override |
| Future<void> remove(String key) => Future<void>.value(); |
| |
| @override |
| Future<void> shutdown() => Future<void>.value(); |
| } |