Imported Upstream version 12.1.0
[contrib/python-twisted.git] / twisted / web / xmlrpc.py
1 # -*- test-case-name: twisted.web.test.test_xmlrpc -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 A generic resource for publishing objects via XML-RPC.
7
8 Maintainer: Itamar Shtull-Trauring
9 """
10
11 # System Imports
12 import sys, xmlrpclib, urlparse
13
14
15 # Sibling Imports
16 from twisted.web import resource, server, http
17 from twisted.internet import defer, protocol, reactor
18 from twisted.python import log, reflect, failure
19
20 # These are deprecated, use the class level definitions
21 NOT_FOUND = 8001
22 FAILURE = 8002
23
24
25 # Useful so people don't need to import xmlrpclib directly
26 Fault = xmlrpclib.Fault
27 Binary = xmlrpclib.Binary
28 Boolean = xmlrpclib.Boolean
29 DateTime = xmlrpclib.DateTime
30
31 # On Python 2.4 and earlier, DateTime.decode returns unicode.
32 if sys.version_info[:2] < (2, 5):
33     _decode = DateTime.decode
34     DateTime.decode = lambda self, value: _decode(self, value.encode('ascii'))
35
36
37 def withRequest(f):
38     """
39     Decorator to cause the request to be passed as the first argument
40     to the method.
41
42     If an I{xmlrpc_} method is wrapped with C{withRequest}, the
43     request object is passed as the first argument to that method.
44     For example::
45
46         @withRequest
47         def xmlrpc_echo(self, request, s):
48             return s
49
50     @since: 10.2
51     """
52     f.withRequest = True
53     return f
54
55
56
57 class NoSuchFunction(Fault):
58     """
59     There is no function by the given name.
60     """
61
62
63 class Handler:
64     """
65     Handle a XML-RPC request and store the state for a request in progress.
66
67     Override the run() method and return result using self.result,
68     a Deferred.
69
70     We require this class since we're not using threads, so we can't
71     encapsulate state in a running function if we're going  to have
72     to wait for results.
73
74     For example, lets say we want to authenticate against twisted.cred,
75     run a LDAP query and then pass its result to a database query, all
76     as a result of a single XML-RPC command. We'd use a Handler instance
77     to store the state of the running command.
78     """
79
80     def __init__(self, resource, *args):
81         self.resource = resource # the XML-RPC resource we are connected to
82         self.result = defer.Deferred()
83         self.run(*args)
84
85     def run(self, *args):
86         # event driven equivalent of 'raise UnimplementedError'
87         self.result.errback(
88             NotImplementedError("Implement run() in subclasses"))
89
90
91 class XMLRPC(resource.Resource):
92     """
93     A resource that implements XML-RPC.
94
95     You probably want to connect this to '/RPC2'.
96
97     Methods published can return XML-RPC serializable results, Faults,
98     Binary, Boolean, DateTime, Deferreds, or Handler instances.
99
100     By default methods beginning with 'xmlrpc_' are published.
101
102     Sub-handlers for prefixed methods (e.g., system.listMethods)
103     can be added with putSubHandler. By default, prefixes are
104     separated with a '.'. Override self.separator to change this.
105
106     @ivar allowNone: Permit XML translating of Python constant None.
107     @type allowNone: C{bool}
108
109     @ivar useDateTime: Present datetime values as datetime.datetime objects?
110         Requires Python >= 2.5.
111     @type useDateTime: C{bool}
112     """
113
114     # Error codes for Twisted, if they conflict with yours then
115     # modify them at runtime.
116     NOT_FOUND = 8001
117     FAILURE = 8002
118
119     isLeaf = 1
120     separator = '.'
121     allowedMethods = ('POST',)
122
123     def __init__(self, allowNone=False, useDateTime=False):
124         resource.Resource.__init__(self)
125         self.subHandlers = {}
126         self.allowNone = allowNone
127         self.useDateTime = useDateTime
128
129
130     def __setattr__(self, name, value):
131         if name == "useDateTime" and value and sys.version_info[:2] < (2, 5):
132             raise RuntimeError("useDateTime requires Python 2.5 or later.")
133         self.__dict__[name] = value
134
135
136     def putSubHandler(self, prefix, handler):
137         self.subHandlers[prefix] = handler
138
139     def getSubHandler(self, prefix):
140         return self.subHandlers.get(prefix, None)
141
142     def getSubHandlerPrefixes(self):
143         return self.subHandlers.keys()
144
145     def render_POST(self, request):
146         request.content.seek(0, 0)
147         request.setHeader("content-type", "text/xml")
148         try:
149             if self.useDateTime:
150                 args, functionPath = xmlrpclib.loads(request.content.read(),
151                     use_datetime=True)
152             else:
153                 # Maintain backwards compatibility with Python < 2.5
154                 args, functionPath = xmlrpclib.loads(request.content.read())
155         except Exception, e:
156             f = Fault(self.FAILURE, "Can't deserialize input: %s" % (e,))
157             self._cbRender(f, request)
158         else:
159             try:
160                 function = self.lookupProcedure(functionPath)
161             except Fault, f:
162                 self._cbRender(f, request)
163             else:
164                 # Use this list to track whether the response has failed or not.
165                 # This will be used later on to decide if the result of the
166                 # Deferred should be written out and Request.finish called.
167                 responseFailed = []
168                 request.notifyFinish().addErrback(responseFailed.append)
169                 if getattr(function, 'withRequest', False):
170                     d = defer.maybeDeferred(function, request, *args)
171                 else:
172                     d = defer.maybeDeferred(function, *args)
173                 d.addErrback(self._ebRender)
174                 d.addCallback(self._cbRender, request, responseFailed)
175         return server.NOT_DONE_YET
176
177
178     def _cbRender(self, result, request, responseFailed=None):
179         if responseFailed:
180             return
181
182         if isinstance(result, Handler):
183             result = result.result
184         if not isinstance(result, Fault):
185             result = (result,)
186         try:
187             try:
188                 content = xmlrpclib.dumps(
189                     result, methodresponse=True,
190                     allow_none=self.allowNone)
191             except Exception, e:
192                 f = Fault(self.FAILURE, "Can't serialize output: %s" % (e,))
193                 content = xmlrpclib.dumps(f, methodresponse=True,
194                                           allow_none=self.allowNone)
195
196             request.setHeader("content-length", str(len(content)))
197             request.write(content)
198         except:
199             log.err()
200         request.finish()
201
202
203     def _ebRender(self, failure):
204         if isinstance(failure.value, Fault):
205             return failure.value
206         log.err(failure)
207         return Fault(self.FAILURE, "error")
208
209
210     def lookupProcedure(self, procedurePath):
211         """
212         Given a string naming a procedure, return a callable object for that
213         procedure or raise NoSuchFunction.
214
215         The returned object will be called, and should return the result of the
216         procedure, a Deferred, or a Fault instance.
217
218         Override in subclasses if you want your own policy.  The base
219         implementation that given C{'foo'}, C{self.xmlrpc_foo} will be returned.
220         If C{procedurePath} contains C{self.separator}, the sub-handler for the
221         initial prefix is used to search for the remaining path.
222
223         If you override C{lookupProcedure}, you may also want to override
224         C{listProcedures} to accurately report the procedures supported by your
225         resource, so that clients using the I{system.listMethods} procedure
226         receive accurate results.
227
228         @since: 11.1
229         """
230         if procedurePath.find(self.separator) != -1:
231             prefix, procedurePath = procedurePath.split(self.separator, 1)
232             handler = self.getSubHandler(prefix)
233             if handler is None:
234                 raise NoSuchFunction(self.NOT_FOUND,
235                     "no such subHandler %s" % prefix)
236             return handler.lookupProcedure(procedurePath)
237
238         f = getattr(self, "xmlrpc_%s" % procedurePath, None)
239         if not f:
240             raise NoSuchFunction(self.NOT_FOUND,
241                 "procedure %s not found" % procedurePath)
242         elif not callable(f):
243             raise NoSuchFunction(self.NOT_FOUND,
244                 "procedure %s not callable" % procedurePath)
245         else:
246             return f
247
248     def listProcedures(self):
249         """
250         Return a list of the names of all xmlrpc procedures.
251
252         @since: 11.1
253         """
254         return reflect.prefixedMethodNames(self.__class__, 'xmlrpc_')
255
256
257 class XMLRPCIntrospection(XMLRPC):
258     """
259     Implement the XML-RPC Introspection API.
260
261     By default, the methodHelp method returns the 'help' method attribute,
262     if it exists, otherwise the __doc__ method attribute, if it exists,
263     otherwise the empty string.
264
265     To enable the methodSignature method, add a 'signature' method attribute
266     containing a list of lists. See methodSignature's documentation for the
267     format. Note the type strings should be XML-RPC types, not Python types.
268     """
269
270     def __init__(self, parent):
271         """
272         Implement Introspection support for an XMLRPC server.
273
274         @param parent: the XMLRPC server to add Introspection support to.
275         @type parent: L{XMLRPC}
276         """
277         XMLRPC.__init__(self)
278         self._xmlrpc_parent = parent
279
280     def xmlrpc_listMethods(self):
281         """
282         Return a list of the method names implemented by this server.
283         """
284         functions = []
285         todo = [(self._xmlrpc_parent, '')]
286         while todo:
287             obj, prefix = todo.pop(0)
288             functions.extend([prefix + name for name in obj.listProcedures()])
289             todo.extend([ (obj.getSubHandler(name),
290                            prefix + name + obj.separator)
291                           for name in obj.getSubHandlerPrefixes() ])
292         return functions
293
294     xmlrpc_listMethods.signature = [['array']]
295
296     def xmlrpc_methodHelp(self, method):
297         """
298         Return a documentation string describing the use of the given method.
299         """
300         method = self._xmlrpc_parent.lookupProcedure(method)
301         return (getattr(method, 'help', None)
302                 or getattr(method, '__doc__', None) or '')
303
304     xmlrpc_methodHelp.signature = [['string', 'string']]
305
306     def xmlrpc_methodSignature(self, method):
307         """
308         Return a list of type signatures.
309
310         Each type signature is a list of the form [rtype, type1, type2, ...]
311         where rtype is the return type and typeN is the type of the Nth
312         argument. If no signature information is available, the empty
313         string is returned.
314         """
315         method = self._xmlrpc_parent.lookupProcedure(method)
316         return getattr(method, 'signature', None) or ''
317
318     xmlrpc_methodSignature.signature = [['array', 'string'],
319                                         ['string', 'string']]
320
321
322 def addIntrospection(xmlrpc):
323     """
324     Add Introspection support to an XMLRPC server.
325
326     @param parent: the XMLRPC server to add Introspection support to.
327     @type parent: L{XMLRPC}
328     """
329     xmlrpc.putSubHandler('system', XMLRPCIntrospection(xmlrpc))
330
331
332 class QueryProtocol(http.HTTPClient):
333
334     def connectionMade(self):
335         self._response = None
336         self.sendCommand('POST', self.factory.path)
337         self.sendHeader('User-Agent', 'Twisted/XMLRPClib')
338         self.sendHeader('Host', self.factory.host)
339         self.sendHeader('Content-type', 'text/xml')
340         self.sendHeader('Content-length', str(len(self.factory.payload)))
341         if self.factory.user:
342             auth = '%s:%s' % (self.factory.user, self.factory.password)
343             auth = auth.encode('base64').strip()
344             self.sendHeader('Authorization', 'Basic %s' % (auth,))
345         self.endHeaders()
346         self.transport.write(self.factory.payload)
347
348     def handleStatus(self, version, status, message):
349         if status != '200':
350             self.factory.badStatus(status, message)
351
352     def handleResponse(self, contents):
353         """
354         Handle the XML-RPC response received from the server.
355
356         Specifically, disconnect from the server and store the XML-RPC
357         response so that it can be properly handled when the disconnect is
358         finished.
359         """
360         self.transport.loseConnection()
361         self._response = contents
362
363     def connectionLost(self, reason):
364         """
365         The connection to the server has been lost.
366
367         If we have a full response from the server, then parse it and fired a
368         Deferred with the return value or C{Fault} that the server gave us.
369         """
370         http.HTTPClient.connectionLost(self, reason)
371         if self._response is not None:
372             response, self._response = self._response, None
373             self.factory.parseResponse(response)
374
375
376 payloadTemplate = """<?xml version="1.0"?>
377 <methodCall>
378 <methodName>%s</methodName>
379 %s
380 </methodCall>
381 """
382
383
384 class _QueryFactory(protocol.ClientFactory):
385     """
386     XML-RPC Client Factory
387
388     @ivar path: The path portion of the URL to which to post method calls.
389     @type path: C{str}
390
391     @ivar host: The value to use for the Host HTTP header.
392     @type host: C{str}
393
394     @ivar user: The username with which to authenticate with the server
395         when making calls.
396     @type user: C{str} or C{NoneType}
397
398     @ivar password: The password with which to authenticate with the server
399         when making calls.
400     @type password: C{str} or C{NoneType}
401
402     @ivar useDateTime: Accept datetime values as datetime.datetime objects.
403         also passed to the underlying xmlrpclib implementation.  Default to
404         False.  Requires Python >= 2.5.
405     @type useDateTime: C{bool}
406     """
407
408     deferred = None
409     protocol = QueryProtocol
410
411     def __init__(self, path, host, method, user=None, password=None,
412                  allowNone=False, args=(), canceller=None, useDateTime=False):
413         """
414         @param method: The name of the method to call.
415         @type method: C{str}
416
417         @param allowNone: allow the use of None values in parameters. It's
418             passed to the underlying xmlrpclib implementation. Default to False.
419         @type allowNone: C{bool} or C{NoneType}
420
421         @param args: the arguments to pass to the method.
422         @type args: C{tuple}
423
424         @param canceller: A 1-argument callable passed to the deferred as the
425             canceller callback.
426         @type canceller: callable or C{NoneType}
427         """
428         self.path, self.host = path, host
429         self.user, self.password = user, password
430         self.payload = payloadTemplate % (method,
431             xmlrpclib.dumps(args, allow_none=allowNone))
432         self.deferred = defer.Deferred(canceller)
433         self.useDateTime = useDateTime
434
435     def parseResponse(self, contents):
436         if not self.deferred:
437             return
438         try:
439             if self.useDateTime:
440                 response = xmlrpclib.loads(contents,
441                     use_datetime=True)[0][0]
442             else:
443                 # Maintain backwards compatibility with Python < 2.5
444                 response = xmlrpclib.loads(contents)[0][0]
445         except:
446             deferred, self.deferred = self.deferred, None
447             deferred.errback(failure.Failure())
448         else:
449             deferred, self.deferred = self.deferred, None
450             deferred.callback(response)
451
452     def clientConnectionLost(self, _, reason):
453         if self.deferred is not None:
454             deferred, self.deferred = self.deferred, None
455             deferred.errback(reason)
456
457     clientConnectionFailed = clientConnectionLost
458
459     def badStatus(self, status, message):
460         deferred, self.deferred = self.deferred, None
461         deferred.errback(ValueError(status, message))
462
463
464
465 class Proxy:
466     """
467     A Proxy for making remote XML-RPC calls.
468
469     Pass the URL of the remote XML-RPC server to the constructor.
470
471     Use proxy.callRemote('foobar', *args) to call remote method
472     'foobar' with *args.
473
474     @ivar user: The username with which to authenticate with the server
475         when making calls.  If specified, overrides any username information
476         embedded in C{url}.  If not specified, a value may be taken from
477         C{url} if present.
478     @type user: C{str} or C{NoneType}
479
480     @ivar password: The password with which to authenticate with the server
481         when making calls.  If specified, overrides any password information
482         embedded in C{url}.  If not specified, a value may be taken from
483         C{url} if present.
484     @type password: C{str} or C{NoneType}
485
486     @ivar allowNone: allow the use of None values in parameters. It's
487         passed to the underlying xmlrpclib implementation. Default to False.
488     @type allowNone: C{bool} or C{NoneType}
489
490     @ivar useDateTime: Accept datetime values as datetime.datetime objects.
491         also passed to the underlying xmlrpclib implementation.  Default to
492         False.  Requires Python >= 2.5.
493     @type useDateTime: C{bool}
494
495     @ivar connectTimeout: Number of seconds to wait before assuming the
496         connection has failed.
497     @type connectTimeout: C{float}
498
499     @ivar _reactor: the reactor used to create connections.
500     @type _reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
501
502     @ivar queryFactory: object returning a factory for XML-RPC protocol. Mainly
503         useful for tests.
504     """
505     queryFactory = _QueryFactory
506
507     def __init__(self, url, user=None, password=None, allowNone=False,
508                  useDateTime=False, connectTimeout=30.0, reactor=reactor):
509         """
510         @param url: The URL to which to post method calls.  Calls will be made
511             over SSL if the scheme is HTTPS.  If netloc contains username or
512             password information, these will be used to authenticate, as long as
513             the C{user} and C{password} arguments are not specified.
514         @type url: C{str}
515
516         """
517         scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
518         netlocParts = netloc.split('@')
519         if len(netlocParts) == 2:
520             userpass = netlocParts.pop(0).split(':')
521             self.user = userpass.pop(0)
522             try:
523                 self.password = userpass.pop(0)
524             except:
525                 self.password = None
526         else:
527             self.user = self.password = None
528         hostport = netlocParts[0].split(':')
529         self.host = hostport.pop(0)
530         try:
531             self.port = int(hostport.pop(0))
532         except:
533             self.port = None
534         self.path = path
535         if self.path in ['', None]:
536             self.path = '/'
537         self.secure = (scheme == 'https')
538         if user is not None:
539             self.user = user
540         if password is not None:
541             self.password = password
542         self.allowNone = allowNone
543         self.useDateTime = useDateTime
544         self.connectTimeout = connectTimeout
545         self._reactor = reactor
546
547
548     def __setattr__(self, name, value):
549         if name == "useDateTime" and value and sys.version_info[:2] < (2, 5):
550             raise RuntimeError("useDateTime requires Python 2.5 or later.")
551         self.__dict__[name] = value
552
553
554     def callRemote(self, method, *args):
555         """
556         Call remote XML-RPC C{method} with given arguments.
557
558         @return: a L{defer.Deferred} that will fire with the method response,
559             or a failure if the method failed. Generally, the failure type will
560             be L{Fault}, but you can also have an C{IndexError} on some buggy
561             servers giving empty responses.
562
563             If the deferred is cancelled before the request completes, the
564             connection is closed and the deferred will fire with a
565             L{defer.CancelledError}.
566         """
567         def cancel(d):
568             factory.deferred = None
569             connector.disconnect()
570         factory = self.queryFactory(
571             self.path, self.host, method, self.user,
572             self.password, self.allowNone, args, cancel, self.useDateTime)
573
574         if self.secure:
575             from twisted.internet import ssl
576             connector = self._reactor.connectSSL(
577                 self.host, self.port or 443,
578                 factory, ssl.ClientContextFactory(),
579                 timeout=self.connectTimeout)
580         else:
581             connector = self._reactor.connectTCP(
582                 self.host, self.port or 80, factory,
583                 timeout=self.connectTimeout)
584         return factory.deferred
585
586
587 __all__ = [
588     "XMLRPC", "Handler", "NoSuchFunction", "Proxy",
589
590     "Fault", "Binary", "Boolean", "DateTime"]