OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library appengine.internal; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:io'; |
| 9 |
| 10 import 'package:gcloud/service_scope.dart' as ss; |
| 11 import 'package:gcloud/storage.dart' as storage; |
| 12 import 'package:gcloud/db.dart' as db; |
| 13 import 'package:gcloud/datastore.dart' as datastore; |
| 14 import 'package:gcloud/http.dart' as gcloud_http; |
| 15 import 'package:http/http.dart' as http; |
| 16 import 'package:googleapis_auth/auth_io.dart' as auth; |
| 17 |
| 18 import '../api/logging.dart' as logging; |
| 19 import '../api/modules.dart' as modules; |
| 20 import '../api/memcache.dart' as memcache; |
| 21 import '../api/users.dart' as users; |
| 22 |
| 23 import 'protobuf_api/rpc/rpc_service.dart'; |
| 24 import 'protobuf_api/rpc/rpc_service_remote_api.dart'; |
| 25 |
| 26 import 'appengine_context.dart'; |
| 27 import 'client_context.dart'; |
| 28 import 'server/server.dart'; |
| 29 import 'server/context_registry.dart'; |
| 30 |
| 31 // Currently only with storage scopes. |
| 32 http.Client _authClient; |
| 33 ContextRegistry _contextRegistry; |
| 34 |
| 35 ClientContext contextFromRequest(HttpRequest request) { |
| 36 return _contextRegistry.lookup(request); |
| 37 } |
| 38 |
| 39 Future<ContextRegistry> initializeAppEngine() { |
| 40 RPCService initializeRPC() { |
| 41 var apiHostString = Platform.environment['API_HOST']; |
| 42 var apiPortString = Platform.environment['API_PORT']; |
| 43 |
| 44 if (apiHostString == null) apiHostString = 'appengine.googleapis.com'; |
| 45 var apiPort = apiPortString != null ? int.parse(apiPortString) : 10001; |
| 46 |
| 47 return new RPCServiceRemoteApi(apiHostString, apiPort); |
| 48 } |
| 49 |
| 50 AppengineContext getDockerContext() { |
| 51 var env = Platform.environment; |
| 52 |
| 53 String applicationID = env['GAE_LONG_APP_ID']; |
| 54 String module = env['GAE_MODULE_NAME']; |
| 55 String version = env['GAE_MODULE_VERSION']; |
| 56 String instance = env['GAE_MODULE_INSTANCE']; |
| 57 String partition = env['GAE_PARTITION']; |
| 58 String pubServeUrlString = env['DART_PUB_SERVE']; |
| 59 |
| 60 // TODO: [instance] is currently only passed by devappserver when starting |
| 61 // docker container but not by real deployment. |
| 62 if (applicationID == null || module == null || version == null || |
| 63 /*instance == null || */partition == null) { |
| 64 throw new StateError('Expected docker environment variables not found.'); |
| 65 } |
| 66 |
| 67 Uri pubServeUrl = pubServeUrlString != null |
| 68 ? Uri.parse(pubServeUrlString) |
| 69 : null; |
| 70 |
| 71 return new AppengineContext( |
| 72 partition, applicationID, version, module, instance, pubServeUrl); |
| 73 } |
| 74 |
| 75 Future<storage.Storage> getStorage(AppengineContext context) { |
| 76 if (context.isDevelopmentEnvironment) { |
| 77 // When running locally the service account path is passed through |
| 78 // an environment variable. |
| 79 var serviceAccount = Platform.environment['STORAGE_SERVICE_ACCOUNT_FILE']; |
| 80 if (serviceAccount != null) { |
| 81 return new File(serviceAccount).readAsString().then((keyJson) { |
| 82 var creds = new auth.ServiceAccountCredentials.fromJson(keyJson); |
| 83 return auth.clientViaServiceAccount(creds, storage.Storage.SCOPES) |
| 84 .then((client) { |
| 85 _authClient = client; |
| 86 return new storage.Storage(client, context.applicationID); |
| 87 }); |
| 88 }); |
| 89 } else { |
| 90 return new Future.value(); |
| 91 } |
| 92 } else { |
| 93 return auth.clientViaMetadataServer().then((client) { |
| 94 _authClient = client; |
| 95 return new storage.Storage(client, context.applicationID); |
| 96 }); |
| 97 } |
| 98 } |
| 99 |
| 100 if (_contextRegistry != null) { |
| 101 return new Future.value(_contextRegistry); |
| 102 } else { |
| 103 var context = getDockerContext(); |
| 104 var rpcService = initializeRPC(); |
| 105 |
| 106 return getStorage(context).then((storage) { |
| 107 _contextRegistry = new ContextRegistry(rpcService, storage, context); |
| 108 return _contextRegistry; |
| 109 }); |
| 110 } |
| 111 } |
| 112 |
| 113 void initializeContext(Services services) { |
| 114 db.registerDbService(services.db); |
| 115 datastore.registerDatastoreService(services.db.datastore); |
| 116 storage.registerStorageService(services.storage); |
| 117 logging.registerLoggingService(services.logging); |
| 118 modules.registerModulesService(services.modules); |
| 119 memcache.registerMemcacheService(services.memcache); |
| 120 |
| 121 if (_authClient != null) { |
| 122 gcloud_http.registerAuthClientService(_authClient); |
| 123 |
| 124 // This will automatically close the authenticated HTTP client when the |
| 125 // HTTP server shuts down. |
| 126 ss.registerScopeExitCallback(() => _authClient.close()); |
| 127 } |
| 128 } |
| 129 |
| 130 void initializeRequestSpecificServices(Services services) { |
| 131 logging.registerLoggingService(services.logging); |
| 132 users.registerUserService(services.users); |
| 133 } |
| 134 |
| 135 Future withAppEngineServices(Future callback()) { |
| 136 return initializeAppEngine().then((ContextRegistry contextRegistry) { |
| 137 return ss.fork(() { |
| 138 var backgroundServices = _contextRegistry.newBackgroundServices(); |
| 139 initializeContext(backgroundServices); |
| 140 return callback(); |
| 141 }); |
| 142 }); |
| 143 } |
| 144 |
| 145 Future runAppEngine(void handler(request, context), void onError(e, s)) { |
| 146 return withAppEngineServices(() { |
| 147 var appengineServer = new AppEngineHttpServer(_contextRegistry); |
| 148 appengineServer.run((request, context) { |
| 149 ss.fork(() { |
| 150 initializeRequestSpecificServices(context.services); |
| 151 handler(request, context); |
| 152 return request.response.done; |
| 153 }, onError: (error, stack) { |
| 154 var context = _contextRegistry.lookup(request); |
| 155 if (context != null) { |
| 156 try { |
| 157 context.services.logging.error( |
| 158 'Uncaught error in request handler: $error\n$stack'); |
| 159 } catch (e) { |
| 160 print('Error while logging uncaught error: $e'); |
| 161 } |
| 162 } else { |
| 163 // TODO: We could log on the background ticket here. |
| 164 print('Unable to log error, since response has already been sent.'); |
| 165 } |
| 166 onError('Uncaught error in request handler zone: $error', stack); |
| 167 |
| 168 // In many cases errors happen during request processing or response |
| 169 // preparation. In such cases we want to close the connection, since |
| 170 // user code might not be able to. |
| 171 try { |
| 172 request.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR; |
| 173 } on StateError catch (_) {} |
| 174 request.response.close().catchError((closeError, closeErrorStack) { |
| 175 onError('Forcefully closing response, due to error in request ' |
| 176 'handler zone, resulted in an error: $closeError', |
| 177 closeErrorStack); |
| 178 }); |
| 179 }); |
| 180 }); |
| 181 return appengineServer.done; |
| 182 }); |
| 183 } |
OLD | NEW |