129 """A stub for calling services on a remote server over HTTP. |
129 """A stub for calling services on a remote server over HTTP. |
130 |
130 |
131 You can use this to stub out any service that the remote server supports. |
131 You can use this to stub out any service that the remote server supports. |
132 """ |
132 """ |
133 |
133 |
134 def __init__(self, server, path): |
134 def __init__(self, server, path, _test_stub_map=None): |
135 """Constructs a new RemoteStub that communicates with the specified server. |
135 """Constructs a new RemoteStub that communicates with the specified server. |
136 |
136 |
137 Args: |
137 Args: |
138 server: An instance of a subclass of |
138 server: An instance of a subclass of |
139 google.appengine.tools.appengine_rpc.AbstractRpcServer. |
139 google.appengine.tools.appengine_rpc.AbstractRpcServer. |
140 path: The path to the handler this stub should send requests to. |
140 path: The path to the handler this stub should send requests to. |
141 """ |
141 """ |
142 self._server = server |
142 self._server = server |
143 self._path = path |
143 self._path = path |
|
144 self._test_stub_map = _test_stub_map |
144 |
145 |
145 def _PreHookHandler(self, service, call, request, response): |
146 def _PreHookHandler(self, service, call, request, response): |
146 pass |
147 pass |
147 |
148 |
148 def _PostHookHandler(self, service, call, request, response): |
149 def _PostHookHandler(self, service, call, request, response): |
149 pass |
150 pass |
150 |
151 |
151 def MakeSyncCall(self, service, call, request, response): |
152 def MakeSyncCall(self, service, call, request, response): |
152 self._PreHookHandler(service, call, request, response) |
153 self._PreHookHandler(service, call, request, response) |
|
154 try: |
|
155 test_stub = self._test_stub_map and self._test_stub_map.GetStub(service) |
|
156 if test_stub: |
|
157 test_stub.MakeSyncCall(service, call, request, response) |
|
158 else: |
|
159 self._MakeRealSyncCall(service, call, request, response) |
|
160 finally: |
|
161 self._PostHookHandler(service, call, request, response) |
|
162 |
|
163 def _MakeRealSyncCall(self, service, call, request, response): |
153 request_pb = remote_api_pb.Request() |
164 request_pb = remote_api_pb.Request() |
154 request_pb.set_service_name(service) |
165 request_pb.set_service_name(service) |
155 request_pb.set_method(call) |
166 request_pb.set_method(call) |
156 request_pb.mutable_request().set_contents(request.Encode()) |
167 request_pb.mutable_request().set_contents(request.Encode()) |
157 |
168 |
158 response_pb = remote_api_pb.Response() |
169 response_pb = remote_api_pb.Response() |
159 encoded_request = request_pb.Encode() |
170 encoded_request = request_pb.Encode() |
160 encoded_response = self._server.Send(self._path, encoded_request) |
171 encoded_response = self._server.Send(self._path, encoded_request) |
161 response_pb.ParseFromString(encoded_response) |
172 response_pb.ParseFromString(encoded_response) |
162 |
173 |
163 try: |
174 if response_pb.has_application_error(): |
164 if response_pb.has_application_error(): |
175 error_pb = response_pb.application_error() |
165 error_pb = response_pb.application_error() |
176 raise apiproxy_errors.ApplicationError(error_pb.code(), |
166 raise datastore._ToDatastoreError( |
177 error_pb.detail()) |
167 apiproxy_errors.ApplicationError(error_pb.code(), error_pb.detail())) |
178 elif response_pb.has_exception(): |
168 elif response_pb.has_exception(): |
179 raise pickle.loads(response_pb.exception().contents()) |
169 raise pickle.loads(response_pb.exception().contents()) |
180 elif response_pb.has_java_exception(): |
170 elif response_pb.has_java_exception(): |
181 raise UnknownJavaServerError("An unknown error has occured in the " |
171 raise UnknownJavaServerError("An unknown error has occured in the " |
182 "Java remote_api handler for this call.") |
172 "Java remote_api handler for this call.") |
183 else: |
173 else: |
184 response.ParseFromString(response_pb.response().contents()) |
174 response.ParseFromString(response_pb.response().contents()) |
|
175 finally: |
|
176 self._PostHookHandler(service, call, request, response) |
|
177 |
185 |
178 def CreateRPC(self): |
186 def CreateRPC(self): |
179 return apiproxy_rpc.RPC(stub=self) |
187 return apiproxy_rpc.RPC(stub=self) |
180 |
188 |
181 |
189 |
185 A specialised stub is required because there are some datastore operations |
193 A specialised stub is required because there are some datastore operations |
186 that preserve state between calls. This stub makes queries possible. |
194 that preserve state between calls. This stub makes queries possible. |
187 Transactions on the remote datastore are unfortunately still impossible. |
195 Transactions on the remote datastore are unfortunately still impossible. |
188 """ |
196 """ |
189 |
197 |
190 def __init__(self, server, path): |
198 def __init__(self, server, path, default_result_count=20, |
191 super(RemoteDatastoreStub, self).__init__(server, path) |
199 _test_stub_map=None): |
|
200 """Constructor. |
|
201 |
|
202 Args: |
|
203 server: The server name to connect to. |
|
204 path: The URI path on the server. |
|
205 default_result_count: The number of items to fetch, by default, in a |
|
206 datastore Query or Next operation. This affects the batch size of |
|
207 query iterators. |
|
208 """ |
|
209 super(RemoteDatastoreStub, self).__init__(server, path, _test_stub_map) |
|
210 self.default_result_count = default_result_count |
192 self.__queries = {} |
211 self.__queries = {} |
193 self.__transactions = {} |
212 self.__transactions = {} |
194 |
213 |
195 self.__next_local_cursor = 1 |
214 self.__next_local_cursor = 1 |
196 self.__local_cursor_lock = threading.Lock() |
215 self.__local_cursor_lock = threading.Lock() |
235 |
254 |
236 if query is None: |
255 if query is None: |
237 query_result.set_more_results(False) |
256 query_result.set_more_results(False) |
238 return |
257 return |
239 |
258 |
|
259 if next_request.has_count(): |
|
260 result_count = next_request.count() |
|
261 else: |
|
262 result_count = self.default_result_count |
|
263 |
240 request = datastore_pb.Query() |
264 request = datastore_pb.Query() |
241 request.CopyFrom(query) |
265 request.CopyFrom(query) |
242 if request.has_limit(): |
266 request.set_count(result_count) |
243 request.set_limit(min(request.limit(), next_request.count())) |
|
244 else: |
|
245 request.set_limit(next_request.count()) |
|
246 request.set_count(request.limit()) |
|
247 |
267 |
248 super(RemoteDatastoreStub, self).MakeSyncCall( |
268 super(RemoteDatastoreStub, self).MakeSyncCall( |
249 'remote_datastore', 'RunQuery', request, query_result) |
269 'remote_datastore', 'RunQuery', request, query_result) |
250 |
270 |
251 query.set_offset(query.offset() + query_result.result_size()) |
271 query.set_offset(query.offset() + query_result.result_size()) |
414 def _Dynamic_DeleteIndex(self, index, void): |
434 def _Dynamic_DeleteIndex(self, index, void): |
415 raise apiproxy_errors.CapabilityDisabledError( |
435 raise apiproxy_errors.CapabilityDisabledError( |
416 'The remote datastore does not support index manipulation.') |
436 'The remote datastore does not support index manipulation.') |
417 |
437 |
418 |
438 |
|
439 ALL_SERVICES = set([ |
|
440 'capability_service', |
|
441 'datastore_v3', |
|
442 'images', |
|
443 'mail', |
|
444 'memcache', |
|
445 'taskqueue', |
|
446 'urlfetch', |
|
447 'xmpp', |
|
448 ]) |
|
449 |
|
450 |
419 def ConfigureRemoteApi(app_id, |
451 def ConfigureRemoteApi(app_id, |
420 path, |
452 path, |
421 auth_func, |
453 auth_func, |
422 servername=None, |
454 servername=None, |
423 rpc_server_factory=appengine_rpc.HttpRpcServer, |
455 rpc_server_factory=appengine_rpc.HttpRpcServer, |
424 rtok=None, |
456 rtok=None, |
425 secure=False): |
457 secure=False, |
|
458 services=None, |
|
459 default_auth_domain=None): |
426 """Does necessary setup to allow easy remote access to App Engine APIs. |
460 """Does necessary setup to allow easy remote access to App Engine APIs. |
427 |
461 |
428 Either servername must be provided or app_id must not be None. If app_id |
462 Either servername must be provided or app_id must not be None. If app_id |
429 is None and a servername is provided, this function will send a request |
463 is None and a servername is provided, this function will send a request |
430 to the server to retrieve the app_id. |
464 to the server to retrieve the app_id. |
471 raise ConfigurationError('Token validation failed during app_id lookup. ' |
508 raise ConfigurationError('Token validation failed during app_id lookup. ' |
472 '(sent %s, got %s)' % (repr(rtok), |
509 '(sent %s, got %s)' % (repr(rtok), |
473 repr(app_info['rtok']))) |
510 repr(app_info['rtok']))) |
474 app_id = app_info['app_id'] |
511 app_id = app_info['app_id'] |
475 |
512 |
|
513 if services is not None: |
|
514 services = set(services) |
|
515 unsupported = services.difference(ALL_SERVICES) |
|
516 if unsupported: |
|
517 raise ConfigurationError('Unsupported service(s): %s' |
|
518 % (', '.join(unsupported),)) |
|
519 else: |
|
520 services = set(ALL_SERVICES) |
|
521 |
476 os.environ['APPLICATION_ID'] = app_id |
522 os.environ['APPLICATION_ID'] = app_id |
|
523 if default_auth_domain: |
|
524 os.environ['AUTH_DOMAIN'] = default_auth_domain |
|
525 elif 'AUTH_DOMAIN' not in os.environ: |
|
526 os.environ['AUTH_DOMAIN'] = 'gmail.com' |
477 apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() |
527 apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() |
478 datastore_stub = RemoteDatastoreStub(server, path) |
528 if 'datastore_v3' in services: |
479 apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', datastore_stub) |
529 services.remove('datastore_v3') |
|
530 datastore_stub = RemoteDatastoreStub(server, path) |
|
531 apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', datastore_stub) |
480 stub = RemoteStub(server, path) |
532 stub = RemoteStub(server, path) |
481 for service in ['capability_service', 'images', 'mail', 'memcache', |
533 for service in services: |
482 'urlfetch']: |
|
483 apiproxy_stub_map.apiproxy.RegisterStub(service, stub) |
534 apiproxy_stub_map.apiproxy.RegisterStub(service, stub) |
484 |
535 |
485 |
536 |
486 def MaybeInvokeAuthentication(): |
537 def MaybeInvokeAuthentication(): |
487 """Sends an empty request through to the configured end-point. |
538 """Sends an empty request through to the configured end-point. |