Catch situations where currentframe() returns None. See SF patch #1447410, this is...
[python.git] / Lib / SimpleXMLRPCServer.py
blob052a8e4aed8607d5adb9d03eea07848f2f43dc88
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, fcntl
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('.')
121 else:
122 attrs = [attr]
124 for i in attrs:
125 if i.startswith('_'):
126 raise AttributeError(
127 'attempt to access private attribute "%s"' % i
129 else:
130 obj = getattr(obj,i)
131 return obj
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.
148 u = {}
149 for x in lst:
150 u[x] = 1
152 return u.keys()
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):
163 self.funcs = {}
164 self.instance = None
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
182 SimpleXMLRPCServer.
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
208 for the function.
211 if name is None:
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
217 namespace.
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
228 namespace.
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)
248 # generate response
249 try:
250 if dispatch_method is not None:
251 response = dispatch_method(method, params)
252 else:
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)
258 except Fault, fault:
259 response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
260 encoding=self.encoding)
261 except:
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,
268 return response
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
278 # methods
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
285 # of methods
286 elif not hasattr(self.instance, '_dispatch'):
287 methods = remove_duplicates(
288 methods + list_public_methods(self.instance)
290 methods.sort()
291 return methods
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."""
311 method = None
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'):
321 try:
322 method = resolve_dotted_attribute(
323 self.instance,
324 method_name,
325 self.allow_dotted_names
327 except AttributeError:
328 pass
330 # Note that we aren't checking that the method actually
331 # be a callable object of some kind
332 if method is None:
333 return ""
334 else:
335 import pydoc
336 return pydoc.getdoc(method)
338 def system_multicall(self, call_list):
339 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
340 [[4], ...]
342 Allows the caller to package multiple XML-RPC calls into a single
343 request.
345 See http://www.xmlrpc.com/discuss/msgReader$1208
348 results = []
349 for call in call_list:
350 method_name = call['methodName']
351 params = call['params']
353 try:
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)])
357 except Fault, fault:
358 results.append(
359 {'faultCode' : fault.faultCode,
360 'faultString' : fault.faultString}
362 except:
363 results.append(
364 {'faultCode' : 1,
365 'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)}
367 return results
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,
375 if available.
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
387 not be called.
390 func = None
391 try:
392 # check to see if a matching function has been registered
393 func = self.funcs[method]
394 except KeyError:
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)
399 else:
400 # call instance method directly
401 try:
402 func = resolve_dotted_attribute(
403 self.instance,
404 method,
405 self.allow_dotted_names
407 except AttributeError:
408 pass
410 if func is not None:
411 return func(*params)
412 else:
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
419 XML-RPC requests.
422 def do_POST(self):
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.
429 try:
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"])
436 L = []
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])
441 data = ''.join(L)
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)
454 self.end_headers()
455 else:
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)))
460 self.end_headers()
461 self.wfile.write(response)
463 # shut down the connection
464 self.wfile.flush()
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)
514 print
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.
524 code = 400
525 message, explain = \
526 BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
528 response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
530 'code' : code,
531 'message' : message,
532 'explain' : explain
534 print 'Status: %d %s' % (code, message)
535 print 'Content-Type: text/html'
536 print 'Content-Length: %d' % len(response)
537 print
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
545 headers.
548 if request_text is None and \
549 os.environ.get('REQUEST_METHOD', None) == 'GET':
550 self.handle_get()
551 else:
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()