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

Unified Diff: third_party/gsutil/third_party/protorpc/protorpc/wsgi/service.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/wsgi/service.py
diff --git a/third_party/gsutil/third_party/protorpc/protorpc/wsgi/service.py b/third_party/gsutil/third_party/protorpc/protorpc/wsgi/service.py
new file mode 100755
index 0000000000000000000000000000000000000000..bc1377e93546f0f731f8d819d51f2444b489e9bf
--- /dev/null
+++ b/third_party/gsutil/third_party/protorpc/protorpc/wsgi/service.py
@@ -0,0 +1,268 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 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.
+#
+
+"""ProtoRPC WSGI service applications.
+
+Use functions in this module to configure ProtoRPC services for use with
+WSGI applications. For more information about WSGI, please see:
+
+ http://wsgi.org/wsgi
+ http://docs.python.org/library/wsgiref.html
+"""
+import six
+
+__author__ = 'rafek@google.com (Rafe Kaplan)'
+
+import cgi
+import six.moves.http_client
+import logging
+import re
+
+from .. import messages
+from .. import registry
+from .. import remote
+from .. import util
+from . import util as wsgi_util
+
+__all__ = [
+ 'DEFAULT_REGISTRY_PATH',
+ 'service_app',
+]
+
+_METHOD_PATTERN = r'(?:\.([^?]+))'
+_REQUEST_PATH_PATTERN = r'^(%%s)%s$' % _METHOD_PATTERN
+
+_HTTP_BAD_REQUEST = wsgi_util.error(six.moves.http_client.BAD_REQUEST)
+_HTTP_NOT_FOUND = wsgi_util.error(six.moves.http_client.NOT_FOUND)
+_HTTP_UNSUPPORTED_MEDIA_TYPE = wsgi_util.error(six.moves.http_client.UNSUPPORTED_MEDIA_TYPE)
+
+DEFAULT_REGISTRY_PATH = '/protorpc'
+
+
+@util.positional(2)
+def service_mapping(service_factory, service_path=r'.*', protocols=None):
+ """WSGI application that handles a single ProtoRPC service mapping.
+
+ Args:
+ service_factory: Service factory for creating instances of service request
+ handlers. Either callable that takes no parameters and returns a service
+ instance or a service class whose constructor requires no parameters.
+ service_path: Regular expression for matching requests against. Requests
+ that do not have matching paths will cause a 404 (Not Found) response.
+ protocols: remote.Protocols instance that configures supported protocols
+ on server.
+ """
+ service_class = getattr(service_factory, 'service_class', service_factory)
+ remote_methods = service_class.all_remote_methods()
+ path_matcher = re.compile(_REQUEST_PATH_PATTERN % service_path)
+
+ def protorpc_service_app(environ, start_response):
+ """Actual WSGI application function."""
+ path_match = path_matcher.match(environ['PATH_INFO'])
+ if not path_match:
+ return _HTTP_NOT_FOUND(environ, start_response)
+ service_path = path_match.group(1)
+ method_name = path_match.group(2)
+
+ content_type = environ.get('CONTENT_TYPE')
+ if not content_type:
+ content_type = environ.get('HTTP_CONTENT_TYPE')
+ if not content_type:
+ return _HTTP_BAD_REQUEST(environ, start_response)
+
+ # TODO(rafek): Handle alternate encodings.
+ content_type = cgi.parse_header(content_type)[0]
+
+ request_method = environ['REQUEST_METHOD']
+ if request_method != 'POST':
+ content = ('%s.%s is a ProtoRPC method.\n\n'
+ 'Service %s\n\n'
+ 'More about ProtoRPC: '
+ '%s\n' %
+ (service_path,
+ method_name,
+ service_class.definition_name().encode('utf-8'),
+ util.PROTORPC_PROJECT_URL))
+ error_handler = wsgi_util.error(
+ six.moves.http_client.METHOD_NOT_ALLOWED,
+ six.moves.http_client.responses[six.moves.http_client.METHOD_NOT_ALLOWED],
+ content=content,
+ content_type='text/plain; charset=utf-8')
+ return error_handler(environ, start_response)
+
+ local_protocols = protocols or remote.Protocols.get_default()
+ try:
+ protocol = local_protocols.lookup_by_content_type(content_type)
+ except KeyError:
+ return _HTTP_UNSUPPORTED_MEDIA_TYPE(environ,start_response)
+
+ def send_rpc_error(status_code, state, message, error_name=None):
+ """Helper function to send an RpcStatus message as response.
+
+ Will create static error handler and begin response.
+
+ Args:
+ status_code: HTTP integer status code.
+ state: remote.RpcState enum value to send as response.
+ message: Helpful message to send in response.
+ error_name: Error name if applicable.
+
+ Returns:
+ List containing encoded content response using the same content-type as
+ the request.
+ """
+ status = remote.RpcStatus(state=state,
+ error_message=message,
+ error_name=error_name)
+ encoded_status = protocol.encode_message(status)
+ error_handler = wsgi_util.error(
+ status_code,
+ content_type=protocol.default_content_type,
+ content=encoded_status)
+ return error_handler(environ, start_response)
+
+ method = remote_methods.get(method_name)
+ if not method:
+ return send_rpc_error(six.moves.http_client.BAD_REQUEST,
+ remote.RpcState.METHOD_NOT_FOUND_ERROR,
+ 'Unrecognized RPC method: %s' % method_name)
+
+ content_length = int(environ.get('CONTENT_LENGTH') or '0')
+
+ remote_info = method.remote
+ try:
+ request = protocol.decode_message(
+ remote_info.request_type, environ['wsgi.input'].read(content_length))
+ except (messages.ValidationError, messages.DecodeError) as err:
+ return send_rpc_error(six.moves.http_client.BAD_REQUEST,
+ remote.RpcState.REQUEST_ERROR,
+ 'Error parsing ProtoRPC request '
+ '(Unable to parse request content: %s)' % err)
+
+ instance = service_factory()
+
+ initialize_request_state = getattr(
+ instance, 'initialize_request_state', None)
+ if initialize_request_state:
+ # TODO(rafek): This is not currently covered by tests.
+ server_port = environ.get('SERVER_PORT', None)
+ if server_port:
+ server_port = int(server_port)
+
+ headers = []
+ for name, value in six.iteritems(environ):
+ if name.startswith('HTTP_'):
+ headers.append((name[len('HTTP_'):].lower().replace('_', '-'), value))
+ request_state = remote.HttpRequestState(
+ remote_host=environ.get('REMOTE_HOST', None),
+ remote_address=environ.get('REMOTE_ADDR', None),
+ server_host=environ.get('SERVER_HOST', None),
+ server_port=server_port,
+ http_method=request_method,
+ service_path=service_path,
+ headers=headers)
+
+ initialize_request_state(request_state)
+
+ try:
+ response = method(instance, request)
+ encoded_response = protocol.encode_message(response)
+ except remote.ApplicationError as err:
+ return send_rpc_error(six.moves.http_client.BAD_REQUEST,
+ remote.RpcState.APPLICATION_ERROR,
+ err.message,
+ err.error_name)
+ except Exception as err:
+ logging.exception('Encountered unexpected error from ProtoRPC '
+ 'method implementation: %s (%s)' %
+ (err.__class__.__name__, err))
+ return send_rpc_error(six.moves.http_client.INTERNAL_SERVER_ERROR,
+ remote.RpcState.SERVER_ERROR,
+ 'Internal Server Error')
+
+ response_headers = [('content-type', content_type)]
+ start_response('%d %s' % (six.moves.http_client.OK, six.moves.http_client.responses[six.moves.http_client.OK],),
+ response_headers)
+ return [encoded_response]
+
+ # Return WSGI application.
+ return protorpc_service_app
+
+
+@util.positional(1)
+def service_mappings(services, registry_path=DEFAULT_REGISTRY_PATH):
+ """Create multiple service mappings with optional RegistryService.
+
+ Use this function to create single WSGI application that maps to
+ multiple ProtoRPC services plus an optional RegistryService.
+
+ Example:
+ services = service.service_mappings(
+ [(r'/time', TimeService),
+ (r'/weather', WeatherService)
+ ])
+
+ In this example, the services WSGI application will map to two services,
+ TimeService and WeatherService to the '/time' and '/weather' paths
+ respectively. In addition, it will also add a ProtoRPC RegistryService
+ configured to serve information about both services at the (default) path
+ '/protorpc'.
+
+ Args:
+ services: If a dictionary is provided instead of a list of tuples, the
+ dictionary item pairs are used as the mappings instead.
+ Otherwise, a list of tuples (service_path, service_factory):
+ service_path: The path to mount service on.
+ service_factory: A service class or service instance factory.
+ registry_path: A string to change where the registry is mapped (the default
+ location is '/protorpc'). When None, no registry is created or mounted.
+
+ Returns:
+ WSGI application that serves ProtoRPC services on their respective URLs
+ plus optional RegistryService.
+ """
+ if isinstance(services, dict):
+ services = six.iteritems(services)
+
+ final_mapping = []
+ paths = set()
+ registry_map = {} if registry_path else None
+
+ for service_path, service_factory in services:
+ try:
+ service_class = service_factory.service_class
+ except AttributeError:
+ service_class = service_factory
+
+ if service_path not in paths:
+ paths.add(service_path)
+ else:
+ raise remote.ServiceConfigurationError(
+ 'Path %r is already defined in service mapping' %
+ service_path.encode('utf-8'))
+
+ if registry_map is not None:
+ registry_map[service_path] = service_class
+
+ final_mapping.append(service_mapping(service_factory, service_path))
+
+ if registry_map is not None:
+ final_mapping.append(service_mapping(
+ registry.RegistryService.new_factory(registry_map), registry_path))
+
+ return wsgi_util.first_found(final_mapping)
+

Powered by Google App Engine
This is Rietveld 408576698