1 """Simple XML-RPC Server.
3 This module can be used to create simple XML-RPC servers
4 by creating a server and either installing functions, a
5 class instance, or by extending the SimpleXMLRPCServer
8 It can also be used to handle XML-RPC requests in a CGI
9 environment using CGIXMLRPCRequestHandler.
11 A list of possible usage patterns follows:
15 server = SimpleXMLRPCServer(("localhost", 8000))
16 server.register_function(pow)
17 server.register_function(lambda x,y: x+y, 'add')
18 server.serve_forever()
20 2. Install an instance:
24 # make all of the string functions available through
28 def _listMethods(self):
29 # implement this method so that system.listMethods
30 # knows to advertise the strings methods
31 return list_public_methods(self) + \
32 ['string.' + method for method in list_public_methods(self.string)]
33 def pow(self, x, y): return pow(x, y)
34 def add(self, x, y) : return x + y
36 server = SimpleXMLRPCServer(("localhost", 8000))
37 server.register_introspection_functions()
38 server.register_instance(MyFuncs())
39 server.serve_forever()
41 3. Install an instance with custom dispatch method:
44 def _listMethods(self):
45 # this method must be present for system.listMethods
48 def _methodHelp(self, method):
49 # this method must be present for system.methodHelp
52 return "add(2,3) => 5"
54 return "pow(x, y[, z]) => number"
56 # By convention, return empty
57 # string if no help is available
59 def _dispatch(self, method, params):
63 return params[0] + params[1]
67 server = SimpleXMLRPCServer(("localhost", 8000))
68 server.register_introspection_functions()
69 server.register_instance(Math())
70 server.serve_forever()
72 4. Subclass SimpleXMLRPCServer:
74 class MathServer(SimpleXMLRPCServer):
75 def _dispatch(self, method, params):
77 # We are forcing the 'export_' prefix on methods that are
78 # callable through XML-RPC to prevent potential security
80 func = getattr(self, 'export_' + method)
81 except AttributeError:
82 raise Exception('method "%s" is not supported' % method)
86 def export_add(self, x, y):
89 server = MathServer(("localhost", 8000))
90 server.serve_forever()
94 server = CGIXMLRPCRequestHandler()
95 server.register_function(pow)
96 server.handle_request()
99 # Written by Brian Quinlan (brian@sweetapp.com).
100 # Based on code written by Fredrik Lundh.
103 from xmlrpclib
import Fault
105 import BaseHTTPServer
110 def resolve_dotted_attribute(obj
, attr
):
111 """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
113 Resolves a dotted attribute name to an object. Raises
114 an AttributeError if any attribute in the chain starts with a '_'.
117 for i
in attr
.split('.'):
118 if i
.startswith('_'):
119 raise AttributeError(
120 'attempt to access private attribute "%s"' % i
126 def list_public_methods(obj
):
127 """Returns a list of attribute strings, found in the specified
128 object, which represent callable attributes"""
130 return [member
for member
in dir(obj
)
131 if not member
.startswith('_') and
132 callable(getattr(obj
, member
))]
134 def remove_duplicates(lst
):
135 """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
137 Returns a copy of a list without duplicates. Every list
138 item must be hashable and the order of the items in the
139 resulting list is not defined.
147 class SimpleXMLRPCDispatcher
:
148 """Mix-in class that dispatches XML-RPC requests.
150 This class is used to register XML-RPC method handlers
151 and then to dispatch them. There should never be any
152 reason to instantiate this class directly.
159 def register_instance(self
, instance
):
160 """Registers an instance to respond to XML-RPC requests.
162 Only one instance can be installed at a time.
164 If the registered instance has a _dispatch method then that
165 method will be called with the name of the XML-RPC method and
166 it's parameters as a tuple
167 e.g. instance._dispatch('add',(2,3))
169 If the registered instance does not have a _dispatch method
170 then the instance will be searched to find a matching method
171 and, if found, will be called. Methods beginning with an '_'
172 are considered private and will not be called by
175 If a registered function matches a XML-RPC request, then it
176 will be called instead of the registered instance.
179 self
.instance
= instance
181 def register_function(self
, function
, name
= None):
182 """Registers a function to respond to XML-RPC requests.
184 The optional name argument can be used to set a Unicode name
189 name
= function
.__name
__
190 self
.funcs
[name
] = function
192 def register_introspection_functions(self
):
193 """Registers the XML-RPC introspection methods in the system
196 see http://xmlrpc.usefulinc.com/doc/reserved.html
199 self
.funcs
.update({'system.listMethods' : self
.system_listMethods
,
200 'system.methodSignature' : self
.system_methodSignature
,
201 'system.methodHelp' : self
.system_methodHelp
})
203 def register_multicall_functions(self
):
204 """Registers the XML-RPC multicall method in the system
207 see http://www.xmlrpc.com/discuss/msgReader$1208"""
209 self
.funcs
.update({'system.multicall' : self
.system_multicall
})
211 def _marshaled_dispatch(self
, data
, dispatch_method
= None):
212 """Dispatches an XML-RPC method from marshalled (XML) data.
214 XML-RPC methods are dispatched from the marshalled (XML) data
215 using the _dispatch method and the result is returned as
216 marshalled data. For backwards compatibility, a dispatch
217 function can be provided as an argument (see comment in
218 SimpleXMLRPCRequestHandler.do_POST) but overriding the
219 existing method through subclassing is the prefered means
220 of changing method dispatch behavior.
223 params
, method
= xmlrpclib
.loads(data
)
227 if dispatch_method
is not None:
228 response
= dispatch_method(method
, params
)
230 response
= self
._dispatch
(method
, params
)
231 # wrap response in a singleton tuple
232 response
= (response
,)
233 response
= xmlrpclib
.dumps(response
, methodresponse
=1)
235 response
= xmlrpclib
.dumps(fault
)
237 # report exception back to server
238 response
= xmlrpclib
.dumps(
239 xmlrpclib
.Fault(1, "%s:%s" % (sys
.exc_type
, sys
.exc_value
))
244 def system_listMethods(self
):
245 """system.listMethods() => ['add', 'subtract', 'multiple']
247 Returns a list of the methods supported by the server."""
249 methods
= self
.funcs
.keys()
250 if self
.instance
is not None:
251 # Instance can implement _listMethod to return a list of
253 if hasattr(self
.instance
, '_listMethods'):
254 methods
= remove_duplicates(
255 methods
+ self
.instance
._listMethods
()
257 # if the instance has a _dispatch method then we
258 # don't have enough information to provide a list
260 elif not hasattr(self
.instance
, '_dispatch'):
261 methods
= remove_duplicates(
262 methods
+ list_public_methods(self
.instance
)
267 def system_methodSignature(self
, method_name
):
268 """system.methodSignature('add') => [double, int, int]
270 Returns a list describing the signiture of the method. In the
271 above example, the add method takes two integers as arguments
272 and returns a double result.
274 This server does NOT support system.methodSignature."""
276 # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
278 return 'signatures not supported'
280 def system_methodHelp(self
, method_name
):
281 """system.methodHelp('add') => "Adds two integers together"
283 Returns a string containing documentation for the specified method."""
286 if self
.funcs
.has_key(method_name
):
287 method
= self
.funcs
[method_name
]
288 elif self
.instance
is not None:
289 # Instance can implement _methodHelp to return help for a method
290 if hasattr(self
.instance
, '_methodHelp'):
291 return self
.instance
._methodHelp
(method_name
)
292 # if the instance has a _dispatch method then we
293 # don't have enough information to provide help
294 elif not hasattr(self
.instance
, '_dispatch'):
296 method
= resolve_dotted_attribute(
300 except AttributeError:
303 # Note that we aren't checking that the method actually
304 # be a callable object of some kind
308 return pydoc
.getdoc(method
)
310 def system_multicall(self
, call_list
):
311 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
314 Allows the caller to package multiple XML-RPC calls into a single
317 See http://www.xmlrpc.com/discuss/msgReader$1208
321 for call
in call_list
:
322 method_name
= call
['methodName']
323 params
= call
['params']
326 # XXX A marshalling error in any response will fail the entire
327 # multicall. If someone cares they should fix this.
328 results
.append([self
._dispatch
(method_name
, params
)])
331 {'faultCode' : fault
.faultCode
,
332 'faultString' : fault
.faultString
}
337 'faultString' : "%s:%s" % (sys
.exc_type
, sys
.exc_value
)}
341 def _dispatch(self
, method
, params
):
342 """Dispatches the XML-RPC method.
344 XML-RPC calls are forwarded to a registered function that
345 matches the called XML-RPC method name. If no such function
346 exists then the call is forwarded to the registered instance,
349 If the registered instance has a _dispatch method then that
350 method will be called with the name of the XML-RPC method and
351 it's parameters as a tuple
352 e.g. instance._dispatch('add',(2,3))
354 If the registered instance does not have a _dispatch method
355 then the instance will be searched to find a matching method
356 and, if found, will be called.
358 Methods beginning with an '_' are considered private and will
364 # check to see if a matching function has been registered
365 func
= self
.funcs
[method
]
367 if self
.instance
is not None:
368 # check for a _dispatch method
369 if hasattr(self
.instance
, '_dispatch'):
370 return self
.instance
._dispatch
(method
, params
)
372 # call instance method directly
374 func
= resolve_dotted_attribute(
378 except AttributeError:
384 raise Exception('method "%s" is not supported' % method
)
386 class SimpleXMLRPCRequestHandler(BaseHTTPServer
.BaseHTTPRequestHandler
):
387 """Simple XML-RPC request handler class.
389 Handles all HTTP POST requests and attempts to decode them as
394 """Handles the HTTP POST request.
396 Attempts to interpret all HTTP POST requests as XML-RPC calls,
397 which are forwarded to the server's _dispatch method for handling.
402 data
= self
.rfile
.read(int(self
.headers
["content-length"]))
403 # In previous versions of SimpleXMLRPCServer, _dispatch
404 # could be overridden in this class, instead of in
405 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
406 # check to see if a subclass implements _dispatch and dispatch
407 # using that method if present.
408 response
= self
.server
._marshaled
_dispatch
(
409 data
, getattr(self
, '_dispatch', None)
411 except: # This should only happen if the module is buggy
412 # internal error, report as HTTP server error
413 self
.send_response(500)
416 # got a valid XML RPC response
417 self
.send_response(200)
418 self
.send_header("Content-type", "text/xml")
419 self
.send_header("Content-length", str(len(response
)))
421 self
.wfile
.write(response
)
423 # shut down the connection
425 self
.connection
.shutdown(1)
427 def log_request(self
, code
='-', size
='-'):
428 """Selectively log an accepted request."""
430 if self
.server
.logRequests
:
431 BaseHTTPServer
.BaseHTTPRequestHandler
.log_request(self
, code
, size
)
433 class SimpleXMLRPCServer(SocketServer
.TCPServer
,
434 SimpleXMLRPCDispatcher
):
435 """Simple XML-RPC server.
437 Simple XML-RPC server that allows functions and a single instance
438 to be installed to handle requests. The default implementation
439 attempts to dispatch XML-RPC calls to the functions or instance
440 installed in the server. Override the _dispatch method inhereted
441 from SimpleXMLRPCDispatcher to change this behavior.
444 def __init__(self
, addr
, requestHandler
=SimpleXMLRPCRequestHandler
,
446 self
.logRequests
= logRequests
448 SimpleXMLRPCDispatcher
.__init
__(self
)
449 SocketServer
.TCPServer
.__init
__(self
, addr
, requestHandler
)
451 class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher
):
452 """Simple handler for XML-RPC data passed through CGI."""
455 SimpleXMLRPCDispatcher
.__init
__(self
)
457 def handle_xmlrpc(self
, request_text
):
458 """Handle a single XML-RPC request"""
460 response
= self
._marshaled
_dispatch
(request_text
)
462 print 'Content-Type: text/xml'
463 print 'Content-Length: %d' % len(response
)
467 def handle_get(self
):
468 """Handle a single HTTP GET request.
470 Default implementation indicates an error because
471 XML-RPC uses the POST method.
476 BaseHTTPServer
.BaseHTTPRequestHandler
.responses
[code
]
478 response
= BaseHTTPServer
.DEFAULT_ERROR_MESSAGE
% \
484 print 'Status: %d %s' % (code
, message
)
485 print 'Content-Type: text/html'
486 print 'Content-Length: %d' % len(response
)
490 def handle_request(self
, request_text
= None):
491 """Handle a single XML-RPC request passed through a CGI post method.
493 If no XML data is given then it is read from stdin. The resulting
494 XML-RPC response is printed to stdout along with the correct HTTP
498 if request_text
is None and \
499 os
.environ
.get('REQUEST_METHOD', None) == 'GET':
502 # POST data is normally available through stdin
503 if request_text
is None:
504 request_text
= sys
.stdin
.read()
506 self
.handle_xmlrpc(request_text
)
508 if __name__
== '__main__':
509 server
= SimpleXMLRPCServer(("localhost", 8000))
510 server
.register_function(pow)
511 server
.register_function(lambda x
,y
: x
+y
, 'add')
512 server
.serve_forever()