root / trunk / twisted / web2 / xmlrpc.py

Revision 24441, 7.3 kB (checked in by thijs, 1 year ago)

Merge maintainer-email-2438: Get rid of references to maintainer email addresses from code.

Author: thijs
Reviewer: exarkun
Fixes: #2438

Line 
1 # -*- test-case-name: twisted.web2.test.test_xmlrpc -*-
2 #
3 # Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4 # See LICENSE for details.
5
6
7 """
8 A generic resource for publishing objects via XML-RPC.
9
10 Maintainer: Itamar Shtull-Trauring
11 """
12
13 # System Imports
14 import xmlrpclib
15
16 # Sibling Imports
17 from twisted.web2 import resource, stream
18 from twisted.web2 import responsecode, http, http_headers
19 from twisted.internet import defer
20 from twisted.python import log, reflect
21
22 # Useful so people don't need to import xmlrpclib directly
23 Fault = xmlrpclib.Fault
24 Binary = xmlrpclib.Binary
25 Boolean = xmlrpclib.Boolean
26 DateTime = xmlrpclib.DateTime
27
28
29 class NoSuchFunction(Fault):
30     """There is no function by the given name."""
31     pass
32
33
34 class XMLRPC(resource.Resource):
35     """A resource that implements XML-RPC.
36     
37     You probably want to connect this to '/RPC2'.
38
39     Methods published can return XML-RPC serializable results, Faults,
40     Binary, Boolean, DateTime, Deferreds, or Handler instances.
41
42     By default methods beginning with 'xmlrpc_' are published.
43
44     Sub-handlers for prefixed methods (e.g., system.listMethods)
45     can be added with putSubHandler. By default, prefixes are
46     separated with a '.'. Override self.separator to change this.
47     """
48
49     # Error codes for Twisted, if they conflict with yours then
50     # modify them at runtime.
51     NOT_FOUND = 8001
52     FAILURE = 8002
53
54     separator = '.'
55
56     def __init__(self):
57         resource.Resource.__init__(self)
58         self.subHandlers = {}
59
60     def putSubHandler(self, prefix, handler):
61         self.subHandlers[prefix] = handler
62
63     def getSubHandler(self, prefix):
64         return self.subHandlers.get(prefix, None)
65
66     def getSubHandlerPrefixes(self):
67         return self.subHandlers.keys()
68
69     def render(self, request):
70         # For GET/HEAD: Return an error message
71         s=("<html><head><title>XML-RPC responder</title></head>"
72            "<body><h1>XML-RPC responder</h1>POST your XML-RPC here.</body></html>")
73        
74         return http.Response(responsecode.OK,
75             {'content-type': http_headers.MimeType('text', 'html')},
76             s)
77    
78     def http_POST(self, request):
79         parser, unmarshaller = xmlrpclib.getparser()
80         deferred = stream.readStream(request.stream, parser.feed)
81         deferred.addCallback(lambda x: self._cbDispatch(
82             request, parser, unmarshaller))
83         deferred.addErrback(self._ebRender)
84         deferred.addCallback(self._cbRender, request)
85         return deferred
86
87     def _cbDispatch(self, request, parser, unmarshaller):
88         parser.close()
89         args, functionPath = unmarshaller.close(), unmarshaller.getmethodname()
90
91         function = self.getFunction(functionPath)
92         return defer.maybeDeferred(function, request, *args)
93
94     def _cbRender(self, result, request):
95         if not isinstance(result, Fault):
96             result = (result,)
97         try:
98             s = xmlrpclib.dumps(result, methodresponse=1)
99         except:
100             f = Fault(self.FAILURE, "can't serialize output")
101             s = xmlrpclib.dumps(f, methodresponse=1)
102         return http.Response(responsecode.OK,
103             {'content-type': http_headers.MimeType('text', 'xml')},
104             s)
105
106     def _ebRender(self, failure):
107         if isinstance(failure.value, Fault):
108             return failure.value
109         log.err(failure)
110         return Fault(self.FAILURE, "error")
111
112     def getFunction(self, functionPath):
113         """Given a string, return a function, or raise NoSuchFunction.
114
115         This returned function will be called, and should return the result
116         of the call, a Deferred, or a Fault instance.
117
118         Override in subclasses if you want your own policy. The default
119         policy is that given functionPath 'foo', return the method at
120         self.xmlrpc_foo, i.e. getattr(self, "xmlrpc_" + functionPath).
121         If functionPath contains self.separator, the sub-handler for
122         the initial prefix is used to search for the remaining path.
123         """
124         if functionPath.find(self.separator) != -1:
125             prefix, functionPath = functionPath.split(self.separator, 1)
126             handler = self.getSubHandler(prefix)
127             if handler is None:
128                 raise NoSuchFunction(self.NOT_FOUND, "no such subHandler %s" % prefix)
129             return handler.getFunction(functionPath)
130
131         f = getattr(self, "xmlrpc_%s" % functionPath, None)
132         if not f:
133             raise NoSuchFunction(self.NOT_FOUND, "function %s not found" % functionPath)
134         elif not callable(f):
135             raise NoSuchFunction(self.NOT_FOUND, "function %s not callable" % functionPath)
136         else:
137             return f
138
139     def _listFunctions(self):
140         """Return a list of the names of all xmlrpc methods."""
141         return reflect.prefixedMethodNames(self.__class__, 'xmlrpc_')
142
143
144 class XMLRPCIntrospection(XMLRPC):
145     """Implement the XML-RPC Introspection API.
146
147     By default, the methodHelp method returns the 'help' method attribute,
148     if it exists, otherwise the __doc__ method attribute, if it exists,
149     otherwise the empty string.
150
151     To enable the methodSignature method, add a 'signature' method attribute
152     containing a list of lists. See methodSignature's documentation for the
153     format. Note the type strings should be XML-RPC types, not Python types.
154     """
155
156     def __init__(self, parent):
157         """Implement Introspection support for an XMLRPC server.
158
159         @param parent: the XMLRPC server to add Introspection support to.
160         """
161
162         XMLRPC.__init__(self)
163         self._xmlrpc_parent = parent
164
165     def xmlrpc_listMethods(self, request):
166         """Return a list of the method names implemented by this server."""
167         functions = []
168         todo = [(self._xmlrpc_parent, '')]
169         while todo:
170             obj, prefix = todo.pop(0)
171             functions.extend([prefix + name for name in obj._listFunctions()])
172             todo.extend([(obj.getSubHandler(name),
173                            prefix + name + obj.separator)
174                           for name in obj.getSubHandlerPrefixes()])
175         functions.sort()
176         return functions
177
178     xmlrpc_listMethods.signature = [['array']]
179
180     def xmlrpc_methodHelp(self, request, method):
181         """Return a documentation string describing the use of the given method.
182         """
183         method = self._xmlrpc_parent.getFunction(method)
184         return (getattr(method, 'help', None)
185                 or getattr(method, '__doc__', None) or '')
186
187     xmlrpc_methodHelp.signature = [['string', 'string']]
188
189     def xmlrpc_methodSignature(self, request, method):
190         """Return a list of type signatures.
191
192         Each type signature is a list of the form [rtype, type1, type2, ...]
193         where rtype is the return type and typeN is the type of the Nth
194         argument. If no signature information is available, the empty
195         string is returned.
196         """
197         method = self._xmlrpc_parent.getFunction(method)
198         return getattr(method, 'signature', None) or ''
199
200     xmlrpc_methodSignature.signature = [['array', 'string'],
201                                         ['string', 'string']]
202
203
204 def addIntrospection(xmlrpc):
205     """Add Introspection support to an XMLRPC server.
206
207     @param xmlrpc: The xmlrpc server to add Introspection support to.
208     """
209     xmlrpc.putSubHandler('system', XMLRPCIntrospection(xmlrpc))
210
211
212 __all__ = ["XMLRPC", "NoSuchFunction", "Fault"]
Note: See TracBrowser for help on using the browser.