This file is indexed.

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