Index: tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/webapp/service_handlers.py |
diff --git a/tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/webapp/service_handlers.py b/tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/webapp/service_handlers.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..a574b2b4e08016c317e3b62168edcc9347264222 |
--- /dev/null |
+++ b/tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/webapp/service_handlers.py |
@@ -0,0 +1,834 @@ |
+#!/usr/bin/env python |
+# |
+# Copyright 2010 Google Inc. |
+# |
+# Licensed under the Apache License, Version 2.0 (the "License"); |
+# you may not use this file except in compliance with the License. |
+# You may obtain a copy of the License at |
+# |
+# http://www.apache.org/licenses/LICENSE-2.0 |
+# |
+# Unless required by applicable law or agreed to in writing, software |
+# distributed under the License is distributed on an "AS IS" BASIS, |
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+# See the License for the specific language governing permissions and |
+# limitations under the License. |
+# |
+ |
+"""Handlers for remote services. |
+ |
+This module contains classes that may be used to build a service |
+on top of the App Engine Webapp framework. |
+ |
+The services request handler can be configured to handle requests in a number |
+of different request formats. All different request formats must have a way |
+to map the request to the service handlers defined request message.Message |
+class. The handler can also send a response in any format that can be mapped |
+from the response message.Message class. |
+ |
+Participants in an RPC: |
+ |
+ There are four classes involved with the life cycle of an RPC. |
+ |
+ Service factory: A user-defined service factory that is responsible for |
+ instantiating an RPC service. The methods intended for use as RPC |
+ methods must be decorated by the 'remote' decorator. |
+ |
+ RPCMapper: Responsible for determining whether or not a specific request |
+ matches a particular RPC format and translating between the actual |
+ request/response and the underlying message types. A single instance of |
+ an RPCMapper sub-class is required per service configuration. Each |
+ mapper must be usable across multiple requests. |
+ |
+ ServiceHandler: A webapp.RequestHandler sub-class that responds to the |
+ webapp framework. It mediates between the RPCMapper and service |
+ implementation class during a request. As determined by the Webapp |
+ framework, a new ServiceHandler instance is created to handle each |
+ user request. A handler is never used to handle more than one request. |
+ |
+ ServiceHandlerFactory: A class that is responsible for creating new, |
+ properly configured ServiceHandler instance for each request. The |
+ factory is configured by providing it with a set of RPCMapper instances. |
+ When the Webapp framework invokes the service handler, the handler |
+ creates a new service class instance. The service class instance is |
+ provided with a reference to the handler. A single instance of an |
+ RPCMapper sub-class is required to configure each service. Each mapper |
+ instance must be usable across multiple requests. |
+ |
+RPC mappers: |
+ |
+ RPC mappers translate between a single HTTP based RPC protocol and the |
+ underlying service implementation. Each RPC mapper must configured |
+ with the following information to determine if it is an appropriate |
+ mapper for a given request: |
+ |
+ http_methods: Set of HTTP methods supported by handler. |
+ |
+ content_types: Set of supported content types. |
+ |
+ default_content_type: Default content type for handler responses. |
+ |
+ Built-in mapper implementations: |
+ |
+ URLEncodedRPCMapper: Matches requests that are compatible with post |
+ forms with the 'application/x-www-form-urlencoded' content-type |
+ (this content type is the default if none is specified. It |
+ translates post parameters into request parameters. |
+ |
+ ProtobufRPCMapper: Matches requests that are compatible with post |
+ forms with the 'application/x-google-protobuf' content-type. It |
+ reads the contents of a binary post request. |
+ |
+Public Exceptions: |
+ Error: Base class for service handler errors. |
+ ServiceConfigurationError: Raised when a service not correctly configured. |
+ RequestError: Raised by RPC mappers when there is an error in its request |
+ or request format. |
+ ResponseError: Raised by RPC mappers when there is an error in its response. |
+""" |
+import six |
+ |
+__author__ = 'rafek@google.com (Rafe Kaplan)' |
+ |
+import six.moves.http_client |
+import logging |
+ |
+from .google_imports import webapp |
+from .google_imports import webapp_util |
+from .. import messages |
+from .. import protobuf |
+from .. import protojson |
+from .. import protourlencode |
+from .. import registry |
+from .. import remote |
+from .. import util |
+from . import forms |
+ |
+__all__ = [ |
+ 'Error', |
+ 'RequestError', |
+ 'ResponseError', |
+ 'ServiceConfigurationError', |
+ |
+ 'DEFAULT_REGISTRY_PATH', |
+ |
+ 'ProtobufRPCMapper', |
+ 'RPCMapper', |
+ 'ServiceHandler', |
+ 'ServiceHandlerFactory', |
+ 'URLEncodedRPCMapper', |
+ 'JSONRPCMapper', |
+ 'service_mapping', |
+ 'run_services', |
+] |
+ |
+ |
+class Error(Exception): |
+ """Base class for all errors in service handlers module.""" |
+ |
+ |
+class ServiceConfigurationError(Error): |
+ """When service configuration is incorrect.""" |
+ |
+ |
+class RequestError(Error): |
+ """Error occurred when building request.""" |
+ |
+ |
+class ResponseError(Error): |
+ """Error occurred when building response.""" |
+ |
+ |
+_URLENCODED_CONTENT_TYPE = protourlencode.CONTENT_TYPE |
+_PROTOBUF_CONTENT_TYPE = protobuf.CONTENT_TYPE |
+_JSON_CONTENT_TYPE = protojson.CONTENT_TYPE |
+ |
+_EXTRA_JSON_CONTENT_TYPES = ['application/x-javascript', |
+ 'text/javascript', |
+ 'text/x-javascript', |
+ 'text/x-json', |
+ 'text/json', |
+ ] |
+ |
+# The whole method pattern is an optional regex. It contains a single |
+# group used for mapping to the query parameter. This is passed to the |
+# parameters of 'get' and 'post' on the ServiceHandler. |
+_METHOD_PATTERN = r'(?:\.([^?]*))?' |
+ |
+DEFAULT_REGISTRY_PATH = forms.DEFAULT_REGISTRY_PATH |
+ |
+ |
+class RPCMapper(object): |
+ """Interface to mediate between request and service object. |
+ |
+ Request mappers are implemented to support various types of |
+ RPC protocols. It is responsible for identifying whether a |
+ given request matches a particular protocol, resolve the remote |
+ method to invoke and mediate between the request and appropriate |
+ protocol messages for the remote method. |
+ """ |
+ |
+ @util.positional(4) |
+ def __init__(self, |
+ http_methods, |
+ default_content_type, |
+ protocol, |
+ content_types=None): |
+ """Constructor. |
+ |
+ Args: |
+ http_methods: Set of HTTP methods supported by mapper. |
+ default_content_type: Default content type supported by mapper. |
+ protocol: The protocol implementation. Must implement encode_message and |
+ decode_message. |
+ content_types: Set of additionally supported content types. |
+ """ |
+ self.__http_methods = frozenset(http_methods) |
+ self.__default_content_type = default_content_type |
+ self.__protocol = protocol |
+ |
+ if content_types is None: |
+ content_types = [] |
+ self.__content_types = frozenset([self.__default_content_type] + |
+ content_types) |
+ |
+ @property |
+ def http_methods(self): |
+ return self.__http_methods |
+ |
+ @property |
+ def default_content_type(self): |
+ return self.__default_content_type |
+ |
+ @property |
+ def content_types(self): |
+ return self.__content_types |
+ |
+ def build_request(self, handler, request_type): |
+ """Build request message based on request. |
+ |
+ Each request mapper implementation is responsible for converting a |
+ request to an appropriate message instance. |
+ |
+ Args: |
+ handler: RequestHandler instance that is servicing request. |
+ Must be initialized with request object and been previously determined |
+ to matching the protocol of the RPCMapper. |
+ request_type: Message type to build. |
+ |
+ Returns: |
+ Instance of request_type populated by protocol buffer in request body. |
+ |
+ Raises: |
+ RequestError if the mapper implementation is not able to correctly |
+ convert the request to the appropriate message. |
+ """ |
+ try: |
+ return self.__protocol.decode_message(request_type, handler.request.body) |
+ except (messages.ValidationError, messages.DecodeError) as err: |
+ raise RequestError('Unable to parse request content: %s' % err) |
+ |
+ def build_response(self, handler, response, pad_string=False): |
+ """Build response based on service object response message. |
+ |
+ Each request mapper implementation is responsible for converting a |
+ response message to an appropriate handler response. |
+ |
+ Args: |
+ handler: RequestHandler instance that is servicing request. |
+ Must be initialized with request object and been previously determined |
+ to matching the protocol of the RPCMapper. |
+ response: Response message as returned from the service object. |
+ |
+ Raises: |
+ ResponseError if the mapper implementation is not able to correctly |
+ convert the message to an appropriate response. |
+ """ |
+ try: |
+ encoded_message = self.__protocol.encode_message(response) |
+ except messages.ValidationError as err: |
+ raise ResponseError('Unable to encode message: %s' % err) |
+ else: |
+ handler.response.headers['Content-Type'] = self.default_content_type |
+ handler.response.out.write(encoded_message) |
+ |
+ |
+class ServiceHandlerFactory(object): |
+ """Factory class used for instantiating new service handlers. |
+ |
+ Normally a handler class is passed directly to the webapp framework |
+ so that it can be simply instantiated to handle a single request. |
+ The service handler, however, must be configured with additional |
+ information so that it knows how to instantiate a service object. |
+ This class acts the same as a normal RequestHandler class by |
+ overriding the __call__ method to correctly configures a ServiceHandler |
+ instance with a new service object. |
+ |
+ The factory must also provide a set of RPCMapper instances which |
+ examine a request to determine what protocol is being used and mediates |
+ between the request and the service object. |
+ |
+ The mapping of a service handler must have a single group indicating the |
+ part of the URL path that maps to the request method. This group must |
+ exist but can be optional for the request (the group may be followed by |
+ '?' in the regular expression matching the request). |
+ |
+ Usage: |
+ |
+ stock_factory = ServiceHandlerFactory(StockService) |
+ ... configure stock_factory by adding RPCMapper instances ... |
+ |
+ application = webapp.WSGIApplication( |
+ [stock_factory.mapping('/stocks')]) |
+ |
+ Default usage: |
+ |
+ application = webapp.WSGIApplication( |
+ [ServiceHandlerFactory.default(StockService).mapping('/stocks')]) |
+ """ |
+ |
+ def __init__(self, service_factory): |
+ """Constructor. |
+ |
+ Args: |
+ service_factory: Service factory to instantiate and provide to |
+ service handler. |
+ """ |
+ self.__service_factory = service_factory |
+ self.__request_mappers = [] |
+ |
+ def all_request_mappers(self): |
+ """Get all request mappers. |
+ |
+ Returns: |
+ Iterator of all request mappers used by this service factory. |
+ """ |
+ return iter(self.__request_mappers) |
+ |
+ def add_request_mapper(self, mapper): |
+ """Add request mapper to end of request mapper list.""" |
+ self.__request_mappers.append(mapper) |
+ |
+ def __call__(self): |
+ """Construct a new service handler instance.""" |
+ return ServiceHandler(self, self.__service_factory()) |
+ |
+ @property |
+ def service_factory(self): |
+ """Service factory associated with this factory.""" |
+ return self.__service_factory |
+ |
+ @staticmethod |
+ def __check_path(path): |
+ """Check a path parameter. |
+ |
+ Make sure a provided path parameter is compatible with the |
+ webapp URL mapping. |
+ |
+ Args: |
+ path: Path to check. This is a plain path, not a regular expression. |
+ |
+ Raises: |
+ ValueError if path does not start with /, path ends with /. |
+ """ |
+ if path.endswith('/'): |
+ raise ValueError('Path %s must not end with /.' % path) |
+ |
+ def mapping(self, path): |
+ """Convenience method to map service to application. |
+ |
+ Args: |
+ path: Path to map service to. It must be a simple path |
+ with a leading / and no trailing /. |
+ |
+ Returns: |
+ Mapping from service URL to service handler factory. |
+ """ |
+ self.__check_path(path) |
+ |
+ service_url_pattern = r'(%s)%s' % (path, _METHOD_PATTERN) |
+ |
+ return service_url_pattern, self |
+ |
+ @classmethod |
+ def default(cls, service_factory, parameter_prefix=''): |
+ """Convenience method to map default factory configuration to application. |
+ |
+ Creates a standardized default service factory configuration that pre-maps |
+ the URL encoded protocol handler to the factory. |
+ |
+ Args: |
+ service_factory: Service factory to instantiate and provide to |
+ service handler. |
+ method_parameter: The name of the form parameter used to determine the |
+ method to invoke used by the URLEncodedRPCMapper. If None, no |
+ parameter is used and the mapper will only match against the form |
+ path-name. Defaults to 'method'. |
+ parameter_prefix: If provided, all the parameters in the form are |
+ expected to begin with that prefix by the URLEncodedRPCMapper. |
+ |
+ Returns: |
+ Mapping from service URL to service handler factory. |
+ """ |
+ factory = cls(service_factory) |
+ |
+ factory.add_request_mapper(ProtobufRPCMapper()) |
+ factory.add_request_mapper(JSONRPCMapper()) |
+ |
+ return factory |
+ |
+ |
+class ServiceHandler(webapp.RequestHandler): |
+ """Web handler for RPC service. |
+ |
+ Overridden methods: |
+ get: All requests handled by 'handle' method. HTTP method stored in |
+ attribute. Takes remote_method parameter as derived from the URL mapping. |
+ post: All requests handled by 'handle' method. HTTP method stored in |
+ attribute. Takes remote_method parameter as derived from the URL mapping. |
+ redirect: Not implemented for this service handler. |
+ |
+ New methods: |
+ handle: Handle request for both GET and POST. |
+ |
+ Attributes (in addition to attributes in RequestHandler): |
+ service: Service instance associated with request being handled. |
+ method: Method of request. Used by RPCMapper to determine match. |
+ remote_method: Sub-path as provided to the 'get' and 'post' methods. |
+ """ |
+ |
+ def __init__(self, factory, service): |
+ """Constructor. |
+ |
+ Args: |
+ factory: Instance of ServiceFactory used for constructing new service |
+ instances used for handling requests. |
+ service: Service instance used for handling RPC. |
+ """ |
+ self.__factory = factory |
+ self.__service = service |
+ |
+ @property |
+ def service(self): |
+ return self.__service |
+ |
+ def __show_info(self, service_path, remote_method): |
+ self.response.headers['content-type'] = 'text/plain; charset=utf-8' |
+ response_message = [] |
+ if remote_method: |
+ response_message.append('%s.%s is a ProtoRPC method.\n\n' %( |
+ service_path, remote_method)) |
+ else: |
+ response_message.append('%s is a ProtoRPC service.\n\n' % service_path) |
+ definition_name_function = getattr(self.__service, 'definition_name', None) |
+ if definition_name_function: |
+ definition_name = definition_name_function() |
+ else: |
+ definition_name = '%s.%s' % (self.__service.__module__, |
+ self.__service.__class__.__name__) |
+ |
+ response_message.append('Service %s\n\n' % definition_name) |
+ response_message.append('More about ProtoRPC: ') |
+ |
+ response_message.append('http://code.google.com/p/google-protorpc\n') |
+ self.response.out.write(util.pad_string(''.join(response_message))) |
+ |
+ def get(self, service_path, remote_method): |
+ """Handler method for GET requests. |
+ |
+ Args: |
+ service_path: Service path derived from request URL. |
+ remote_method: Sub-path after service path has been matched. |
+ """ |
+ self.handle('GET', service_path, remote_method) |
+ |
+ def post(self, service_path, remote_method): |
+ """Handler method for POST requests. |
+ |
+ Args: |
+ service_path: Service path derived from request URL. |
+ remote_method: Sub-path after service path has been matched. |
+ """ |
+ self.handle('POST', service_path, remote_method) |
+ |
+ def redirect(self, uri, permanent=False): |
+ """Not supported for services.""" |
+ raise NotImplementedError('Services do not currently support redirection.') |
+ |
+ def __send_error(self, |
+ http_code, |
+ status_state, |
+ error_message, |
+ mapper, |
+ error_name=None): |
+ status = remote.RpcStatus(state=status_state, |
+ error_message=error_message, |
+ error_name=error_name) |
+ mapper.build_response(self, status) |
+ self.response.headers['content-type'] = mapper.default_content_type |
+ |
+ logging.error(error_message) |
+ response_content = self.response.out.getvalue() |
+ padding = ' ' * max(0, 512 - len(response_content)) |
+ self.response.out.write(padding) |
+ |
+ self.response.set_status(http_code, error_message) |
+ |
+ def __send_simple_error(self, code, message, pad=True): |
+ """Send error to caller without embedded message.""" |
+ self.response.headers['content-type'] = 'text/plain; charset=utf-8' |
+ logging.error(message) |
+ self.response.set_status(code, message) |
+ |
+ response_message = six.moves.http_client.responses.get(code, 'Unknown Error') |
+ if pad: |
+ response_message = util.pad_string(response_message) |
+ self.response.out.write(response_message) |
+ |
+ def __get_content_type(self): |
+ content_type = self.request.headers.get('content-type', None) |
+ if not content_type: |
+ content_type = self.request.environ.get('HTTP_CONTENT_TYPE', None) |
+ if not content_type: |
+ return None |
+ |
+ # Lop off parameters from the end (for example content-encoding) |
+ return content_type.split(';', 1)[0].lower() |
+ |
+ def __headers(self, content_type): |
+ for name in self.request.headers: |
+ name = name.lower() |
+ if name == 'content-type': |
+ value = content_type |
+ elif name == 'content-length': |
+ value = str(len(self.request.body)) |
+ else: |
+ value = self.request.headers.get(name, '') |
+ yield name, value |
+ |
+ def handle(self, http_method, service_path, remote_method): |
+ """Handle a service request. |
+ |
+ The handle method will handle either a GET or POST response. |
+ It is up to the individual mappers from the handler factory to determine |
+ which request methods they can service. |
+ |
+ If the protocol is not recognized, the request does not provide a correct |
+ request for that protocol or the service object does not support the |
+ requested RPC method, will return error code 400 in the response. |
+ |
+ Args: |
+ http_method: HTTP method of request. |
+ service_path: Service path derived from request URL. |
+ remote_method: Sub-path after service path has been matched. |
+ """ |
+ self.response.headers['x-content-type-options'] = 'nosniff' |
+ if not remote_method and http_method == 'GET': |
+ # Special case a normal get request, presumably via a browser. |
+ self.error(405) |
+ self.__show_info(service_path, remote_method) |
+ return |
+ |
+ content_type = self.__get_content_type() |
+ |
+ # Provide server state to the service. If the service object does not have |
+ # an "initialize_request_state" method, will not attempt to assign state. |
+ try: |
+ state_initializer = self.service.initialize_request_state |
+ except AttributeError: |
+ pass |
+ else: |
+ server_port = self.request.environ.get('SERVER_PORT', None) |
+ if server_port: |
+ server_port = int(server_port) |
+ |
+ request_state = remote.HttpRequestState( |
+ remote_host=self.request.environ.get('REMOTE_HOST', None), |
+ remote_address=self.request.environ.get('REMOTE_ADDR', None), |
+ server_host=self.request.environ.get('SERVER_HOST', None), |
+ server_port=server_port, |
+ http_method=http_method, |
+ service_path=service_path, |
+ headers=list(self.__headers(content_type))) |
+ state_initializer(request_state) |
+ |
+ if not content_type: |
+ self.__send_simple_error(400, 'Invalid RPC request: missing content-type') |
+ return |
+ |
+ # Search for mapper to mediate request. |
+ for mapper in self.__factory.all_request_mappers(): |
+ if content_type in mapper.content_types: |
+ break |
+ else: |
+ if http_method == 'GET': |
+ self.error(six.moves.http_client.UNSUPPORTED_MEDIA_TYPE) |
+ self.__show_info(service_path, remote_method) |
+ else: |
+ self.__send_simple_error(six.moves.http_client.UNSUPPORTED_MEDIA_TYPE, |
+ 'Unsupported content-type: %s' % content_type) |
+ return |
+ |
+ try: |
+ if http_method not in mapper.http_methods: |
+ if http_method == 'GET': |
+ self.error(six.moves.http_client.METHOD_NOT_ALLOWED) |
+ self.__show_info(service_path, remote_method) |
+ else: |
+ self.__send_simple_error(six.moves.http_client.METHOD_NOT_ALLOWED, |
+ 'Unsupported HTTP method: %s' % http_method) |
+ return |
+ |
+ try: |
+ try: |
+ method = getattr(self.service, remote_method) |
+ method_info = method.remote |
+ except AttributeError as err: |
+ self.__send_error( |
+ 400, remote.RpcState.METHOD_NOT_FOUND_ERROR, |
+ 'Unrecognized RPC method: %s' % remote_method, |
+ mapper) |
+ return |
+ |
+ request = mapper.build_request(self, method_info.request_type) |
+ except (RequestError, messages.DecodeError) as err: |
+ self.__send_error(400, |
+ remote.RpcState.REQUEST_ERROR, |
+ 'Error parsing ProtoRPC request (%s)' % err, |
+ mapper) |
+ return |
+ |
+ try: |
+ response = method(request) |
+ except remote.ApplicationError as err: |
+ self.__send_error(400, |
+ remote.RpcState.APPLICATION_ERROR, |
+ err.message, |
+ mapper, |
+ err.error_name) |
+ return |
+ |
+ mapper.build_response(self, response) |
+ except Exception as err: |
+ logging.error('An unexpected error occured when handling RPC: %s', |
+ err, exc_info=1) |
+ |
+ self.__send_error(500, |
+ remote.RpcState.SERVER_ERROR, |
+ 'Internal Server Error', |
+ mapper) |
+ return |
+ |
+ |
+# TODO(rafek): Support tag-id only forms. |
+class URLEncodedRPCMapper(RPCMapper): |
+ """Request mapper for application/x-www-form-urlencoded forms. |
+ |
+ This mapper is useful for building forms that can invoke RPC. Many services |
+ are also configured to work using URL encoded request information because |
+ of its perceived ease of programming and debugging. |
+ |
+ The mapper must be provided with at least method_parameter or |
+ remote_method_pattern so that it is possible to determine how to determine the |
+ requests RPC method. If both are provided, the service will respond to both |
+ method request types, however, only one may be present in a given request. |
+ If both types are detected, the request will not match. |
+ """ |
+ |
+ def __init__(self, parameter_prefix=''): |
+ """Constructor. |
+ |
+ Args: |
+ parameter_prefix: If provided, all the parameters in the form are |
+ expected to begin with that prefix. |
+ """ |
+ # Private attributes: |
+ # __parameter_prefix: parameter prefix as provided by constructor |
+ # parameter. |
+ super(URLEncodedRPCMapper, self).__init__(['POST'], |
+ _URLENCODED_CONTENT_TYPE, |
+ self) |
+ self.__parameter_prefix = parameter_prefix |
+ |
+ def encode_message(self, message): |
+ """Encode a message using parameter prefix. |
+ |
+ Args: |
+ message: Message to URL Encode. |
+ |
+ Returns: |
+ URL encoded message. |
+ """ |
+ return protourlencode.encode_message(message, |
+ prefix=self.__parameter_prefix) |
+ |
+ @property |
+ def parameter_prefix(self): |
+ """Prefix all form parameters are expected to begin with.""" |
+ return self.__parameter_prefix |
+ |
+ def build_request(self, handler, request_type): |
+ """Build request from URL encoded HTTP request. |
+ |
+ Constructs message from names of URL encoded parameters. If this service |
+ handler has a parameter prefix, parameters must begin with it or are |
+ ignored. |
+ |
+ Args: |
+ handler: RequestHandler instance that is servicing request. |
+ request_type: Message type to build. |
+ |
+ Returns: |
+ Instance of request_type populated by protocol buffer in request |
+ parameters. |
+ |
+ Raises: |
+ RequestError if message type contains nested message field or repeated |
+ message field. Will raise RequestError if there are any repeated |
+ parameters. |
+ """ |
+ request = request_type() |
+ builder = protourlencode.URLEncodedRequestBuilder( |
+ request, prefix=self.__parameter_prefix) |
+ for argument in sorted(handler.request.arguments()): |
+ values = handler.request.get_all(argument) |
+ try: |
+ builder.add_parameter(argument, values) |
+ except messages.DecodeError as err: |
+ raise RequestError(str(err)) |
+ return request |
+ |
+ |
+class ProtobufRPCMapper(RPCMapper): |
+ """Request mapper for application/x-protobuf service requests. |
+ |
+ This mapper will parse protocol buffer from a POST body and return the request |
+ as a protocol buffer. |
+ """ |
+ |
+ def __init__(self): |
+ super(ProtobufRPCMapper, self).__init__(['POST'], |
+ _PROTOBUF_CONTENT_TYPE, |
+ protobuf) |
+ |
+ |
+class JSONRPCMapper(RPCMapper): |
+ """Request mapper for application/x-protobuf service requests. |
+ |
+ This mapper will parse protocol buffer from a POST body and return the request |
+ as a protocol buffer. |
+ """ |
+ |
+ def __init__(self): |
+ super(JSONRPCMapper, self).__init__( |
+ ['POST'], |
+ _JSON_CONTENT_TYPE, |
+ protojson, |
+ content_types=_EXTRA_JSON_CONTENT_TYPES) |
+ |
+ |
+def service_mapping(services, |
+ registry_path=DEFAULT_REGISTRY_PATH): |
+ """Create a services mapping for use with webapp. |
+ |
+ Creates basic default configuration and registration for ProtoRPC services. |
+ Each service listed in the service mapping has a standard service handler |
+ factory created for it. |
+ |
+ The list of mappings can either be an explicit path to service mapping or |
+ just services. If mappings are just services, they will automatically |
+ be mapped to their default name. For exampel: |
+ |
+ package = 'my_package' |
+ |
+ class MyService(remote.Service): |
+ ... |
+ |
+ server_mapping([('/my_path', MyService), # Maps to /my_path |
+ MyService, # Maps to /my_package/MyService |
+ ]) |
+ |
+ Specifying a service mapping: |
+ |
+ Normally services are mapped to URL paths by specifying a tuple |
+ (path, service): |
+ path: The path the service resides on. |
+ service: The service class or service factory for creating new instances |
+ of the service. For more information about service factories, please |
+ see remote.Service.new_factory. |
+ |
+ If no tuple is provided, and therefore no path specified, a default path |
+ is calculated by using the fully qualified service name using a URL path |
+ separator for each of its components instead of a '.'. |
+ |
+ Args: |
+ services: Can be service type, service factory or string definition name of |
+ service being mapped or list of tuples (path, service): |
+ path: Path on server to map service to. |
+ service: Service type, service factory or string definition name of |
+ service being mapped. |
+ Can also be a dict. If so, the keys are treated as the path and values as |
+ the service. |
+ registry_path: Path to give to registry service. Use None to disable |
+ registry service. |
+ |
+ Returns: |
+ List of tuples defining a mapping of request handlers compatible with a |
+ webapp application. |
+ |
+ Raises: |
+ ServiceConfigurationError when duplicate paths are provided. |
+ """ |
+ if isinstance(services, dict): |
+ services = six.iteritems(services) |
+ mapping = [] |
+ registry_map = {} |
+ |
+ if registry_path is not None: |
+ registry_service = registry.RegistryService.new_factory(registry_map) |
+ services = list(services) + [(registry_path, registry_service)] |
+ mapping.append((registry_path + r'/form(?:/)?', |
+ forms.FormsHandler.new_factory(registry_path))) |
+ mapping.append((registry_path + r'/form/(.+)', forms.ResourceHandler)) |
+ |
+ paths = set() |
+ for service_item in services: |
+ infer_path = not isinstance(service_item, (list, tuple)) |
+ if infer_path: |
+ service = service_item |
+ else: |
+ service = service_item[1] |
+ |
+ service_class = getattr(service, 'service_class', service) |
+ |
+ if infer_path: |
+ path = '/' + service_class.definition_name().replace('.', '/') |
+ else: |
+ path = service_item[0] |
+ |
+ if path in paths: |
+ raise ServiceConfigurationError( |
+ 'Path %r is already defined in service mapping' % path.encode('utf-8')) |
+ else: |
+ paths.add(path) |
+ |
+ # Create service mapping for webapp. |
+ new_mapping = ServiceHandlerFactory.default(service).mapping(path) |
+ mapping.append(new_mapping) |
+ |
+ # Update registry with service class. |
+ registry_map[path] = service_class |
+ |
+ return mapping |
+ |
+ |
+def run_services(services, |
+ registry_path=DEFAULT_REGISTRY_PATH): |
+ """Handle CGI request using service mapping. |
+ |
+ Args: |
+ Same as service_mapping. |
+ """ |
+ mappings = service_mapping(services, registry_path=registry_path) |
+ application = webapp.WSGIApplication(mappings) |
+ webapp_util.run_wsgi_app(application) |