| 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)
|
|
|