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
115 def resolve_dotted_attribute(obj
, attr
, allow_dotted_names
=True):
116 """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
118 Resolves a dotted attribute name to an object. Raises
119 an AttributeError if any attribute in the chain starts with a '_'.
121 If the optional allow_dotted_names argument is false, dots are not
122 supported and this function operates similar to getattr(obj, attr).
125 if allow_dotted_names
:
126 attrs
= attr
.split('.')
131 if i
.startswith('_'):
132 raise AttributeError(
133 'attempt to access private attribute "%s"' % i
139 def list_public_methods(obj
):
140 """Returns a list of attribute strings, found in the specified
141 object, which represent callable attributes"""
143 return [member
for member
in dir(obj
)
144 if not member
.startswith('_') and
145 hasattr(getattr(obj
, member
), '__call__')]
147 def remove_duplicates(lst
):
148 """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
150 Returns a copy of a list without duplicates. Every list
151 item must be hashable and the order of the items in the
152 resulting list is not defined.
160 class SimpleXMLRPCDispatcher
:
161 """Mix-in class that dispatches XML-RPC requests.
163 This class is used to register XML-RPC method handlers
164 and then to dispatch them. This class doesn't need to be
165 instanced directly when used by SimpleXMLRPCServer but it
166 can be instanced when used by the MultiPathXMLRPCServer
169 def __init__(self
, allow_none
=False, encoding
=None):
172 self
.allow_none
= allow_none
173 self
.encoding
= encoding
175 def register_instance(self
, instance
, allow_dotted_names
=False):
176 """Registers an instance to respond to XML-RPC requests.
178 Only one instance can be installed at a time.
180 If the registered instance has a _dispatch method then that
181 method will be called with the name of the XML-RPC method and
182 its parameters as a tuple
183 e.g. instance._dispatch('add',(2,3))
185 If the registered instance does not have a _dispatch method
186 then the instance will be searched to find a matching method
187 and, if found, will be called. Methods beginning with an '_'
188 are considered private and will not be called by
191 If a registered function matches a XML-RPC request, then it
192 will be called instead of the registered instance.
194 If the optional allow_dotted_names argument is true and the
195 instance does not have a _dispatch method, method names
196 containing dots are supported and resolved, as long as none of
197 the name segments start with an '_'.
199 *** SECURITY WARNING: ***
201 Enabling the allow_dotted_names options allows intruders
202 to access your module's global variables and may allow
203 intruders to execute arbitrary code on your machine. Only
204 use this option on a secure, closed network.
208 self
.instance
= instance
209 self
.allow_dotted_names
= allow_dotted_names
211 def register_function(self
, function
, name
= None):
212 """Registers a function to respond to XML-RPC requests.
214 The optional name argument can be used to set a Unicode name
219 name
= function
.__name
__
220 self
.funcs
[name
] = function
222 def register_introspection_functions(self
):
223 """Registers the XML-RPC introspection methods in the system
226 see http://xmlrpc.usefulinc.com/doc/reserved.html
229 self
.funcs
.update({'system.listMethods' : self
.system_listMethods
,
230 'system.methodSignature' : self
.system_methodSignature
,
231 'system.methodHelp' : self
.system_methodHelp
})
233 def register_multicall_functions(self
):
234 """Registers the XML-RPC multicall method in the system
237 see http://www.xmlrpc.com/discuss/msgReader$1208"""
239 self
.funcs
.update({'system.multicall' : self
.system_multicall
})
241 def _marshaled_dispatch(self
, data
, dispatch_method
= None, path
= None):
242 """Dispatches an XML-RPC method from marshalled (XML) data.
244 XML-RPC methods are dispatched from the marshalled (XML) data
245 using the _dispatch method and the result is returned as
246 marshalled data. For backwards compatibility, a dispatch
247 function can be provided as an argument (see comment in
248 SimpleXMLRPCRequestHandler.do_POST) but overriding the
249 existing method through subclassing is the prefered means
250 of changing method dispatch behavior.
254 params
, method
= xmlrpclib
.loads(data
)
257 if dispatch_method
is not None:
258 response
= dispatch_method(method
, params
)
260 response
= self
._dispatch
(method
, params
)
261 # wrap response in a singleton tuple
262 response
= (response
,)
263 response
= xmlrpclib
.dumps(response
, methodresponse
=1,
264 allow_none
=self
.allow_none
, encoding
=self
.encoding
)
266 response
= xmlrpclib
.dumps(fault
, allow_none
=self
.allow_none
,
267 encoding
=self
.encoding
)
269 # report exception back to server
270 exc_type
, exc_value
, exc_tb
= sys
.exc_info()
271 response
= xmlrpclib
.dumps(
272 xmlrpclib
.Fault(1, "%s:%s" % (exc_type
, exc_value
)),
273 encoding
=self
.encoding
, allow_none
=self
.allow_none
,
278 def system_listMethods(self
):
279 """system.listMethods() => ['add', 'subtract', 'multiple']
281 Returns a list of the methods supported by the server."""
283 methods
= self
.funcs
.keys()
284 if self
.instance
is not None:
285 # Instance can implement _listMethod to return a list of
287 if hasattr(self
.instance
, '_listMethods'):
288 methods
= remove_duplicates(
289 methods
+ self
.instance
._listMethods
()
291 # if the instance has a _dispatch method then we
292 # don't have enough information to provide a list
294 elif not hasattr(self
.instance
, '_dispatch'):
295 methods
= remove_duplicates(
296 methods
+ list_public_methods(self
.instance
)
301 def system_methodSignature(self
, method_name
):
302 """system.methodSignature('add') => [double, int, int]
304 Returns a list describing the signature of the method. In the
305 above example, the add method takes two integers as arguments
306 and returns a double result.
308 This server does NOT support system.methodSignature."""
310 # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
312 return 'signatures not supported'
314 def system_methodHelp(self
, method_name
):
315 """system.methodHelp('add') => "Adds two integers together"
317 Returns a string containing documentation for the specified method."""
320 if method_name
in self
.funcs
:
321 method
= self
.funcs
[method_name
]
322 elif self
.instance
is not None:
323 # Instance can implement _methodHelp to return help for a method
324 if hasattr(self
.instance
, '_methodHelp'):
325 return self
.instance
._methodHelp
(method_name
)
326 # if the instance has a _dispatch method then we
327 # don't have enough information to provide help
328 elif not hasattr(self
.instance
, '_dispatch'):
330 method
= resolve_dotted_attribute(
333 self
.allow_dotted_names
335 except AttributeError:
338 # Note that we aren't checking that the method actually
339 # be a callable object of some kind
344 return pydoc
.getdoc(method
)
346 def system_multicall(self
, call_list
):
347 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
350 Allows the caller to package multiple XML-RPC calls into a single
353 See http://www.xmlrpc.com/discuss/msgReader$1208
357 for call
in call_list
:
358 method_name
= call
['methodName']
359 params
= call
['params']
362 # XXX A marshalling error in any response will fail the entire
363 # multicall. If someone cares they should fix this.
364 results
.append([self
._dispatch
(method_name
, params
)])
367 {'faultCode' : fault
.faultCode
,
368 'faultString' : fault
.faultString
}
371 exc_type
, exc_value
, exc_tb
= sys
.exc_info()
374 'faultString' : "%s:%s" % (exc_type
, exc_value
)}
378 def _dispatch(self
, method
, params
):
379 """Dispatches the XML-RPC method.
381 XML-RPC calls are forwarded to a registered function that
382 matches the called XML-RPC method name. If no such function
383 exists then the call is forwarded to the registered instance,
386 If the registered instance has a _dispatch method then that
387 method will be called with the name of the XML-RPC method and
388 its parameters as a tuple
389 e.g. instance._dispatch('add',(2,3))
391 If the registered instance does not have a _dispatch method
392 then the instance will be searched to find a matching method
393 and, if found, will be called.
395 Methods beginning with an '_' are considered private and will
401 # check to see if a matching function has been registered
402 func
= self
.funcs
[method
]
404 if self
.instance
is not None:
405 # check for a _dispatch method
406 if hasattr(self
.instance
, '_dispatch'):
407 return self
.instance
._dispatch
(method
, params
)
409 # call instance method directly
411 func
= resolve_dotted_attribute(
414 self
.allow_dotted_names
416 except AttributeError:
422 raise Exception('method "%s" is not supported' % method
)
424 class SimpleXMLRPCRequestHandler(BaseHTTPServer
.BaseHTTPRequestHandler
):
425 """Simple XML-RPC request handler class.
427 Handles all HTTP POST requests and attempts to decode them as
431 # Class attribute listing the accessible path components;
432 # paths not on this list will result in a 404 error.
433 rpc_paths
= ('/', '/RPC2')
435 #if not None, encode responses larger than this, if possible
436 encode_threshold
= 1400 #a common MTU
438 #Override form StreamRequestHandler: full buffering of output
441 disable_nagle_algorithm
= True
443 # a re to match a gzip Accept-Encoding
444 aepattern
= re
.compile(r
"""
445 \s* ([^\s;]+) \s* #content-coding
446 (;\s* q \s*=\s* ([0-9\.]+))? #q
447 """, re
.VERBOSE | re
.IGNORECASE
)
449 def accept_encodings(self
):
451 ae
= self
.headers
.get("Accept-Encoding", "")
452 for e
in ae
.split(","):
453 match
= self
.aepattern
.match(e
)
456 v
= float(v
) if v
else 1.0
457 r
[match
.group(1)] = v
460 def is_rpc_path_valid(self
):
462 return self
.path
in self
.rpc_paths
464 # If .rpc_paths is empty, just assume all paths are legal
468 """Handles the HTTP POST request.
470 Attempts to interpret all HTTP POST requests as XML-RPC calls,
471 which are forwarded to the server's _dispatch method for handling.
474 # Check that the path is legal
475 if not self
.is_rpc_path_valid():
480 # Get arguments by reading body of request.
481 # We read this in chunks to avoid straining
482 # socket.read(); around the 10 or 15Mb mark, some platforms
483 # begin to have problems (bug #792570).
484 max_chunk_size
= 10*1024*1024
485 size_remaining
= int(self
.headers
["content-length"])
487 while size_remaining
:
488 chunk_size
= min(size_remaining
, max_chunk_size
)
489 L
.append(self
.rfile
.read(chunk_size
))
490 size_remaining
-= len(L
[-1])
493 data
= self
.decode_request_content(data
)
495 return #response has been sent
497 # In previous versions of SimpleXMLRPCServer, _dispatch
498 # could be overridden in this class, instead of in
499 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
500 # check to see if a subclass implements _dispatch and dispatch
501 # using that method if present.
502 response
= self
.server
._marshaled
_dispatch
(
503 data
, getattr(self
, '_dispatch', None), self
.path
505 except Exception, e
: # This should only happen if the module is buggy
506 # internal error, report as HTTP server error
507 self
.send_response(500)
509 # Send information about the exception if requested
510 if hasattr(self
.server
, '_send_traceback_header') and \
511 self
.server
._send
_traceback
_header
:
512 self
.send_header("X-exception", str(e
))
513 self
.send_header("X-traceback", traceback
.format_exc())
515 self
.send_header("Content-length", "0")
518 # got a valid XML RPC response
519 self
.send_response(200)
520 self
.send_header("Content-type", "text/xml")
521 if self
.encode_threshold
is not None:
522 if len(response
) > self
.encode_threshold
:
523 q
= self
.accept_encodings().get("gzip", 0)
526 response
= xmlrpclib
.gzip_encode(response
)
527 self
.send_header("Content-Encoding", "gzip")
528 except NotImplementedError:
530 self
.send_header("Content-length", str(len(response
)))
532 self
.wfile
.write(response
)
534 def decode_request_content(self
, data
):
535 #support gzip encoding of request
536 encoding
= self
.headers
.get("content-encoding", "identity").lower()
537 if encoding
== "identity":
539 if encoding
== "gzip":
541 return xmlrpclib
.gzip_decode(data
)
542 except NotImplementedError:
543 self
.send_response(501, "encoding %r not supported" % encoding
)
545 self
.send_response(400, "error decoding gzip content")
547 self
.send_response(501, "encoding %r not supported" % encoding
)
548 self
.send_header("Content-length", "0")
551 def report_404 (self
):
553 self
.send_response(404)
554 response
= 'No such page'
555 self
.send_header("Content-type", "text/plain")
556 self
.send_header("Content-length", str(len(response
)))
558 self
.wfile
.write(response
)
560 def log_request(self
, code
='-', size
='-'):
561 """Selectively log an accepted request."""
563 if self
.server
.logRequests
:
564 BaseHTTPServer
.BaseHTTPRequestHandler
.log_request(self
, code
, size
)
566 class SimpleXMLRPCServer(SocketServer
.TCPServer
,
567 SimpleXMLRPCDispatcher
):
568 """Simple XML-RPC server.
570 Simple XML-RPC server that allows functions and a single instance
571 to be installed to handle requests. The default implementation
572 attempts to dispatch XML-RPC calls to the functions or instance
573 installed in the server. Override the _dispatch method inhereted
574 from SimpleXMLRPCDispatcher to change this behavior.
577 allow_reuse_address
= True
579 # Warning: this is for debugging purposes only! Never set this to True in
580 # production code, as will be sending out sensitive information (exception
581 # and stack trace details) when exceptions are raised inside
582 # SimpleXMLRPCRequestHandler.do_POST
583 _send_traceback_header
= False
585 def __init__(self
, addr
, requestHandler
=SimpleXMLRPCRequestHandler
,
586 logRequests
=True, allow_none
=False, encoding
=None, bind_and_activate
=True):
587 self
.logRequests
= logRequests
589 SimpleXMLRPCDispatcher
.__init
__(self
, allow_none
, encoding
)
590 SocketServer
.TCPServer
.__init
__(self
, addr
, requestHandler
, bind_and_activate
)
592 # [Bug #1222790] If possible, set close-on-exec flag; if a
593 # method spawns a subprocess, the subprocess shouldn't have
594 # the listening socket open.
595 if fcntl
is not None and hasattr(fcntl
, 'FD_CLOEXEC'):
596 flags
= fcntl
.fcntl(self
.fileno(), fcntl
.F_GETFD
)
597 flags |
= fcntl
.FD_CLOEXEC
598 fcntl
.fcntl(self
.fileno(), fcntl
.F_SETFD
, flags
)
600 class MultiPathXMLRPCServer(SimpleXMLRPCServer
):
601 """Multipath XML-RPC Server
602 This specialization of SimpleXMLRPCServer allows the user to create
603 multiple Dispatcher instances and assign them to different
604 HTTP request paths. This makes it possible to run two or more
605 'virtual XML-RPC servers' at the same port.
606 Make sure that the requestHandler accepts the paths in question.
608 def __init__(self
, addr
, requestHandler
=SimpleXMLRPCRequestHandler
,
609 logRequests
=True, allow_none
=False, encoding
=None, bind_and_activate
=True):
611 SimpleXMLRPCServer
.__init
__(self
, addr
, requestHandler
, logRequests
, allow_none
,
612 encoding
, bind_and_activate
)
613 self
.dispatchers
= {}
614 self
.allow_none
= allow_none
615 self
.encoding
= encoding
617 def add_dispatcher(self
, path
, dispatcher
):
618 self
.dispatchers
[path
] = dispatcher
621 def get_dispatcher(self
, path
):
622 return self
.dispatchers
[path
]
624 def _marshaled_dispatch(self
, data
, dispatch_method
= None, path
= None):
626 response
= self
.dispatchers
[path
]._marshaled
_dispatch
(
627 data
, dispatch_method
, path
)
629 # report low level exception back to server
630 # (each dispatcher should have handled their own
632 exc_type
, exc_value
= sys
.exc_info()[:2]
633 response
= xmlrpclib
.dumps(
634 xmlrpclib
.Fault(1, "%s:%s" % (exc_type
, exc_value
)),
635 encoding
=self
.encoding
, allow_none
=self
.allow_none
)
638 class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher
):
639 """Simple handler for XML-RPC data passed through CGI."""
641 def __init__(self
, allow_none
=False, encoding
=None):
642 SimpleXMLRPCDispatcher
.__init
__(self
, allow_none
, encoding
)
644 def handle_xmlrpc(self
, request_text
):
645 """Handle a single XML-RPC request"""
647 response
= self
._marshaled
_dispatch
(request_text
)
649 print 'Content-Type: text/xml'
650 print 'Content-Length: %d' % len(response
)
652 sys
.stdout
.write(response
)
654 def handle_get(self
):
655 """Handle a single HTTP GET request.
657 Default implementation indicates an error because
658 XML-RPC uses the POST method.
663 BaseHTTPServer
.BaseHTTPRequestHandler
.responses
[code
]
665 response
= BaseHTTPServer
.DEFAULT_ERROR_MESSAGE
% \
671 print 'Status: %d %s' % (code
, message
)
672 print 'Content-Type: %s' % BaseHTTPServer
.DEFAULT_ERROR_CONTENT_TYPE
673 print 'Content-Length: %d' % len(response
)
675 sys
.stdout
.write(response
)
677 def handle_request(self
, request_text
= None):
678 """Handle a single XML-RPC request passed through a CGI post method.
680 If no XML data is given then it is read from stdin. The resulting
681 XML-RPC response is printed to stdout along with the correct HTTP
685 if request_text
is None and \
686 os
.environ
.get('REQUEST_METHOD', None) == 'GET':
689 # POST data is normally available through stdin
691 length
= int(os
.environ
.get('CONTENT_LENGTH', None))
692 except (TypeError, ValueError):
694 if request_text
is None:
695 request_text
= sys
.stdin
.read(length
)
697 self
.handle_xmlrpc(request_text
)
699 if __name__
== '__main__':
700 print 'Running XML-RPC server on port 8000'
701 server
= SimpleXMLRPCServer(("localhost", 8000))
702 server
.register_function(pow)
703 server
.register_function(lambda x
,y
: x
+y
, 'add')
704 server
.serve_forever()