Enable Redis in Cloud Run (#537)

diff --git a/bin/server_cloud_run.dart b/bin/server_cloud_run.dart
new file mode 100644
index 0000000..912beb5
--- /dev/null
+++ b/bin/server_cloud_run.dart
@@ -0,0 +1,18 @@
+// 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; see `bin/server.dart` for the GAE server.
+library services.bin;
+
+import 'dart:async';
+
+import 'package:dart_services/services_cloud_run.dart' as services_cloud_run;
+import 'package:dart_services/src/sdk_manager.dart';
+
+Future<void> main(List<String> args) async {
+  await SdkManager.sdk.init();
+  await SdkManager.flutterSdk.init();
+
+  services_cloud_run.main(args);
+}
diff --git a/cloud_run.Dockerfile b/cloud_run.Dockerfile
index 7c15cf0..7a19d48 100644
--- a/cloud_run.Dockerfile
+++ b/cloud_run.Dockerfile
@@ -49,4 +49,6 @@
 # the Dart app using custom script enabling debug modes.
 CMD []
 
-ENTRYPOINT ["/dart_runtime/dart_cloud_run.sh", "--port", "${PORT}", "--server-url", "http://0.0.0.0"]
+ENTRYPOINT ["/dart_runtime/dart_cloud_run.sh", "--port", "${PORT}", \
+            "--server-url", "http://0.0.0.0", \
+            "--services_cloud_run", "redis://10.0.0.4:6379"]
diff --git a/lib/services_cloud_run.dart b/lib/services_cloud_run.dart
new file mode 100644
index 0000000..5d4ee42
--- /dev/null
+++ b/lib/services_cloud_run.dart
@@ -0,0 +1,115 @@
+// 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 server for Cloud Run.
+library services_cloud_run;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:logging/logging.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart' as shelf;
+
+import 'src/common.dart';
+import 'src/common_server_api.dart';
+import 'src/common_server_impl.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.addOption('server-url', defaultsTo: 'http://localhost');
+  parser.addOption('redis-url');
+
+  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 redisServerUri = result['redis-url'] as String;
+  final sdk = sdkPath;
+
+  Logger.root.level = Level.FINER;
+  Logger.root.onRecord.listen((LogRecord record) {
+    print(record);
+    if (record.stackTrace != null) print(record.stackTrace);
+  });
+
+  EndpointsServer.serve(sdk, port, redisServerUri)
+      .then((EndpointsServer server) {
+    _logger.info('Listening on port ${server.port}');
+  });
+}
+
+class EndpointsServer {
+  static Future<EndpointsServer> serve(
+      String sdkPath, int port, String redisServerUri) {
+    final endpointsServer = EndpointsServer._(sdkPath, port, redisServerUri);
+
+    return shelf
+        .serve(endpointsServer.handler, InternetAddress.anyIPv4, port)
+        .then((HttpServer server) {
+      endpointsServer.server = server;
+      return endpointsServer;
+    });
+  }
+
+  final int port;
+  HttpServer server;
+  String redisServerUri;
+
+  Pipeline pipeline;
+  Handler handler;
+
+  CommonServerApi commonServerApi;
+  FlutterWebManager flutterWebManager;
+
+  EndpointsServer._(String sdkPath, this.port, this.redisServerUri) {
+    flutterWebManager = FlutterWebManager(SdkManager.flutterSdk);
+    final commonServerImpl = CommonServerImpl(
+      sdkPath,
+      flutterWebManager,
+      _ServerContainer(),
+      redisServerUri == null
+          ? InMemoryCache()
+          : RedisCache(
+              redisServerUri,
+              // The name of the Cloud Run revision being run, for more detail please see:
+              // https://cloud.google.com/run/docs/reference/container-contract#env-vars
+              Platform.environment['K_REVISION'],
+            ),
+    );
+    commonServerApi = CommonServerApi(commonServerImpl);
+    commonServerImpl.init();
+
+    pipeline = const Pipeline()
+        .addMiddleware(logRequests())
+        .addMiddleware(_createCustomCorsHeadersMiddleware());
+
+    handler = pipeline.addHandler(commonServerApi.router.handler);
+  }
+
+  Middleware _createCustomCorsHeadersMiddleware() {
+    return shelf_cors.createCorsHeadersMiddleware(corsHeaders: <String, String>{
+      'Access-Control-Allow-Origin': '*',
+      'Access-Control-Allow-Methods': 'GET, 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';
+}
diff --git a/lib/services_dev.dart b/lib/services_dev.dart
index a36d272..1706490 100644
--- a/lib/services_dev.dart
+++ b/lib/services_dev.dart
@@ -26,8 +26,6 @@
 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);
@@ -90,17 +88,6 @@
     handler = pipeline.addHandler(commonServerApi.router.handler);
   }
 
-  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': '*',