/usr/lib/python3/dist-packages/protorpc/webapp/service_handlers.py is in python3-protorpc-standalone 0.9.1-3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 | #!/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.
"""
__author__ = 'rafek@google.com (Rafe Kaplan)'
import httplib
import logging
import webapp2 as 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 = httplib.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(httplib.UNSUPPORTED_MEDIA_TYPE)
self.__show_info(service_path, remote_method)
else:
self.__send_simple_error(httplib.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(httplib.METHOD_NOT_ALLOWED)
self.__show_info(service_path, remote_method)
else:
self.__send_simple_error(httplib.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 = services.iteritems()
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)
|