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