Fixes (accepts patch) issue1339 - http://bugs.python.org/issue1339
[python.git] / Lib / SimpleXMLRPCServer.py
blob5fad0af4a344bbdbdbd5e445712d7c44923341bd
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
6 class.
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:
13 1. Install functions:
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:
22 class MyFuncs:
23 def __init__(self):
24 # make all of the string functions available through
25 # string.func_name
26 import string
27 self.string = string
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:
43 class Math:
44 def _listMethods(self):
45 # this method must be present for system.listMethods
46 # to work
47 return ['add', 'pow']
48 def _methodHelp(self, method):
49 # this method must be present for system.methodHelp
50 # to work
51 if method == 'add':
52 return "add(2,3) => 5"
53 elif method == 'pow':
54 return "pow(x, y[, z]) => number"
55 else:
56 # By convention, return empty
57 # string if no help is available
58 return ""
59 def _dispatch(self, method, params):
60 if method == 'pow':
61 return pow(*params)
62 elif method == 'add':
63 return params[0] + params[1]
64 else:
65 raise 'bad method'
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):
76 try:
77 # We are forcing the 'export_' prefix on methods that are
78 # callable through XML-RPC to prevent potential security
79 # problems
80 func = getattr(self, 'export_' + method)
81 except AttributeError:
82 raise Exception('method "%s" is not supported' % method)
83 else:
84 return func(*params)
86 def export_add(self, x, y):
87 return x + y
89 server = MathServer(("localhost", 8000))
90 server.serve_forever()
92 5. CGI script:
94 server = CGIXMLRPCRequestHandler()
95 server.register_function(pow)
96 server.handle_request()
97 """
99 # Written by Brian Quinlan (brian@sweetapp.com).
100 # Based on code written by Fredrik Lundh.
102 import xmlrpclib
103 from xmlrpclib import Fault
104 import SocketServer
105 import BaseHTTPServer
106 import sys
107 import os
108 import traceback
109 try:
110 import fcntl
111 except ImportError:
112 fcntl = None
114 def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
115 """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
117 Resolves a dotted attribute name to an object. Raises
118 an AttributeError if any attribute in the chain starts with a '_'.
120 If the optional allow_dotted_names argument is false, dots are not
121 supported and this function operates similar to getattr(obj, attr).
124 if allow_dotted_names:
125 attrs = attr.split('.')
126 else:
127 attrs = [attr]
129 for i in attrs:
130 if i.startswith('_'):
131 raise AttributeError(
132 'attempt to access private attribute "%s"' % i
134 else:
135 obj = getattr(obj,i)
136 return obj
138 def list_public_methods(obj):
139 """Returns a list of attribute strings, found in the specified
140 object, which represent callable attributes"""
142 return [member for member in dir(obj)
143 if not member.startswith('_') and
144 callable(getattr(obj, member))]
146 def remove_duplicates(lst):
147 """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
149 Returns a copy of a list without duplicates. Every list
150 item must be hashable and the order of the items in the
151 resulting list is not defined.
153 u = {}
154 for x in lst:
155 u[x] = 1
157 return u.keys()
159 class SimpleXMLRPCDispatcher:
160 """Mix-in class that dispatches XML-RPC requests.
162 This class is used to register XML-RPC method handlers
163 and then to dispatch them. There should never be any
164 reason to instantiate this class directly.
167 def __init__(self, allow_none, encoding):
168 self.funcs = {}
169 self.instance = None
170 self.allow_none = allow_none
171 self.encoding = encoding
173 def register_instance(self, instance, allow_dotted_names=False):
174 """Registers an instance to respond to XML-RPC requests.
176 Only one instance can be installed at a time.
178 If the registered instance has a _dispatch method then that
179 method will be called with the name of the XML-RPC method and
180 its parameters as a tuple
181 e.g. instance._dispatch('add',(2,3))
183 If the registered instance does not have a _dispatch method
184 then the instance will be searched to find a matching method
185 and, if found, will be called. Methods beginning with an '_'
186 are considered private and will not be called by
187 SimpleXMLRPCServer.
189 If a registered function matches a XML-RPC request, then it
190 will be called instead of the registered instance.
192 If the optional allow_dotted_names argument is true and the
193 instance does not have a _dispatch method, method names
194 containing dots are supported and resolved, as long as none of
195 the name segments start with an '_'.
197 *** SECURITY WARNING: ***
199 Enabling the allow_dotted_names options allows intruders
200 to access your module's global variables and may allow
201 intruders to execute arbitrary code on your machine. Only
202 use this option on a secure, closed network.
206 self.instance = instance
207 self.allow_dotted_names = allow_dotted_names
209 def register_function(self, function, name = None):
210 """Registers a function to respond to XML-RPC requests.
212 The optional name argument can be used to set a Unicode name
213 for the function.
216 if name is None:
217 name = function.__name__
218 self.funcs[name] = function
220 def register_introspection_functions(self):
221 """Registers the XML-RPC introspection methods in the system
222 namespace.
224 see http://xmlrpc.usefulinc.com/doc/reserved.html
227 self.funcs.update({'system.listMethods' : self.system_listMethods,
228 'system.methodSignature' : self.system_methodSignature,
229 'system.methodHelp' : self.system_methodHelp})
231 def register_multicall_functions(self):
232 """Registers the XML-RPC multicall method in the system
233 namespace.
235 see http://www.xmlrpc.com/discuss/msgReader$1208"""
237 self.funcs.update({'system.multicall' : self.system_multicall})
239 def _marshaled_dispatch(self, data, dispatch_method = None):
240 """Dispatches an XML-RPC method from marshalled (XML) data.
242 XML-RPC methods are dispatched from the marshalled (XML) data
243 using the _dispatch method and the result is returned as
244 marshalled data. For backwards compatibility, a dispatch
245 function can be provided as an argument (see comment in
246 SimpleXMLRPCRequestHandler.do_POST) but overriding the
247 existing method through subclassing is the prefered means
248 of changing method dispatch behavior.
251 try:
252 params, method = xmlrpclib.loads(data)
254 # generate response
255 if dispatch_method is not None:
256 response = dispatch_method(method, params)
257 else:
258 response = self._dispatch(method, params)
259 # wrap response in a singleton tuple
260 response = (response,)
261 response = xmlrpclib.dumps(response, methodresponse=1,
262 allow_none=self.allow_none, encoding=self.encoding)
263 except Fault, fault:
264 response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
265 encoding=self.encoding)
266 except:
267 # report exception back to server
268 exc_type, exc_value, exc_tb = sys.exc_info()
269 response = xmlrpclib.dumps(
270 xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),
271 encoding=self.encoding, allow_none=self.allow_none,
274 return response
276 def system_listMethods(self):
277 """system.listMethods() => ['add', 'subtract', 'multiple']
279 Returns a list of the methods supported by the server."""
281 methods = self.funcs.keys()
282 if self.instance is not None:
283 # Instance can implement _listMethod to return a list of
284 # methods
285 if hasattr(self.instance, '_listMethods'):
286 methods = remove_duplicates(
287 methods + self.instance._listMethods()
289 # if the instance has a _dispatch method then we
290 # don't have enough information to provide a list
291 # of methods
292 elif not hasattr(self.instance, '_dispatch'):
293 methods = remove_duplicates(
294 methods + list_public_methods(self.instance)
296 methods.sort()
297 return methods
299 def system_methodSignature(self, method_name):
300 """system.methodSignature('add') => [double, int, int]
302 Returns a list describing the signature of the method. In the
303 above example, the add method takes two integers as arguments
304 and returns a double result.
306 This server does NOT support system.methodSignature."""
308 # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
310 return 'signatures not supported'
312 def system_methodHelp(self, method_name):
313 """system.methodHelp('add') => "Adds two integers together"
315 Returns a string containing documentation for the specified method."""
317 method = None
318 if self.funcs.has_key(method_name):
319 method = self.funcs[method_name]
320 elif self.instance is not None:
321 # Instance can implement _methodHelp to return help for a method
322 if hasattr(self.instance, '_methodHelp'):
323 return self.instance._methodHelp(method_name)
324 # if the instance has a _dispatch method then we
325 # don't have enough information to provide help
326 elif not hasattr(self.instance, '_dispatch'):
327 try:
328 method = resolve_dotted_attribute(
329 self.instance,
330 method_name,
331 self.allow_dotted_names
333 except AttributeError:
334 pass
336 # Note that we aren't checking that the method actually
337 # be a callable object of some kind
338 if method is None:
339 return ""
340 else:
341 import pydoc
342 return pydoc.getdoc(method)
344 def system_multicall(self, call_list):
345 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
346 [[4], ...]
348 Allows the caller to package multiple XML-RPC calls into a single
349 request.
351 See http://www.xmlrpc.com/discuss/msgReader$1208
354 results = []
355 for call in call_list:
356 method_name = call['methodName']
357 params = call['params']
359 try:
360 # XXX A marshalling error in any response will fail the entire
361 # multicall. If someone cares they should fix this.
362 results.append([self._dispatch(method_name, params)])
363 except Fault, fault:
364 results.append(
365 {'faultCode' : fault.faultCode,
366 'faultString' : fault.faultString}
368 except:
369 exc_type, exc_value, exc_tb = sys.exc_info()
370 results.append(
371 {'faultCode' : 1,
372 'faultString' : "%s:%s" % (exc_type, exc_value)}
374 return results
376 def _dispatch(self, method, params):
377 """Dispatches the XML-RPC method.
379 XML-RPC calls are forwarded to a registered function that
380 matches the called XML-RPC method name. If no such function
381 exists then the call is forwarded to the registered instance,
382 if available.
384 If the registered instance has a _dispatch method then that
385 method will be called with the name of the XML-RPC method and
386 its parameters as a tuple
387 e.g. instance._dispatch('add',(2,3))
389 If the registered instance does not have a _dispatch method
390 then the instance will be searched to find a matching method
391 and, if found, will be called.
393 Methods beginning with an '_' are considered private and will
394 not be called.
397 func = None
398 try:
399 # check to see if a matching function has been registered
400 func = self.funcs[method]
401 except KeyError:
402 if self.instance is not None:
403 # check for a _dispatch method
404 if hasattr(self.instance, '_dispatch'):
405 return self.instance._dispatch(method, params)
406 else:
407 # call instance method directly
408 try:
409 func = resolve_dotted_attribute(
410 self.instance,
411 method,
412 self.allow_dotted_names
414 except AttributeError:
415 pass
417 if func is not None:
418 return func(*params)
419 else:
420 raise Exception('method "%s" is not supported' % method)
422 class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
423 """Simple XML-RPC request handler class.
425 Handles all HTTP POST requests and attempts to decode them as
426 XML-RPC requests.
429 # Class attribute listing the accessible path components;
430 # paths not on this list will result in a 404 error.
431 rpc_paths = ('/', '/RPC2')
433 def is_rpc_path_valid(self):
434 if self.rpc_paths:
435 return self.path in self.rpc_paths
436 else:
437 # If .rpc_paths is empty, just assume all paths are legal
438 return True
440 def do_POST(self):
441 """Handles the HTTP POST request.
443 Attempts to interpret all HTTP POST requests as XML-RPC calls,
444 which are forwarded to the server's _dispatch method for handling.
447 # Check that the path is legal
448 if not self.is_rpc_path_valid():
449 self.report_404()
450 return
452 try:
453 # Get arguments by reading body of request.
454 # We read this in chunks to avoid straining
455 # socket.read(); around the 10 or 15Mb mark, some platforms
456 # begin to have problems (bug #792570).
457 max_chunk_size = 10*1024*1024
458 size_remaining = int(self.headers["content-length"])
459 L = []
460 while size_remaining:
461 chunk_size = min(size_remaining, max_chunk_size)
462 L.append(self.rfile.read(chunk_size))
463 size_remaining -= len(L[-1])
464 data = ''.join(L)
466 # In previous versions of SimpleXMLRPCServer, _dispatch
467 # could be overridden in this class, instead of in
468 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
469 # check to see if a subclass implements _dispatch and dispatch
470 # using that method if present.
471 response = self.server._marshaled_dispatch(
472 data, getattr(self, '_dispatch', None)
474 except Exception, e: # This should only happen if the module is buggy
475 # internal error, report as HTTP server error
476 self.send_response(500)
478 # Send information about the exception if requested
479 if hasattr(self.server, '_send_traceback_header') and \
480 self.server._send_traceback_header:
481 self.send_header("X-exception", str(e))
482 self.send_header("X-traceback", traceback.format_exc())
484 self.end_headers()
485 else:
486 # got a valid XML RPC response
487 self.send_response(200)
488 self.send_header("Content-type", "text/xml")
489 self.send_header("Content-length", str(len(response)))
490 self.end_headers()
491 self.wfile.write(response)
493 # shut down the connection
494 self.wfile.flush()
495 self.connection.shutdown(1)
497 def report_404 (self):
498 # Report a 404 error
499 self.send_response(404)
500 response = 'No such page'
501 self.send_header("Content-type", "text/plain")
502 self.send_header("Content-length", str(len(response)))
503 self.end_headers()
504 self.wfile.write(response)
505 # shut down the connection
506 self.wfile.flush()
507 self.connection.shutdown(1)
509 def log_request(self, code='-', size='-'):
510 """Selectively log an accepted request."""
512 if self.server.logRequests:
513 BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
515 class SimpleXMLRPCServer(SocketServer.TCPServer,
516 SimpleXMLRPCDispatcher):
517 """Simple XML-RPC server.
519 Simple XML-RPC server that allows functions and a single instance
520 to be installed to handle requests. The default implementation
521 attempts to dispatch XML-RPC calls to the functions or instance
522 installed in the server. Override the _dispatch method inhereted
523 from SimpleXMLRPCDispatcher to change this behavior.
526 allow_reuse_address = True
528 # Warning: this is for debugging purposes only! Never set this to True in
529 # production code, as will be sending out sensitive information (exception
530 # and stack trace details) when exceptions are raised inside
531 # SimpleXMLRPCRequestHandler.do_POST
532 _send_traceback_header = False
534 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
535 logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
536 self.logRequests = logRequests
538 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
539 SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
541 # [Bug #1222790] If possible, set close-on-exec flag; if a
542 # method spawns a subprocess, the subprocess shouldn't have
543 # the listening socket open.
544 if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
545 flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
546 flags |= fcntl.FD_CLOEXEC
547 fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
549 class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
550 """Simple handler for XML-RPC data passed through CGI."""
552 def __init__(self, allow_none=False, encoding=None):
553 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
555 def handle_xmlrpc(self, request_text):
556 """Handle a single XML-RPC request"""
558 response = self._marshaled_dispatch(request_text)
560 print 'Content-Type: text/xml'
561 print 'Content-Length: %d' % len(response)
562 print
563 sys.stdout.write(response)
565 def handle_get(self):
566 """Handle a single HTTP GET request.
568 Default implementation indicates an error because
569 XML-RPC uses the POST method.
572 code = 400
573 message, explain = \
574 BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
576 response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
578 'code' : code,
579 'message' : message,
580 'explain' : explain
582 print 'Status: %d %s' % (code, message)
583 print 'Content-Type: text/html'
584 print 'Content-Length: %d' % len(response)
585 print
586 sys.stdout.write(response)
588 def handle_request(self, request_text = None):
589 """Handle a single XML-RPC request passed through a CGI post method.
591 If no XML data is given then it is read from stdin. The resulting
592 XML-RPC response is printed to stdout along with the correct HTTP
593 headers.
596 if request_text is None and \
597 os.environ.get('REQUEST_METHOD', None) == 'GET':
598 self.handle_get()
599 else:
600 # POST data is normally available through stdin
601 if request_text is None:
602 request_text = sys.stdin.read()
604 self.handle_xmlrpc(request_text)
606 if __name__ == '__main__':
607 print 'Running XML-RPC server on port 8000'
608 server = SimpleXMLRPCServer(("localhost", 8000))
609 server.register_function(pow)
610 server.register_function(lambda x,y: x+y, 'add')
611 server.serve_forever()