1
2
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
30 """
31 Base service error.
32 """
33
34
36 """
37 Client made a request for an unknown service.
38 """
39 _amf_code = 'Service.ResourceNotFound'
40
41
43 """
44 Client made a request for an unknown method.
45 """
46 _amf_code = 'Service.MethodNotFound'
47
48
50 """
51 Client made a request for an invalid methodname.
52 """
53 _amf_code = 'Service.MethodInvalid'
54
55
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
74 if isinstance(other, ServiceWrapper):
75 return cmp(self.__dict__, other.__dict__)
76
77 return cmp(self.service, other)
78
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
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
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
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
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
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
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
233 return self.service(self.method, args)
234
235
237 """
238 I hold a collection of services, mapping names to objects.
239 """
241 if isinstance(value, basestring):
242 return value in self.keys()
243
244 return value in self.values()
245
246
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 @ivar debug: Provides debugging information when an error occurs. Use only
268 in non production settings.
269 @type debug: C{bool}
270 """
271
272 _request_class = ServiceRequest
273
274 - def __init__(self, services={}, **kwargs):
275 if not hasattr(services, 'iteritems'):
276 raise TypeError("dict type required for services")
277
278 self.services = ServiceCollection()
279 self.authenticator = kwargs.pop('authenticator', None)
280 self.preprocessor = kwargs.pop('preprocessor', None)
281 self.expose_request = kwargs.pop('expose_request', False)
282 self.strict = kwargs.pop('strict', False)
283 self.logger = kwargs.pop('logger', None)
284 self.timezone_offset = kwargs.pop('timezone_offset', None)
285
286 self.debug = kwargs.pop('debug', False)
287
288 if kwargs:
289 raise TypeError('Unknown kwargs: %r' % (kwargs,))
290
291 for name, service in services.iteritems():
292 self.addService(service, name)
293
294 - def addService(self, service, name=None, description=None,
295 authenticator=None, expose_request=None, preprocessor=None):
296 """
297 Adds a service to the gateway.
298
299 @param service: The service to add to the gateway.
300 @type service: C{callable}, class instance, or a module
301 @param name: The name of the service.
302 @type name: C{str}
303 @raise pyamf.remoting.RemotingError: Service already exists.
304 @raise TypeError: C{service} cannot be a scalar value.
305 @raise TypeError: C{service} must be C{callable} or a module.
306 """
307 if isinstance(service, (int, long, float, basestring)):
308 raise TypeError("Service cannot be a scalar value")
309
310 allowed_types = (types.ModuleType, types.FunctionType, types.DictType,
311 types.MethodType, types.InstanceType, types.ObjectType)
312
313 if not callable(service) and not isinstance(service, allowed_types):
314 raise TypeError("Service must be a callable, module, or an object")
315
316 if name is None:
317
318 if isinstance(service, (type, types.ClassType)):
319 name = service.__name__
320 elif isinstance(service, types.FunctionType):
321 name = service.func_name
322 elif isinstance(service, types.ModuleType):
323 name = service.__name__
324 else:
325 name = str(service)
326
327 if name in self.services:
328 raise remoting.RemotingError("Service %s already exists" % name)
329
330 self.services[name] = ServiceWrapper(service, description,
331 authenticator, expose_request, preprocessor)
332
334 if self.timezone_offset is None:
335 return None
336
337 if isinstance(self.timezone_offset, datetime.timedelta):
338 return self.timezone_offset
339
340 return datetime.timedelta(seconds=self.timezone_offset)
341
343 """
344 Removes a service from the gateway.
345
346 @param service: The service to remove from the gateway.
347 @type service: C{callable} or a class instance
348 @raise NameError: Service not found.
349 """
350 if service not in self.services:
351 raise NameError("Service %s not found" % str(service))
352
353 for name, wrapper in self.services.iteritems():
354 if isinstance(service, basestring) and service == name:
355 del self.services[name]
356
357 return
358 elif isinstance(service, ServiceWrapper) and wrapper == service:
359 del self.services[name]
360
361 return
362 elif isinstance(service, (type, types.ClassType,
363 types.FunctionType)) and wrapper.service == service:
364 del self.services[name]
365
366 return
367
368
369 raise RuntimeError("Something went wrong ...")
370
372 """
373 Returns a service based on the message.
374
375 @raise UnknownServiceError: Unknown service.
376 @param request: The AMF request.
377 @type request: L{Request<pyamf.remoting.Request>}
378 @rtype: L{ServiceRequest}
379 """
380 try:
381 return self._request_class(
382 request.envelope, self.services[target], None)
383 except KeyError:
384 pass
385
386 try:
387 sp = target.split('.')
388 name, meth = '.'.join(sp[:-1]), sp[-1]
389
390 return self._request_class(
391 request.envelope, self.services[name], meth)
392 except (ValueError, KeyError):
393 pass
394
395 raise UnknownServiceError("Unknown service %s" % target)
396
412
414 """
415 Returns the response to the request.
416
417 Any implementing gateway must define this function.
418
419 @param amf_request: The AMF request.
420 @type amf_request: L{Envelope<pyamf.remoting.Envelope>}
421
422 @return: The AMF response.
423 @rtype: L{Envelope<pyamf.remoting.Envelope>}
424 """
425 raise NotImplementedError
426
428 """
429 Decides whether the underlying http request should be exposed as the
430 first argument to the method call. This is granular, looking at the
431 service method first, then at the service level and finally checking
432 the gateway.
433
434 @rtype: C{bool}
435 """
436 expose_request = service_request.service.mustExposeRequest(service_request)
437
438 if expose_request is None:
439 if self.expose_request is None:
440 return False
441
442 return self.expose_request
443
444 return expose_request
445
447 """
448 Gets an authenticator callable based on the service_request. This is
449 granular, looking at the service method first, then at the service
450 level and finally to see if there is a global authenticator function
451 for the gateway. Returns C{None} if one could not be found.
452 """
453 auth = service_request.service.getAuthenticator(service_request)
454
455 if auth is None:
456 return self.authenticator
457
458 return auth
459
461 """
462 Processes an authentication request. If no authenticator is supplied,
463 then authentication succeeds.
464
465 @return: Returns a C{bool} based on the result of authorization. A
466 value of C{False} will stop processing the request and return an
467 error to the client.
468 @rtype: C{bool}
469 """
470 authenticator = self.getAuthenticator(service_request)
471
472 if authenticator is None:
473 return True
474
475 args = (username, password)
476
477 if hasattr(authenticator, '_pyamf_expose_request'):
478 http_request = kwargs.get('http_request', None)
479 args = (http_request,) + args
480
481 return authenticator(*args) == True
482
484 """
485 Gets a preprocessor callable based on the service_request. This is
486 granular, looking at the service method first, then at the service
487 level and finally to see if there is a global preprocessor function
488 for the gateway. Returns C{None} if one could not be found.
489 """
490 preproc = service_request.service.getPreprocessor(service_request)
491
492 if preproc is None:
493 return self.preprocessor
494
495 return preproc
496
498 """
499 Preprocesses a request.
500 """
501 processor = self.getPreprocessor(service_request)
502
503 if processor is None:
504 return
505
506 args = (service_request,) + args
507
508 if hasattr(processor, '_pyamf_expose_request'):
509 http_request = kwargs.get('http_request', None)
510 args = (http_request,) + args
511
512 return processor(*args)
513
515 """
516 Executes the service_request call
517 """
518 if self.mustExposeRequest(service_request):
519 http_request = kwargs.get('http_request', None)
520 args = (http_request,) + args
521
522 return service_request(*args)
523
524
526 """
527 A decorator that facilitates authentication per method. Setting
528 C{expose_request} to C{True} will set the underlying request object (if
529 there is one), usually HTTP and set it to the first argument of the
530 authenticating callable. If there is no request object, the default is
531 C{None}.
532
533 @raise TypeError: C{func} and authenticator must be callable.
534 """
535 if not callable(func):
536 raise TypeError('func must be callable')
537
538 if not callable(c):
539 raise TypeError('Authenticator must be callable')
540
541 attr = func
542
543 if isinstance(func, types.UnboundMethodType):
544 attr = func.im_func
545
546 if expose_request is True:
547 c = globals()['expose_request'](c)
548
549 setattr(attr, '_pyamf_authenticator', c)
550
551 return func
552
553
555 """
556 A decorator that adds an expose_request flag to the underlying callable.
557
558 @raise TypeError: C{func} must be callable.
559 """
560 if not callable(func):
561 raise TypeError("func must be callable")
562
563 if isinstance(func, types.UnboundMethodType):
564 setattr(func.im_func, '_pyamf_expose_request', True)
565 else:
566 setattr(func, '_pyamf_expose_request', True)
567
568 return func
569
570
572 """
573 A decorator that facilitates preprocessing per method. Setting
574 C{expose_request} to C{True} will set the underlying request object (if
575 there is one), usually HTTP and set it to the first argument of the
576 preprocessing callable. If there is no request object, the default is
577 C{None}.
578
579 @raise TypeError: C{func} and preprocessor must be callable.
580 """
581 if not callable(func):
582 raise TypeError('func must be callable')
583
584 if not callable(c):
585 raise TypeError('Preprocessor must be callable')
586
587 attr = func
588
589 if isinstance(func, types.UnboundMethodType):
590 attr = func.im_func
591
592 if expose_request is True:
593 c = globals()['expose_request'](c)
594
595 setattr(attr, '_pyamf_preprocessor', c)
596
597 return func
598
599
608