Package pyamf :: Package remoting :: Package gateway

[hide private]
[frames] | no frames]

# Source Code for Package pyamf.remoting.gateway

      1  # Copyright (c) 2007-2009 The PyAMF Project.
      2  # See LICENSE for details.
      3
      4  """
      5  Remoting server implementations.
      6
      7  @since: 0.1.0
      8  """
      9
     10  import sys, types
     11
     12  import pyamf
     13  from pyamf import remoting, logging, util
     14
     15  SERVER_NAME = 'PyAMF/%s Python/%s' % (
     16      '.'.join(map(lambda x: str(x), pyamf.__version__)),
     17      '.'.join(map(lambda x: str(x), sys.version_info[0:3]))
     18  )
     19
     20  fault_alias = pyamf.get_class_alias(remoting.ErrorFault)
     21
     22 -class BaseServiceError(pyamf.BaseError):
     23      """
     24      Base service error.
     25      """
     26
     27  pyamf.register_class(BaseServiceError, attrs=fault_alias.attrs)
     28  del fault_alias
     29
     30 -class UnknownServiceError(BaseServiceError):
     31      """
     32      Client made a request for an unknown service.
     33      """
     34      _amf_code = 'Service.ResourceNotFound'
     35
     36 -class UnknownServiceMethodError(BaseServiceError):
     37      """
     38      Client made a request for an unknown method.
     39      """
     40      _amf_code = 'Service.MethodNotFound'
     41
     42 -class InvalidServiceMethodError(BaseServiceError):
     43      """
     44      Client made a request for an invalid methodname.
     45      """
     46      _amf_code = 'Service.MethodInvalid'
     47
     48 -class ServiceWrapper(object):
     49      """
     50      Wraps a supplied service with extra functionality.
     51
     52      @ivar service: The original service.
     53      @type service: C{callable}
     54      @ivar description: A description of the service.
     55      @type description: C{str}
     56      """
     57 -    def __init__(self, service, description=None, authenticator=None,
     58          expose_request=None, preprocessor=None):
     59          self.service = service
     60          self.description = description
     61          self.authenticator = authenticator
     62          self.expose_request = expose_request
     63          self.preprocessor = preprocessor
     64
     65 -    def __cmp__(self, other):
     66          if isinstance(other, ServiceWrapper):
     67              return cmp(self.__dict__, other.__dict__)
     68
     69          return cmp(self.service, other)
     70
     71 -    def _get_service_func(self, method, params):
     72          """
     73          @raise InvalidServiceMethodError: Calls to private methods are not
     74              allowed.
     75          @raise UnknownServiceMethodError: Unknown method.
     76          @raise InvalidServiceMethodError: Service method must be callable.
     77          """
     78          service = None
     79
     80          if isinstance(self.service, (type, types.ClassType)):
     81              service = self.service()
     82          else:
     83              service = self.service
     84
     85          if method is not None:
     86              method = str(method)
     87
     88              if method.startswith('_'):
     89                  raise InvalidServiceMethodError(
     90                      "Calls to private methods are not allowed")
     91
     92              try:
     93                  func = getattr(service, method)
     94              except AttributeError:
     95                  raise UnknownServiceMethodError(
     96                      "Unknown method %s" % str(method))
     97
     98              if not callable(func):
     99                  raise InvalidServiceMethodError(
    100                      "Service method %s must be callable" % str(method))
    101
    102              return func
    103
    104          if not callable(service):
    105              raise UnknownServiceMethodError(
    106                  "Unknown method %s" % str(self.service))
    107
    108          return service
    109
    110 -    def __call__(self, method, params):
    111          """
    112          Executes the service.
    113
    114          If the service is a class, it will be instantiated.
    115
    116          @param method: The method to call on the service.
    117          @type method: C{None} or C{mixed}
    118          @param params: The params to pass to the service.
    119          @type params: C{list} or C{tuple}
    120          @return: The result of the execution.
    121          @rtype: C{mixed}
    122          """
    123          func = self._get_service_func(method, params)
    124
    125          return func(*params)
    126
    127 -    def getMethods(self):
    128          """
    129          Gets a C{dict} of valid method callables for the underlying service
    130          object.
    131          """
    132          callables = {}
    133
    134          for name in dir(self.service):
    135              method = getattr(self.service, name)
    136
    137              if name.startswith('_') or not callable(method):
    138                  continue
    139
    140              callables[name] = method
    141
    142          return callables
    143
    144 -    def getAuthenticator(self, service_request=None):
    145          if service_request == None:
    146              return self.authenticator
    147
    148          methods = self.getMethods()
    149
    150          if service_request.method is None:
    151              if hasattr(self.service, '_pyamf_authenticator'):
    152                  return self.service._pyamf_authenticator
    153
    154          if service_request.method not in methods:
    155              return self.authenticator
    156
    157          method = methods[service_request.method]
    158
    159          if hasattr(method, '_pyamf_authenticator'):
    160              return method._pyamf_authenticator
    161
    162          return self.authenticator
    163
    164 -    def mustExposeRequest(self, service_request=None):
    165          if service_request == None:
    166              return self.expose_request
    167
    168          methods = self.getMethods()
    169
    170          if service_request.method is None:
    171              if hasattr(self.service, '_pyamf_expose_request'):
    172                  return self.service._pyamf_expose_request
    173
    174              return self.expose_request
    175
    176          if service_request.method not in methods:
    177              return self.expose_request
    178
    179          method = methods[service_request.method]
    180
    181          if hasattr(method, '_pyamf_expose_request'):
    182              return method._pyamf_expose_request
    183
    184          return self.expose_request
    185
    186 -    def getPreprocessor(self, service_request=None):
    187          if service_request == None:
    188              return self.preprocessor
    189
    190          methods = self.getMethods()
    191
    192          if service_request.method is None:
    193              if hasattr(self.service, '_pyamf_preprocessor'):
    194                  return self.service._pyamf_preprocessor
    195
    196          if service_request.method not in methods:
    197              return self.preprocessor
    198
    199          method = methods[service_request.method]
    200
    201          if hasattr(method, '_pyamf_preprocessor'):
    202              return method._pyamf_preprocessor
    203
    204          return self.preprocessor
    205
    206 -class ServiceRequest(object):
    207      """
    208      Remoting service request.
    209
    210      @ivar request: The request to service.
    211      @type request: L{Envelope<pyamf.remoting.Envelope>}
    212      @ivar service: Facilitates the request.
    213      @type service: L{ServiceWrapper}
    214      @ivar method: The method to call on the service. A value of C{None}
    215          means that the service will be called directly.
    216      @type method: C{None} or C{str}
    217      """
    218 -    def __init__(self, amf_request, service, method):
    219          self.request = amf_request
    220          self.service = service
    221          self.method = method
    222
    223 -    def __call__(self, *args):
    224          return self.service(self.method, args)
    225
    226 -class ServiceCollection(dict):
    227      """
    228      I hold a collection of services, mapping names to objects.
    229      """
    230 -    def __contains__(self, value):
    231          if isinstance(value, basestring):
    232              return value in self.keys()
    233
    234          return value in self.values()
    235
    236 -class BaseGateway(object):
    237      """
    238      Generic Remoting gateway.
    239
    240      @ivar services: A map of service names to callables.
    241      @type services: L{ServiceCollection}
    242      @ivar authenticator: A callable that will check the credentials of
    243          the request before allowing access to the service. Will return a
    244          C{bool} value.
    245      @type authenticator: C{Callable} or C{None}
    246      @ivar preprocessor: Called before the actual service method is invoked.
    247          Useful for setting up sessions etc.
    248      @ivar strict: Defines whether the gateway should use strict en/decoding.
    249      @type strict: C{bool}
    250      """
    251      _request_class = ServiceRequest
    252      debug = False
    253
    254 -    def __init__(self, services={}, authenticator=None, expose_request=False,
    255          preprocessor=None, debug=None, strict=False):
    256          """
    257          @raise TypeError: C{dict} type is required for C{services}.
    258          """
    259          self.logger = logging.instance_logger(self)
    260          self.services = ServiceCollection()
    261          self.authenticator = authenticator
    262          self.preprocessor = preprocessor
    263          self.expose_request = expose_request
    264          self.strict=strict
    265
    266          if debug is not None:
    267              self.debug = debug
    268
    269          if not hasattr(services, 'iteritems'):
    270              raise TypeError("dict type required for services")
    271
    272          for name, service in services.iteritems():
    273              self.addService(service, name)
    274
    275 -    def addService(self, service, name=None, description=None,
    276          authenticator=None, expose_request=None, preprocessor=None):
    277          """
    278          Adds a service to the gateway.
    279
    280          @param service: The service to add to the gateway.
    281          @type service: C{callable}, class instance, or a module
    282          @param name: The name of the service.
    283          @type name: C{str}
    284          @raise pyamf.remoting.RemotingError: Service already exists.
    285          @raise TypeError: C{service} cannot be a scalar value.
    286          @raise TypeError: C{service} must be C{callable} or a module.
    287          """
    288          if isinstance(service, (int, long, float, basestring)):
    289              raise TypeError("Service cannot be a scalar value")
    290
    291          allowed_types = (types.ModuleType, types.FunctionType, types.DictType,
    292              types.MethodType, types.InstanceType, types.ObjectType)
    293
    294          if not callable(service) and not isinstance(service, allowed_types):
    295              raise TypeError("Service must be a callable, module, or an object")
    296
    297          if name is None:
    298              # TODO: include the module in the name
    299              if isinstance(service, (type, types.ClassType)):
    300                  name = service.__name__
    301              elif isinstance(service, types.FunctionType):
    302                  name = service.func_name
    303              elif isinstance(service, types.ModuleType):
    304                  name = service.__name__
    305              else:
    306                  name = str(service)
    307
    308          if name in self.services:
    309              raise remoting.RemotingError("Service %s already exists" % name)
    310
    311          self.services[name] = ServiceWrapper(service, description,
    312              authenticator, expose_request, preprocessor)
    313
    314 -    def removeService(self, service):
    315          """
    316          Removes a service from the gateway.
    317
    318          @param service: The service to remove from the gateway.
    319          @type service: C{callable} or a class instance
    320          @raise NameError: Service not found.
    321          """
    322          if service not in self.services:
    323              raise NameError("Service %s not found" % str(service))
    324
    325          for name, wrapper in self.services.iteritems():
    326              if isinstance(service, basestring) and service == name:
    327                  del self.services[name]
    328
    329                  return
    330              elif isinstance(service, ServiceWrapper) and wrapper == service:
    331                  del self.services[name]
    332
    333                  return
    334              elif isinstance(service, (type, types.ClassType,
    335                  types.FunctionType)) and wrapper.service == service:
    336                  del self.services[name]
    337
    338                  return
    339
    340          # shouldn't ever get here
    341          raise RuntimeError("Something went wrong ...")
    342
    343 -    def getServiceRequest(self, request, target):
    344          """
    345          Returns a service based on the message.
    346
    347          @raise UnknownServiceError: Unknown service.
    348          @param request: The AMF request.
    349          @type request: L{Request<pyamf.remoting.Request>}
    350          @rtype: L{ServiceRequest}
    351          """
    352          try:
    353              return self._request_class(
    354                  request.envelope, self.services[target], None)
    355          except KeyError:
    356              pass
    357
    358          try:
    359              sp = target.split('.')
    360              name, meth = '.'.join(sp[:-1]), sp[-1]
    361
    362              return self._request_class(
    363                  request.envelope, self.services[name], meth)
    364          except (ValueError, KeyError):
    365              pass
    366
    367          raise UnknownServiceError("Unknown service %s" % target)
    368
    369 -    def getProcessor(self, request):
    370          """
    371          Returns request processor.
    372
    373          @param request: The AMF message.
    374          @type request: L{Request<remoting.Request>}
    375          """
    376          if request.target == 'null':
    377              from pyamf.remoting import amf3
    378
    379              return amf3.RequestProcessor(self)
    380          else:
    381              from pyamf.remoting import amf0
    382
    383              return amf0.RequestProcessor(self)
    384
    385 -    def getResponse(self, amf_request):
    386          """
    387          Returns the response to the request.
    388
    389          Any implementing gateway must define this function.
    390
    391          @param amf_request: The AMF request.
    392          @type amf_request: L{Envelope<pyamf.remoting.Envelope>}
    393
    394          @return: The AMF response.
    395          @rtype: L{Envelope<pyamf.remoting.Envelope>}
    396          """
    397          raise NotImplementedError
    398
    399 -    def mustExposeRequest(self, service_request):
    400          """
    401          Decides whether the underlying http request should be exposed as the
    402          first argument to the method call. This is granular, looking at the
    403          service method first, then at the service level and finally checking
    404          the gateway.
    405
    406          @rtype: C{bool}
    407          """
    408          expose_request = service_request.service.mustExposeRequest(service_request)
    409
    410          if expose_request is None:
    411              if self.expose_request is None:
    412                  return False
    413
    414              return self.expose_request
    415
    416          return expose_request
    417
    418 -    def getAuthenticator(self, service_request):
    419          """
    420          Gets an authenticator callable based on the service_request. This is
    421          granular, looking at the service method first, then at the service
    422          level and finally to see if there is a global authenticator function
    423          for the gateway. Returns C{None} if one could not be found.
    424          """
    425          auth = service_request.service.getAuthenticator(service_request)
    426
    427          if auth is None:
    428              return self.authenticator
    429
    430          return auth
    431
    432 -    def authenticateRequest(self, service_request, username, password, **kwargs):
    433          """
    434          Processes an authentication request. If no authenticator is supplied,
    435          then authentication succeeds.
    436
    437          @return: Returns a C{bool} based on the result of authorization. A
    438              value of C{False} will stop processing the request and return an
    439              error to the client.
    440          @rtype: C{bool}
    441          """
    442          authenticator = self.getAuthenticator(service_request)
    443
    444          if authenticator is None:
    445              return True
    446
    447          args = (username, password)
    448
    449          if hasattr(authenticator, '_pyamf_expose_request'):
    450              http_request = kwargs.get('http_request', None)
    451              args = (http_request,) + args
    452
    453          return authenticator(*args) == True
    454
    455 -    def getPreprocessor(self, service_request):
    456          """
    457          Gets a preprocessor callable based on the service_request. This is
    458          granular, looking at the service method first, then at the service
    459          level and finally to see if there is a global preprocessor function
    460          for the gateway. Returns C{None} if one could not be found.
    461          """
    462          preproc = service_request.service.getPreprocessor(service_request)
    463
    464          if preproc is None:
    465              return self.preprocessor
    466
    467          return preproc
    468
    469 -    def preprocessRequest(self, service_request, *args, **kwargs):
    470          """
    471          Preprocesses a request.
    472          """
    473          processor = self.getPreprocessor(service_request)
    474
    475          if processor is None:
    476              return
    477
    478          args = (service_request,) + args
    479
    480          if hasattr(processor, '_pyamf_expose_request'):
    481              http_request = kwargs.get('http_request', None)
    482              args = (http_request,) + args
    483
    484          return processor(*args)
    485
    486 -    def callServiceRequest(self, service_request, *args, **kwargs):
    487          """
    488          Executes the service_request call
    489          """
    490          if self.mustExposeRequest(service_request):
    491              http_request = kwargs.get('http_request', None)
    492              args = (http_request,) + args
    493
    494          return service_request(*args)
    495
    496 -def authenticate(func, c, expose_request=False):
    497      """
    498      A decorator that facilitates authentication per method. Setting
    499      C{expose_request} to C{True} will set the underlying request object (if
    500      there is one), usually HTTP and set it to the first argument of the
    501      authenticating callable. If there is no request object, the default is
    502      C{None}.
    503
    504      @raise TypeError: C{func} and authenticator must be callable.
    505      """
    506      if not callable(func):
    507          raise TypeError('func must be callable')
    508
    509      if not callable(c):
    510          raise TypeError('Authenticator must be callable')
    511
    512      attr = func
    513
    514      if isinstance(func, types.UnboundMethodType):
    515          attr = func.im_func
    516
    517      if expose_request is True:
    518          c = globals()['expose_request'](c)
    519
    520      setattr(attr, '_pyamf_authenticator', c)
    521
    522      return func
    523
    524 -def expose_request(func):
    525      """
    526      A decorator that adds an expose_request flag to the underlying callable.
    527
    528      @raise TypeError: C{func} must be callable.
    529      """
    530      if not callable(func):
    531          raise TypeError("func must be callable")
    532
    533      if isinstance(func, types.UnboundMethodType):
    534          setattr(func.im_func, '_pyamf_expose_request', True)
    535      else:
    536          setattr(func, '_pyamf_expose_request', True)
    537
    538      return func
    539
    540 -def preprocess(func, c, expose_request=False):
    541      """
    542      A decorator that facilitates preprocessing per method. Setting
    543      C{expose_request} to C{True} will set the underlying request object (if
    544      there is one), usually HTTP and set it to the first argument of the
    545      preprocessing callable. If there is no request object, the default is
    546      C{None}.
    547
    548      @raise TypeError: C{func} and preprocessor must be callable.
    549      """
    550      if not callable(func):
    551          raise TypeError('func must be callable')
    552
    553      if not callable(c):
    554          raise TypeError('Preprocessor must be callable')
    555
    556      attr = func
    557
    558      if isinstance(func, types.UnboundMethodType):
    559          attr = func.im_func
    560
    561      if expose_request is True:
    562          c = globals()['expose_request'](c)
    563
    564      setattr(attr, '_pyamf_preprocessor', c)
    565
    566      return func
    567
    568 -def format_exception():
    569      import traceback
    570
    571      f = util.StringIO()
    572
    573      traceback.print_exc(file=f)
    574
    575      return f.getvalue()
    576