Index: pkg/appengine/lib/src/appengine_internal.dart |
diff --git a/pkg/appengine/lib/src/appengine_internal.dart b/pkg/appengine/lib/src/appengine_internal.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c743cdf36cdfb618e7425a10b19a311c8fc1b85d |
--- /dev/null |
+++ b/pkg/appengine/lib/src/appengine_internal.dart |
@@ -0,0 +1,183 @@ |
+// 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. |
+ |
+library appengine.internal; |
+ |
+import 'dart:async'; |
+import 'dart:io'; |
+ |
+import 'package:gcloud/service_scope.dart' as ss; |
+import 'package:gcloud/storage.dart' as storage; |
+import 'package:gcloud/db.dart' as db; |
+import 'package:gcloud/datastore.dart' as datastore; |
+import 'package:gcloud/http.dart' as gcloud_http; |
+import 'package:http/http.dart' as http; |
+import 'package:googleapis_auth/auth_io.dart' as auth; |
+ |
+import '../api/logging.dart' as logging; |
+import '../api/modules.dart' as modules; |
+import '../api/memcache.dart' as memcache; |
+import '../api/users.dart' as users; |
+ |
+import 'protobuf_api/rpc/rpc_service.dart'; |
+import 'protobuf_api/rpc/rpc_service_remote_api.dart'; |
+ |
+import 'appengine_context.dart'; |
+import 'client_context.dart'; |
+import 'server/server.dart'; |
+import 'server/context_registry.dart'; |
+ |
+// Currently only with storage scopes. |
+http.Client _authClient; |
+ContextRegistry _contextRegistry; |
+ |
+ClientContext contextFromRequest(HttpRequest request) { |
+ return _contextRegistry.lookup(request); |
+} |
+ |
+Future<ContextRegistry> initializeAppEngine() { |
+ RPCService initializeRPC() { |
+ var apiHostString = Platform.environment['API_HOST']; |
+ var apiPortString = Platform.environment['API_PORT']; |
+ |
+ if (apiHostString == null) apiHostString = 'appengine.googleapis.com'; |
+ var apiPort = apiPortString != null ? int.parse(apiPortString) : 10001; |
+ |
+ return new RPCServiceRemoteApi(apiHostString, apiPort); |
+ } |
+ |
+ AppengineContext getDockerContext() { |
+ var env = Platform.environment; |
+ |
+ String applicationID = env['GAE_LONG_APP_ID']; |
+ String module = env['GAE_MODULE_NAME']; |
+ String version = env['GAE_MODULE_VERSION']; |
+ String instance = env['GAE_MODULE_INSTANCE']; |
+ String partition = env['GAE_PARTITION']; |
+ String pubServeUrlString = env['DART_PUB_SERVE']; |
+ |
+ // TODO: [instance] is currently only passed by devappserver when starting |
+ // docker container but not by real deployment. |
+ if (applicationID == null || module == null || version == null || |
+ /*instance == null || */partition == null) { |
+ throw new StateError('Expected docker environment variables not found.'); |
+ } |
+ |
+ Uri pubServeUrl = pubServeUrlString != null |
+ ? Uri.parse(pubServeUrlString) |
+ : null; |
+ |
+ return new AppengineContext( |
+ partition, applicationID, version, module, instance, pubServeUrl); |
+ } |
+ |
+ Future<storage.Storage> getStorage(AppengineContext context) { |
+ if (context.isDevelopmentEnvironment) { |
+ // When running locally the service account path is passed through |
+ // an environment variable. |
+ var serviceAccount = Platform.environment['STORAGE_SERVICE_ACCOUNT_FILE']; |
+ if (serviceAccount != null) { |
+ return new File(serviceAccount).readAsString().then((keyJson) { |
+ var creds = new auth.ServiceAccountCredentials.fromJson(keyJson); |
+ return auth.clientViaServiceAccount(creds, storage.Storage.SCOPES) |
+ .then((client) { |
+ _authClient = client; |
+ return new storage.Storage(client, context.applicationID); |
+ }); |
+ }); |
+ } else { |
+ return new Future.value(); |
+ } |
+ } else { |
+ return auth.clientViaMetadataServer().then((client) { |
+ _authClient = client; |
+ return new storage.Storage(client, context.applicationID); |
+ }); |
+ } |
+ } |
+ |
+ if (_contextRegistry != null) { |
+ return new Future.value(_contextRegistry); |
+ } else { |
+ var context = getDockerContext(); |
+ var rpcService = initializeRPC(); |
+ |
+ return getStorage(context).then((storage) { |
+ _contextRegistry = new ContextRegistry(rpcService, storage, context); |
+ return _contextRegistry; |
+ }); |
+ } |
+} |
+ |
+void initializeContext(Services services) { |
+ db.registerDbService(services.db); |
+ datastore.registerDatastoreService(services.db.datastore); |
+ storage.registerStorageService(services.storage); |
+ logging.registerLoggingService(services.logging); |
+ modules.registerModulesService(services.modules); |
+ memcache.registerMemcacheService(services.memcache); |
+ |
+ if (_authClient != null) { |
+ gcloud_http.registerAuthClientService(_authClient); |
+ |
+ // This will automatically close the authenticated HTTP client when the |
+ // HTTP server shuts down. |
+ ss.registerScopeExitCallback(() => _authClient.close()); |
+ } |
+} |
+ |
+void initializeRequestSpecificServices(Services services) { |
+ logging.registerLoggingService(services.logging); |
+ users.registerUserService(services.users); |
+} |
+ |
+Future withAppEngineServices(Future callback()) { |
+ return initializeAppEngine().then((ContextRegistry contextRegistry) { |
+ return ss.fork(() { |
+ var backgroundServices = _contextRegistry.newBackgroundServices(); |
+ initializeContext(backgroundServices); |
+ return callback(); |
+ }); |
+ }); |
+} |
+ |
+Future runAppEngine(void handler(request, context), void onError(e, s)) { |
+ return withAppEngineServices(() { |
+ var appengineServer = new AppEngineHttpServer(_contextRegistry); |
+ appengineServer.run((request, context) { |
+ ss.fork(() { |
+ initializeRequestSpecificServices(context.services); |
+ handler(request, context); |
+ return request.response.done; |
+ }, onError: (error, stack) { |
+ var context = _contextRegistry.lookup(request); |
+ if (context != null) { |
+ try { |
+ context.services.logging.error( |
+ 'Uncaught error in request handler: $error\n$stack'); |
+ } catch (e) { |
+ print('Error while logging uncaught error: $e'); |
+ } |
+ } else { |
+ // TODO: We could log on the background ticket here. |
+ print('Unable to log error, since response has already been sent.'); |
+ } |
+ onError('Uncaught error in request handler zone: $error', stack); |
+ |
+ // In many cases errors happen during request processing or response |
+ // preparation. In such cases we want to close the connection, since |
+ // user code might not be able to. |
+ try { |
+ request.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR; |
+ } on StateError catch (_) {} |
+ request.response.close().catchError((closeError, closeErrorStack) { |
+ onError('Forcefully closing response, due to error in request ' |
+ 'handler zone, resulted in an error: $closeError', |
+ closeErrorStack); |
+ }); |
+ }); |
+ }); |
+ return appengineServer.done; |
+ }); |
+} |