Index: tools/telemetry/third_party/gsutil/third_party/protorpc/protorpc/webapp/service_handlers.py |
diff --git a/tools/telemetry/third_party/gsutil/third_party/protorpc/protorpc/webapp/service_handlers.py b/tools/telemetry/third_party/gsutil/third_party/protorpc/protorpc/webapp/service_handlers.py |
deleted file mode 100644 |
index a574b2b4e08016c317e3b62168edcc9347264222..0000000000000000000000000000000000000000 |
--- a/tools/telemetry/third_party/gsutil/third_party/protorpc/protorpc/webapp/service_handlers.py |
+++ /dev/null |
@@ -1,834 +0,0 @@ |
-#!/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) |