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
109 def resolve_dotted_attribute(obj
, attr
, allow_dotted_names
=True):
110 """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
112 Resolves a dotted attribute name to an object. Raises
113 an AttributeError if any attribute in the chain starts with a '_'.
115 If the optional allow_dotted_names argument is false, dots are not
116 supported and this function operates similar to getattr(obj, attr).
119 if allow_dotted_names
:
120 attrs
= attr
.split('.')
125 if i
.startswith('_'):
126 raise AttributeError(
127 'attempt to access private attribute "%s"' % i
133 def list_public_methods(obj
):
134 """Returns a list of attribute strings, found in the specified
135 object, which represent callable attributes"""
137 return [member
for member
in dir(obj
)
138 if not member
.startswith('_') and
139 callable(getattr(obj
, member
))]
141 def remove_duplicates(lst
):
142 """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
144 Returns a copy of a list without duplicates. Every list
145 item must be hashable and the order of the items in the
146 resulting list is not defined.
154 class SimpleXMLRPCDispatcher
:
155 """Mix-in class that dispatches XML-RPC requests.
157 This class is used to register XML-RPC method handlers
158 and then to dispatch them. There should never be any
159 reason to instantiate this class directly.
162 def __init__(self
, allow_none
, encoding
):
165 self
.allow_none
= allow_none
166 self
.encoding
= encoding
168 def register_instance(self
, instance
, allow_dotted_names
=False):
169 """Registers an instance to respond to XML-RPC requests.
171 Only one instance can be installed at a time.
173 If the registered instance has a _dispatch method then that
174 method will be called with the name of the XML-RPC method and
175 its parameters as a tuple
176 e.g. instance._dispatch('add',(2,3))
178 If the registered instance does not have a _dispatch method
179 then the instance will be searched to find a matching method
180 and, if found, will be called. Methods beginning with an '_'
181 are considered private and will not be called by
184 If a registered function matches a XML-RPC request, then it
185 will be called instead of the registered instance.
187 If the optional allow_dotted_names argument is true and the
188 instance does not have a _dispatch method, method names
189 containing dots are supported and resolved, as long as none of
190 the name segments start with an '_'.
192 *** SECURITY WARNING: ***
194 Enabling the allow_dotted_names options allows intruders
195 to access your module's global variables and may allow
196 intruders to execute arbitrary code on your machine. Only
197 use this option on a secure, closed network.
201 self
.instance
= instance
202 self
.allow_dotted_names
= allow_dotted_names
204 def register_function(self
, function
, name
= None):
205 """Registers a function to respond to XML-RPC requests.
207 The optional name argument can be used to set a Unicode name
212 name
= function
.__name
__
213 self
.funcs
[name
] = function
215 def register_introspection_functions(self
):
216 """Registers the XML-RPC introspection methods in the system
219 see http://xmlrpc.usefulinc.com/doc/reserved.html
222 self
.funcs
.update({'system.listMethods' : self
.system_listMethods
,
223 'system.methodSignature' : self
.system_methodSignature
,
224 'system.methodHelp' : self
.system_methodHelp
})
226 def register_multicall_functions(self
):
227 """Registers the XML-RPC multicall method in the system
230 see http://www.xmlrpc.com/discuss/msgReader$1208"""
232 self
.funcs
.update({'system.multicall' : self
.system_multicall
})
234 def _marshaled_dispatch(self
, data
, dispatch_method
= None):
235 """Dispatches an XML-RPC method from marshalled (XML) data.
237 XML-RPC methods are dispatched from the marshalled (XML) data
238 using the _dispatch method and the result is returned as
239 marshalled data. For backwards compatibility, a dispatch
240 function can be provided as an argument (see comment in
241 SimpleXMLRPCRequestHandler.do_POST) but overriding the
242 existing method through subclassing is the prefered means
243 of changing method dispatch behavior.
246 params
, method
= xmlrpclib
.loads(data
)
250 if dispatch_method
is not None:
251 response
= dispatch_method(method
, params
)
253 response
= self
._dispatch
(method
, params
)
254 # wrap response in a singleton tuple
255 response
= (response
,)
256 response
= xmlrpclib
.dumps(response
, methodresponse
=1,
257 allow_none
=self
.allow_none
, encoding
=self
.encoding
)
259 response
= xmlrpclib
.dumps(fault
, allow_none
=self
.allow_none
,
260 encoding
=self
.encoding
)
262 # report exception back to server
263 response
= xmlrpclib
.dumps(
264 xmlrpclib
.Fault(1, "%s:%s" % (sys
.exc_type
, sys
.exc_value
)),
265 encoding
=self
.encoding
, allow_none
=self
.allow_none
,
270 def system_listMethods(self
):
271 """system.listMethods() => ['add', 'subtract', 'multiple']
273 Returns a list of the methods supported by the server."""
275 methods
= self
.funcs
.keys()
276 if self
.instance
is not None:
277 # Instance can implement _listMethod to return a list of
279 if hasattr(self
.instance
, '_listMethods'):
280 methods
= remove_duplicates(
281 methods
+ self
.instance
._listMethods
()
283 # if the instance has a _dispatch method then we
284 # don't have enough information to provide a list
286 elif not hasattr(self
.instance
, '_dispatch'):
287 methods
= remove_duplicates(
288 methods
+ list_public_methods(self
.instance
)
293 def system_methodSignature(self
, method_name
):
294 """system.methodSignature('add') => [double, int, int]
296 Returns a list describing the signature of the method. In the
297 above example, the add method takes two integers as arguments
298 and returns a double result.
300 This server does NOT support system.methodSignature."""
302 # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
304 return 'signatures not supported'
306 def system_methodHelp(self
, method_name
):
307 """system.methodHelp('add') => "Adds two integers together"
309 Returns a string containing documentation for the specified method."""
312 if self
.funcs
.has_key(method_name
):
313 method
= self
.funcs
[method_name
]
314 elif self
.instance
is not None:
315 # Instance can implement _methodHelp to return help for a method
316 if hasattr(self
.instance
, '_methodHelp'):
317 return self
.instance
._methodHelp
(method_name
)
318 # if the instance has a _dispatch method then we
319 # don't have enough information to provide help
320 elif not hasattr(self
.instance
, '_dispatch'):
322 method
= resolve_dotted_attribute(
325 self
.allow_dotted_names
327 except AttributeError:
330 # Note that we aren't checking that the method actually
331 # be a callable object of some kind
336 return pydoc
.getdoc(method
)
338 def system_multicall(self
, call_list
):
339 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
342 Allows the caller to package multiple XML-RPC calls into a single
345 See http://www.xmlrpc.com/discuss/msgReader$1208
349 for call
in call_list
:
350 method_name
= call
['methodName']
351 params
= call
['params']
354 # XXX A marshalling error in any response will fail the entire
355 # multicall. If someone cares they should fix this.
356 results
.append([self
._dispatch
(method_name
, params
)])
359 {'faultCode' : fault
.faultCode
,
360 'faultString' : fault
.faultString
}
365 'faultString' : "%s:%s" % (sys
.exc_type
, sys
.exc_value
)}
369 def _dispatch(self
, method
, params
):
370 """Dispatches the XML-RPC method.
372 XML-RPC calls are forwarded to a registered function that
373 matches the called XML-RPC method name. If no such function
374 exists then the call is forwarded to the registered instance,
377 If the registered instance has a _dispatch method then that
378 method will be called with the name of the XML-RPC method and
379 its parameters as a tuple
380 e.g. instance._dispatch('add',(2,3))
382 If the registered instance does not have a _dispatch method
383 then the instance will be searched to find a matching method
384 and, if found, will be called.
386 Methods beginning with an '_' are considered private and will
392 # check to see if a matching function has been registered
393 func
= self
.funcs
[method
]
395 if self
.instance
is not None:
396 # check for a _dispatch method
397 if hasattr(self
.instance
, '_dispatch'):
398 return self
.instance
._dispatch
(method
, params
)
400 # call instance method directly
402 func
= resolve_dotted_attribute(
405 self
.allow_dotted_names
407 except AttributeError:
413 raise Exception('method "%s" is not supported' % method
)
415 class SimpleXMLRPCRequestHandler(BaseHTTPServer
.BaseHTTPRequestHandler
):
416 """Simple XML-RPC request handler class.
418 Handles all HTTP POST requests and attempts to decode them as
423 """Handles the HTTP POST request.
425 Attempts to interpret all HTTP POST requests as XML-RPC calls,
426 which are forwarded to the server's _dispatch method for handling.
430 # Get arguments by reading body of request.
431 # We read this in chunks to avoid straining
432 # socket.read(); around the 10 or 15Mb mark, some platforms
433 # begin to have problems (bug #792570).
434 max_chunk_size
= 10*1024*1024
435 size_remaining
= int(self
.headers
["content-length"])
437 while size_remaining
:
438 chunk_size
= min(size_remaining
, max_chunk_size
)
439 L
.append(self
.rfile
.read(chunk_size
))
440 size_remaining
-= len(L
[-1])
443 # In previous versions of SimpleXMLRPCServer, _dispatch
444 # could be overridden in this class, instead of in
445 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
446 # check to see if a subclass implements _dispatch and dispatch
447 # using that method if present.
448 response
= self
.server
._marshaled
_dispatch
(
449 data
, getattr(self
, '_dispatch', None)
451 except: # This should only happen if the module is buggy
452 # internal error, report as HTTP server error
453 self
.send_response(500)
456 # got a valid XML RPC response
457 self
.send_response(200)
458 self
.send_header("Content-type", "text/xml")
459 self
.send_header("Content-length", str(len(response
)))
461 self
.wfile
.write(response
)
463 # shut down the connection
465 self
.connection
.shutdown(1)
467 def log_request(self
, code
='-', size
='-'):
468 """Selectively log an accepted request."""
470 if self
.server
.logRequests
:
471 BaseHTTPServer
.BaseHTTPRequestHandler
.log_request(self
, code
, size
)
473 class SimpleXMLRPCServer(SocketServer
.TCPServer
,
474 SimpleXMLRPCDispatcher
):
475 """Simple XML-RPC server.
477 Simple XML-RPC server that allows functions and a single instance
478 to be installed to handle requests. The default implementation
479 attempts to dispatch XML-RPC calls to the functions or instance
480 installed in the server. Override the _dispatch method inhereted
481 from SimpleXMLRPCDispatcher to change this behavior.
484 allow_reuse_address
= True
486 def __init__(self
, addr
, requestHandler
=SimpleXMLRPCRequestHandler
,
487 logRequests
=True, allow_none
=False, encoding
=None):
488 self
.logRequests
= logRequests
490 SimpleXMLRPCDispatcher
.__init
__(self
, allow_none
, encoding
)
491 SocketServer
.TCPServer
.__init
__(self
, addr
, requestHandler
)
493 # [Bug #1222790] If possible, set close-on-exec flag; if a
494 # method spawns a subprocess, the subprocess shouldn't have
495 # the listening socket open.
496 if hasattr(fcntl
, 'FD_CLOEXEC'):
497 flags
= fcntl
.fcntl(self
.fileno(), fcntl
.F_GETFD
)
498 flags |
= fcntl
.FD_CLOEXEC
499 fcntl
.fcntl(self
.fileno(), fcntl
.F_SETFD
, flags
)
501 class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher
):
502 """Simple handler for XML-RPC data passed through CGI."""
504 def __init__(self
, allow_none
=False, encoding
=None):
505 SimpleXMLRPCDispatcher
.__init
__(self
, allow_none
, encoding
)
507 def handle_xmlrpc(self
, request_text
):
508 """Handle a single XML-RPC request"""
510 response
= self
._marshaled
_dispatch
(request_text
)
512 print 'Content-Type: text/xml'
513 print 'Content-Length: %d' % len(response
)
515 sys
.stdout
.write(response
)
517 def handle_get(self
):
518 """Handle a single HTTP GET request.
520 Default implementation indicates an error because
521 XML-RPC uses the POST method.
526 BaseHTTPServer
.BaseHTTPRequestHandler
.responses
[code
]
528 response
= BaseHTTPServer
.DEFAULT_ERROR_MESSAGE
% \
534 print 'Status: %d %s' % (code
, message
)
535 print 'Content-Type: text/html'
536 print 'Content-Length: %d' % len(response
)
538 sys
.stdout
.write(response
)
540 def handle_request(self
, request_text
= None):
541 """Handle a single XML-RPC request passed through a CGI post method.
543 If no XML data is given then it is read from stdin. The resulting
544 XML-RPC response is printed to stdout along with the correct HTTP
548 if request_text
is None and \
549 os
.environ
.get('REQUEST_METHOD', None) == 'GET':
552 # POST data is normally available through stdin
553 if request_text
is None:
554 request_text
= sys
.stdin
.read()
556 self
.handle_xmlrpc(request_text
)
558 if __name__
== '__main__':
559 server
= SimpleXMLRPCServer(("localhost", 8000))
560 server
.register_function(pow)
561 server
.register_function(lambda x
,y
: x
+y
, 'add')
562 server
.serve_forever()