Move setting of ioready 'wait' earlier in call chain, to
[python/dscho.git] / Lib / SimpleXMLRPCServer.py
blob54533bf0e6097dc40394d25be814b4de49c9510a
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 types
108 import os
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
122 else:
123 obj = getattr(obj,i)
124 return obj
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.
141 u = {}
142 for x in lst:
143 u[x] = 1
145 return u.keys()
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.
155 def __init__(self):
156 self.funcs = {}
157 self.instance = None
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
173 SimpleXMLRPCServer.
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
185 for the function.
188 if name is None:
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
194 namespace.
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
205 namespace.
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)
225 # generate response
226 try:
227 if dispatch_method is not None:
228 response = dispatch_method(method, params)
229 else:
230 response = self._dispatch(method, params)
231 # wrap response in a singleton tuple
232 response = (response,)
233 response = xmlrpclib.dumps(response, methodresponse=1)
234 except Fault, fault:
235 response = xmlrpclib.dumps(fault)
236 except:
237 # report exception back to server
238 response = xmlrpclib.dumps(
239 xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
242 return response
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
252 # methods
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
259 # of methods
260 elif not hasattr(self.instance, '_dispatch'):
261 methods = remove_duplicates(
262 methods + list_public_methods(self.instance)
264 methods.sort()
265 return methods
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."""
285 method = None
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'):
295 try:
296 method = resolve_dotted_attribute(
297 self.instance,
298 method_name
300 except AttributeError:
301 pass
303 # Note that we aren't checking that the method actually
304 # be a callable object of some kind
305 if method is None:
306 return ""
307 else:
308 return pydoc.getdoc(method)
310 def system_multicall(self, call_list):
311 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
312 [[4], ...]
314 Allows the caller to package multiple XML-RPC calls into a single
315 request.
317 See http://www.xmlrpc.com/discuss/msgReader$1208
320 results = []
321 for call in call_list:
322 method_name = call['methodName']
323 params = call['params']
325 try:
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)])
329 except Fault, fault:
330 results.append(
331 {'faultCode' : fault.faultCode,
332 'faultString' : fault.faultString}
334 except:
335 results.append(
336 {'faultCode' : 1,
337 'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)}
339 return results
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,
347 if available.
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
359 not be called.
362 func = None
363 try:
364 # check to see if a matching function has been registered
365 func = self.funcs[method]
366 except KeyError:
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)
371 else:
372 # call instance method directly
373 try:
374 func = resolve_dotted_attribute(
375 self.instance,
376 method
378 except AttributeError:
379 pass
381 if func is not None:
382 return func(*params)
383 else:
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
390 XML-RPC requests.
393 def do_POST(self):
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.
400 try:
401 # get arguments
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)
414 self.end_headers()
415 else:
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)))
420 self.end_headers()
421 self.wfile.write(response)
423 # shut down the connection
424 self.wfile.flush()
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,
445 logRequests=1):
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."""
454 def __init__(self):
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)
464 print
465 print 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.
474 code = 400
475 message, explain = \
476 BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
478 response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
480 'code' : code,
481 'message' : message,
482 'explain' : explain
484 print 'Status: %d %s' % (code, message)
485 print 'Content-Type: text/html'
486 print 'Content-Length: %d' % len(response)
487 print
488 print 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
495 headers.
498 if request_text is None and \
499 os.environ.get('REQUEST_METHOD', None) == 'GET':
500 self.handle_get()
501 else:
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()