Package pyamf :: Package remoting :: Package gateway
[hide private]
[frames] | no frames]

Source Code for Package pyamf.remoting.gateway

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