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