Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2126)

Unified Diff: third_party/gsutil/third_party/protorpc/protorpc/webapp/service_handlers.py

Issue 1377933002: [catapult] - Copy Telemetry's gsutilz over to third_party. (Closed) Base URL: https://github.com/catapult-project/catapult.git@master
Patch Set: Rename to gsutil. Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/gsutil/third_party/protorpc/protorpc/webapp/service_handlers.py
diff --git a/third_party/gsutil/third_party/protorpc/protorpc/webapp/service_handlers.py b/third_party/gsutil/third_party/protorpc/protorpc/webapp/service_handlers.py
new file mode 100755
index 0000000000000000000000000000000000000000..a574b2b4e08016c317e3b62168edcc9347264222
--- /dev/null
+++ b/third_party/gsutil/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)

Powered by Google App Engine
This is Rietveld 408576698