issue5063: Fixes for building RPM on CentOS plus misc .spec file enhancements.
[python.git] / Lib / SimpleXMLRPCServer.py
blob4cf0b18a0d4e52c35c27caa241482d65af2f3f25
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 import re
110 try:
111 import fcntl
112 except ImportError:
113 fcntl = None
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('.')
127 else:
128 attrs = [attr]
130 for i in attrs:
131 if i.startswith('_'):
132 raise AttributeError(
133 'attempt to access private attribute "%s"' % i
135 else:
136 obj = getattr(obj,i)
137 return obj
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.
154 u = {}
155 for x in lst:
156 u[x] = 1
158 return u.keys()
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):
170 self.funcs = {}
171 self.instance = 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
189 SimpleXMLRPCServer.
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
215 for the function.
218 if name is None:
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
224 namespace.
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
235 namespace.
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.
253 try:
254 params, method = xmlrpclib.loads(data)
256 # generate response
257 if dispatch_method is not None:
258 response = dispatch_method(method, params)
259 else:
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)
265 except Fault, fault:
266 response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
267 encoding=self.encoding)
268 except:
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,
276 return response
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
286 # methods
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
293 # of methods
294 elif not hasattr(self.instance, '_dispatch'):
295 methods = remove_duplicates(
296 methods + list_public_methods(self.instance)
298 methods.sort()
299 return methods
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."""
319 method = None
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'):
329 try:
330 method = resolve_dotted_attribute(
331 self.instance,
332 method_name,
333 self.allow_dotted_names
335 except AttributeError:
336 pass
338 # Note that we aren't checking that the method actually
339 # be a callable object of some kind
340 if method is None:
341 return ""
342 else:
343 import pydoc
344 return pydoc.getdoc(method)
346 def system_multicall(self, call_list):
347 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
348 [[4], ...]
350 Allows the caller to package multiple XML-RPC calls into a single
351 request.
353 See http://www.xmlrpc.com/discuss/msgReader$1208
356 results = []
357 for call in call_list:
358 method_name = call['methodName']
359 params = call['params']
361 try:
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)])
365 except Fault, fault:
366 results.append(
367 {'faultCode' : fault.faultCode,
368 'faultString' : fault.faultString}
370 except:
371 exc_type, exc_value, exc_tb = sys.exc_info()
372 results.append(
373 {'faultCode' : 1,
374 'faultString' : "%s:%s" % (exc_type, exc_value)}
376 return results
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,
384 if available.
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
396 not be called.
399 func = None
400 try:
401 # check to see if a matching function has been registered
402 func = self.funcs[method]
403 except KeyError:
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)
408 else:
409 # call instance method directly
410 try:
411 func = resolve_dotted_attribute(
412 self.instance,
413 method,
414 self.allow_dotted_names
416 except AttributeError:
417 pass
419 if func is not None:
420 return func(*params)
421 else:
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
428 XML-RPC requests.
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
439 #and no Nagle.
440 wbufsize = -1
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):
450 r = {}
451 ae = self.headers.get("Accept-Encoding", "")
452 for e in ae.split(","):
453 match = self.aepattern.match(e)
454 if match:
455 v = match.group(3)
456 v = float(v) if v else 1.0
457 r[match.group(1)] = v
458 return r
460 def is_rpc_path_valid(self):
461 if self.rpc_paths:
462 return self.path in self.rpc_paths
463 else:
464 # If .rpc_paths is empty, just assume all paths are legal
465 return True
467 def do_POST(self):
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():
476 self.report_404()
477 return
479 try:
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"])
486 L = []
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])
491 data = ''.join(L)
493 data = self.decode_request_content(data)
494 if data is None:
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")
516 self.end_headers()
517 else:
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)
524 if q:
525 try:
526 response = xmlrpclib.gzip_encode(response)
527 self.send_header("Content-Encoding", "gzip")
528 except NotImplementedError:
529 pass
530 self.send_header("Content-length", str(len(response)))
531 self.end_headers()
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":
538 return data
539 if encoding == "gzip":
540 try:
541 return xmlrpclib.gzip_decode(data)
542 except NotImplementedError:
543 self.send_response(501, "encoding %r not supported" % encoding)
544 except ValueError:
545 self.send_response(400, "error decoding gzip content")
546 else:
547 self.send_response(501, "encoding %r not supported" % encoding)
548 self.send_header("Content-length", "0")
549 self.end_headers()
551 def report_404 (self):
552 # Report a 404 error
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)))
557 self.end_headers()
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
619 return dispatcher
621 def get_dispatcher(self, path):
622 return self.dispatchers[path]
624 def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
625 try:
626 response = self.dispatchers[path]._marshaled_dispatch(
627 data, dispatch_method, path)
628 except:
629 # report low level exception back to server
630 # (each dispatcher should have handled their own
631 # exceptions)
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)
636 return response
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)
651 print
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.
661 code = 400
662 message, explain = \
663 BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
665 response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
667 'code' : code,
668 'message' : message,
669 'explain' : explain
671 print 'Status: %d %s' % (code, message)
672 print 'Content-Type: %s' % BaseHTTPServer.DEFAULT_ERROR_CONTENT_TYPE
673 print 'Content-Length: %d' % len(response)
674 print
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
682 headers.
685 if request_text is None and \
686 os.environ.get('REQUEST_METHOD', None) == 'GET':
687 self.handle_get()
688 else:
689 # POST data is normally available through stdin
690 try:
691 length = int(os.environ.get('CONTENT_LENGTH', None))
692 except (TypeError, ValueError):
693 length = -1
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()