Cloud run dark launch (#605)

diff --git a/Dockerfile b/Dockerfile
index 4286ffa..2ad8f8d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -50,4 +50,7 @@
 # the Dart app using custom script enabling debug modes.
 CMD []
 
-ENTRYPOINT /bin/bash /dart_runtime/dart_run.sh
+ENTRYPOINT ["/dart_runtime/dart_run.sh", \
+            "--port", "8080", \
+            "--dark-launch", \
+            "--proxy-target", "https://dart-service-cloud-run-hdjctvyqtq-uc.a.run.app/"]
diff --git a/cloud_run.Dockerfile b/cloud_run.Dockerfile
index 26ebf5e..cfa6ee5 100644
--- a/cloud_run.Dockerfile
+++ b/cloud_run.Dockerfile
@@ -50,5 +50,4 @@
 CMD []
 
 ENTRYPOINT ["/dart_runtime/dart_cloud_run.sh", "--port", "${PORT}", \
-            "--server-url", "http://0.0.0.0", \
             "--redis-url", "redis://10.0.0.4:6379"]
diff --git a/lib/services_cloud_run.dart b/lib/services_cloud_run.dart
index 6e6b034..2eaf18b 100644
--- a/lib/services_cloud_run.dart
+++ b/lib/services_cloud_run.dart
@@ -23,15 +23,14 @@
 final Logger _logger = Logger('services');
 
 Future<void> main(List<String> args) async {
-  final parser = ArgParser();
-  parser.addOption('port', abbr: 'p');
-  parser.addOption('server-url', defaultsTo: 'http://localhost');
-  parser.addOption('redis-url');
-  final result = parser.parse(args);
+  final parser = ArgParser()
+    ..addOption('port', abbr: 'p')
+    ..addOption('redis-url');
+  final results = parser.parse(args);
 
   // Cloud Run supplies the port to bind to in the environment.
   // Allow command line arg to override environment.
-  final port = int.tryParse(result['port'] as String ?? '') ??
+  final port = int.tryParse(results['port'] as String ?? '') ??
       int.tryParse(Platform.environment['PORT'] ?? '');
   if (port == null) {
     stdout.writeln('Could not parse port value from either environment '
@@ -39,7 +38,7 @@
     exit(1);
   }
 
-  final redisServerUri = result['redis-url'] as String;
+  final redisServerUri = results['redis-url'] as String;
 
   Logger.root.level = Level.FINER;
   Logger.root.onRecord.listen((LogRecord record) {
diff --git a/lib/services_gae.dart b/lib/services_gae.dart
index 5dfd8b6..1cc7e1c 100644
--- a/lib/services_gae.dart
+++ b/lib/services_gae.dart
@@ -9,6 +9,7 @@
 import 'dart:math';
 
 import 'package:appengine/appengine.dart' as ae;
+import 'package:args/args.dart';
 import 'package:logging/logging.dart';
 import 'package:shelf/shelf_io.dart' as shelf_io;
 
@@ -28,8 +29,15 @@
 final Logger _logger = Logger('gae_server');
 
 void main(List<String> args) {
-  var gaePort = 8080;
-  if (args.isNotEmpty) gaePort = int.parse(args[0]);
+  final parser = ArgParser()
+    ..addFlag('dark-launch', help: 'Dark launch proxied compilation requests')
+    ..addOption('proxy-target',
+        help: 'URL base to proxy compilation requests to')
+    ..addOption('port',
+        abbr: 'p', defaultsTo: '8080', help: 'Port to attach to');
+  final results = parser.parse(args);
+
+  final gaePort = int.tryParse(results['port'] as String ?? '') ?? 8080;
 
   if (SdkManager.sdk.sdkPath == null) {
     throw 'No Dart SDK is available; set the DART_SDK env var.';
@@ -46,30 +54,33 @@
     }
   });
   log.info('''Initializing dart-services:
-    port: $gaePort
+    --port: $gaePort
+    --dark-launch: ${results['dark-launch']}
+    --proxy-target: ${results['proxy-target']}
     sdkPath: ${SdkManager.sdk?.sdkPath}
-    REDIS_SERVER_URI: ${io.Platform.environment['REDIS_SERVER_URI']}
-    GAE_VERSION: ${io.Platform.environment['GAE_VERSION']}
+    \$REDIS_SERVER_URI: ${io.Platform.environment['REDIS_SERVER_URI']}
+    \$GAE_VERSION: ${io.Platform.environment['GAE_VERSION']}
   ''');
 
-  final server = GaeServer(io.Platform.environment['REDIS_SERVER_URI']);
+  final server = GaeServer(
+      io.Platform.environment['REDIS_SERVER_URI'],
+      results['dark-launch'].toString().toLowerCase() == 'true',
+      results['proxy-target'].toString());
   server.start(gaePort);
 }
 
 class GaeServer {
   final String redisServerUri;
 
-  bool discoveryEnabled;
   CommonServerImpl commonServerImpl;
   CommonServerApi commonServerApi;
 
-  GaeServer(this.redisServerUri) {
+  GaeServer(this.redisServerUri, bool darkLaunch, String proxyTarget) {
     hierarchicalLoggingEnabled = true;
     recordStackTraceAtLevel = Level.SEVERE;
 
     _logger.level = Level.ALL;
 
-    discoveryEnabled = false;
     commonServerImpl = CommonServerImpl(
       GaeServerContainer(),
       redisServerUri == null
@@ -79,6 +90,10 @@
               io.Platform.environment['GAE_VERSION'],
             ),
     );
+    if (proxyTarget != null && proxyTarget.isNotEmpty) {
+      commonServerImpl =
+          CommonServerImplProxy(commonServerImpl, darkLaunch, proxyTarget);
+    }
     commonServerApi = CommonServerApi(commonServerImpl);
   }
 
diff --git a/lib/src/common_server_impl.dart b/lib/src/common_server_impl.dart
index e523307..7b78e03 100644
--- a/lib/src/common_server_impl.dart
+++ b/lib/src/common_server_impl.dart
@@ -8,6 +8,7 @@
 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';
 
@@ -32,21 +33,161 @@
   String get version;
 }
 
-class CommonServerImpl {
-  final ServerContainer container;
-  final ServerCache cache;
+class CommonServerImplProxy implements CommonServerImpl {
+  const CommonServerImplProxy(
+      this._wrapped, this._darkLaunch, this._proxyTarget);
+  final CommonServerImpl _wrapped;
+  final String _proxyTarget;
+  final bool _darkLaunch;
 
-  Compiler compiler;
-  AnalysisServersWrapper analysisServers;
+  @override
+  Future<String> _checkCache(String query) => _wrapped._checkCache(query);
+
+  @override
+  Future<proto.CompileDDCResponse> _compileDDC(String source) =>
+      _wrapped._compileDDC(source);
+
+  @override
+  Future<proto.CompileResponse> _compileDart2js(String source,
+          {bool returnSourceMap = false}) =>
+      _wrapped._compileDart2js(source, returnSourceMap: returnSourceMap);
+
+  @override
+  Future<void> _setCache(String query, String result) =>
+      _wrapped._setCache(query, result);
+
+  @override
+  bool get analysisServersRunning => _wrapped.analysisServersRunning;
+
+  @override
+  Future<proto.AnalysisResults> analyze(proto.SourceRequest request) =>
+      _wrapped.analyze(request);
+
+  @override
+  Future<proto.AssistsResponse> assists(proto.SourceRequest request) =>
+      _wrapped.assists(request);
+
+  @override
+  Future<proto.CompileResponse> compile(proto.CompileRequest request) {
+    final url = '${_proxyTarget}api/dartservices/v2/compile';
+    final proxyResponse = http.post(url,
+        headers: {'Content-Type': 'application/json; charset=utf-8'},
+        body: _jsonEncoder.convert(request.toProto3Json()));
+    if (_darkLaunch) {
+      proxyResponse.then((response) {
+        log.info(
+            'compile: Proxied request returned status code: ${response.statusCode}');
+      });
+      return _wrapped.compile(request);
+    } else {
+      return proxyResponse.then((response) async {
+        if (response.statusCode == 200) {
+          return proto.CompileResponse.create()
+            ..mergeFromProto3Json(JsonDecoder().convert(response.body));
+        } else {
+          final err = proto.BadRequest.create()
+            ..mergeFromProto3Json(JsonDecoder().convert(response.body));
+          throw BadRequest(err.error.message);
+        }
+      });
+    }
+  }
+
+  @override
+  Future<proto.CompileDDCResponse> compileDDC(proto.CompileDDCRequest request) {
+    final url = '${_proxyTarget}api/dartservices/v2/compileDDC';
+    final proxyResponse = http.post(url,
+        headers: {'Content-Type': 'application/json; charset=utf-8'},
+        body: _jsonEncoder.convert(request.toProto3Json()));
+    if (_darkLaunch) {
+      proxyResponse.then((response) {
+        log.info(
+            'compileDDC: Proxied request returned status code: ${response.statusCode}');
+      });
+      return _wrapped.compileDDC(request);
+    } else {
+      return proxyResponse.then((response) async {
+        if (response.statusCode == 200) {
+          return proto.CompileDDCResponse.create()
+            ..mergeFromProto3Json(JsonDecoder().convert(response.body));
+        } else {
+          final err = proto.BadRequest.create()
+            ..mergeFromProto3Json(JsonDecoder().convert(response.body));
+          throw BadRequest(err.error.message);
+        }
+      });
+    }
+  }
+
+  @override
+  Future<proto.CompleteResponse> complete(proto.SourceRequest request) =>
+      _wrapped.complete(request);
+
+  @override
+  Future<proto.DocumentResponse> document(proto.SourceRequest request) =>
+      _wrapped.document(request);
+
+  @override
+  Future<proto.FixesResponse> fixes(proto.SourceRequest request) =>
+      _wrapped.fixes(request);
+
+  @override
+  Future<proto.FormatResponse> format(proto.SourceRequest request) =>
+      _wrapped.format(request);
+
+  @override
+  Future<void> init() => _wrapped.init();
+
+  @override
+  bool get isHealthy => _wrapped.isHealthy;
+
+  @override
+  bool get isRestarting => _wrapped.isRestarting;
+
+  @override
+  Future shutdown() => _wrapped.shutdown();
+
+  @override
+  Future<proto.VersionResponse> version(proto.VersionRequest request) =>
+      _wrapped.version(request);
+
+  @override
+  AnalysisServersWrapper get _analysisServers => _wrapped._analysisServers;
+
+  @override
+  Compiler get _compiler => _wrapped._compiler;
+
+  @override
+  ServerCache get _cache => _wrapped._cache;
+
+  @override
+  ServerContainer get _container => _wrapped._container;
+
+  @override
+  set _analysisServers(AnalysisServersWrapper __analysisServers) =>
+      _wrapped._analysisServers = __analysisServers;
+
+  @override
+  set _compiler(Compiler __compiler) => _wrapped._compiler = __compiler;
+}
+
+final JsonEncoder _jsonEncoder = const JsonEncoder.withIndent('  ');
+
+class CommonServerImpl {
+  final ServerContainer _container;
+  final ServerCache _cache;
+
+  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;
+  bool get analysisServersRunning => _analysisServers.running;
+  bool get isRestarting => _analysisServers.isRestarting;
+  bool get isHealthy => _analysisServers.isHealthy;
 
   CommonServerImpl(
-    this.container,
-    this.cache,
+    this._container,
+    this._cache,
   ) {
     hierarchicalLoggingEnabled = true;
     log.level = Level.ALL;
@@ -54,18 +195,18 @@
 
   Future<void> init() async {
     log.info('Beginning CommonServer init().');
-    analysisServers = AnalysisServersWrapper();
-    compiler = Compiler(SdkManager.sdk, SdkManager.flutterSdk);
+    _analysisServers = AnalysisServersWrapper();
+    _compiler = Compiler(SdkManager.sdk, SdkManager.flutterSdk);
 
-    await compiler.warmup();
-    await analysisServers.warmup();
+    await _compiler.warmup();
+    await _analysisServers.warmup();
   }
 
   Future<dynamic> shutdown() {
     return Future.wait(<Future<dynamic>>[
-      analysisServers.shutdown(),
-      compiler.dispose(),
-      Future<dynamic>.sync(cache.shutdown)
+      _analysisServers.shutdown(),
+      _compiler.dispose(),
+      Future<dynamic>.sync(_cache.shutdown)
     ]).timeout(const Duration(minutes: 1));
   }
 
@@ -74,7 +215,7 @@
       throw BadRequest('Missing parameter: \'source\'');
     }
 
-    return analysisServers.analyze(request.source);
+    return _analysisServers.analyze(request.source);
   }
 
   Future<proto.CompileResponse> compile(proto.CompileRequest request) {
@@ -102,7 +243,7 @@
       throw BadRequest('Missing parameter: \'offset\'');
     }
 
-    return analysisServers.complete(request.source, request.offset);
+    return _analysisServers.complete(request.source, request.offset);
   }
 
   Future<proto.FixesResponse> fixes(proto.SourceRequest request) {
@@ -113,7 +254,7 @@
       throw BadRequest('Missing parameter: \'offset\'');
     }
 
-    return analysisServers.getFixes(request.source, request.offset);
+    return _analysisServers.getFixes(request.source, request.offset);
   }
 
   Future<proto.AssistsResponse> assists(proto.SourceRequest request) {
@@ -124,7 +265,7 @@
       throw BadRequest('Missing parameter: \'offset\'');
     }
 
-    return analysisServers.getAssists(request.source, request.offset);
+    return _analysisServers.getAssists(request.source, request.offset);
   }
 
   Future<proto.FormatResponse> format(proto.SourceRequest request) {
@@ -132,7 +273,7 @@
       throw BadRequest('Missing parameter: \'source\'');
     }
 
-    return analysisServers.format(request.source, request.offset ?? 0);
+    return _analysisServers.format(request.source, request.offset ?? 0);
   }
 
   Future<proto.DocumentResponse> document(proto.SourceRequest request) async {
@@ -145,7 +286,7 @@
 
     return proto.DocumentResponse()
       ..info.addAll(
-          await analysisServers.dartdoc(request.source, request.offset) ??
+          await _analysisServers.dartdoc(request.source, request.offset) ??
               <String, String>{});
   }
 
@@ -156,7 +297,7 @@
           ..sdkVersionFull = SdkManager.sdk.versionFull
           ..runtimeVersion = vmVersion
           ..servicesVersion = servicesVersion
-          ..appEngineVersion = container.version
+          ..appEngineVersion = _container.version
           ..flutterDartVersion = SdkManager.flutterSdk.version
           ..flutterDartVersionFull = SdkManager.flutterSdk.versionFull
           ..flutterVersion = SdkManager.flutterSdk.flutterVersion,
@@ -187,7 +328,7 @@
       final watch = Stopwatch()..start();
 
       final results =
-          await compiler.compile(source, returnSourceMap: returnSourceMap);
+          await _compiler.compile(source, returnSourceMap: returnSourceMap);
 
       if (results.hasOutput) {
         final lineCount = source.split('\n').length;
@@ -239,7 +380,7 @@
       log.info('CACHE: MISS for compileDDC');
       final watch = Stopwatch()..start();
 
-      final results = await compiler.compileDDC(source);
+      final results = await _compiler.compileDDC(source);
 
       if (results.hasOutput) {
         final lineCount = source.split('\n').length;
@@ -270,10 +411,10 @@
     }
   }
 
-  Future<String> _checkCache(String query) => cache.get(query);
+  Future<String> _checkCache(String query) => _cache.get(query);
 
   Future<void> _setCache(String query, String result) =>
-      cache.set(query, result, expiration: _standardExpiration);
+      _cache.set(query, result, expiration: _standardExpiration);
 }
 
 String _printCompileProblem(CompilationProblem problem) => problem.message;
diff --git a/pubspec.lock b/pubspec.lock
index e675627..34cbbc2 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -154,7 +154,7 @@
       name: codemirror
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.5.20+5.57.0"
+    version: "0.5.21+5.58.0"
   collection:
     dependency: transitive
     description:
@@ -259,7 +259,7 @@
       name: grpc
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.2.0"
+    version: "2.3.0"
   html:
     dependency: transitive
     description:
@@ -280,7 +280,7 @@
       name: http2
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.0"
+    version: "1.0.1"
   http_methods:
     dependency: transitive
     description:
@@ -336,7 +336,7 @@
       name: json_annotation
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "3.0.1"
+    version: "3.1.0"
   logging:
     dependency: "direct main"
     description:
@@ -497,7 +497,7 @@
       name: source_gen
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.9.6"
+    version: "0.9.7+1"
   source_map_stack_trace:
     dependency: transitive
     description:
@@ -567,7 +567,7 @@
       name: test
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.15.3"
+    version: "1.15.4"
   test_api:
     dependency: transitive
     description:
@@ -581,7 +581,7 @@
       name: test_core
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.11"
+    version: "0.3.11+1"
   timing:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index d93d726..0b250a7 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -10,7 +10,7 @@
   analyzer: 0.39.16
   analysis_server_lib: ^0.1.4
   appengine: ^0.12.0
-  args: ^1.5.2
+  args: ^1.6.0
   bazel_worker: ^0.1.23
   crypto: ^2.0.0
   dartis: ^0.5.0
diff --git a/tool/dart_run.sh b/tool/dart_run.sh
index 8453ba8..19bbd3f 100755
--- a/tool/dart_run.sh
+++ b/tool/dart_run.sh
@@ -17,4 +17,5 @@
      ${DBG_OPTION} \
      --enable-vm-service:8181/0.0.0.0 \
      ${DART_VM_OPTIONS} \
-     bin/server.dart
+     bin/server.dart \
+     $@