1
2
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
21 """
22 Base service error.
23 """
24
25 pyamf.register_class(BaseServiceError, attrs=fault_alias.attrs)
26 del fault_alias
27
29 """
30 Client made a request for an unknown service.
31 """
32 _amf_code = 'Service.ResourceNotFound'
33
35 """
36 Client made a request for an unknown method.
37 """
38 _amf_code = 'Service.MethodNotFound'
39
41 """
42 Client made a request for an invalid methodname.
43 """
44 _amf_code = 'Service.MethodInvalid'
45
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
64 if isinstance(other, ServiceWrapper):
65 return cmp(self.__dict__, other.__dict__)
66
67 return cmp(self.service, other)
68
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
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
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
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
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
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
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
211 return self.service(self.method, args)
212
214 """
215 I hold a collection of services, mapping names to objects.
216 """
218 if isinstance(value, basestring):
219 return value in self.keys()
220
221 return value in self.values()
222
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):
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
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
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
321 raise RuntimeError, "Something went wrong ..."
322
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
364
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
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
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
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
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
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
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
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
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
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
550