1
2
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
23 """
24 Base service error.
25 """
26
27 pyamf.register_class(BaseServiceError, attrs=fault_alias.attrs)
28 del fault_alias
29
31 """
32 Client made a request for an unknown service.
33 """
34 _amf_code = 'Service.ResourceNotFound'
35
37 """
38 Client made a request for an unknown method.
39 """
40 _amf_code = 'Service.MethodNotFound'
41
43 """
44 Client made a request for an invalid methodname.
45 """
46 _amf_code = 'Service.MethodInvalid'
47
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
66 if isinstance(other, ServiceWrapper):
67 return cmp(self.__dict__, other.__dict__)
68
69 return cmp(self.service, other)
70
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
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
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
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
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
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
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
224 return self.service(self.method, args)
225
227 """
228 I hold a collection of services, mapping names to objects.
229 """
231 if isinstance(value, basestring):
232 return value in self.keys()
233
234 return value in self.values()
235
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
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
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
341 raise RuntimeError("Something went wrong ...")
342
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
384
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
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
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
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
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
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
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
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
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
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
576