blob: b2a561056f54129d612828ecdfb1675f672ae390 [file] [log] [blame]
Fredrik Lundhb329b712001-09-17 17:35:21 +00001"""Simple XML-RPC Server.
2
3This module can be used to create simple XML-RPC servers
4by creating a server and either installing functions, a
Martin v. Löwisd69663d2003-01-15 11:37:23 +00005class instance, or by extending the SimpleXMLRPCServer
Fredrik Lundhb329b712001-09-17 17:35:21 +00006class.
7
Martin v. Löwisd69663d2003-01-15 11:37:23 +00008It can also be used to handle XML-RPC requests in a CGI
9environment using CGIXMLRPCRequestHandler.
10
Fredrik Lundhb329b712001-09-17 17:35:21 +000011A list of possible usage patterns follows:
12
131. Install functions:
14
15server = SimpleXMLRPCServer(("localhost", 8000))
16server.register_function(pow)
17server.register_function(lambda x,y: x+y, 'add')
18server.serve_forever()
19
202. Install an instance:
21
22class 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
Martin v. Löwisd69663d2003-01-15 11:37:23 +000028 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)]
Fredrik Lundhb329b712001-09-17 17:35:21 +000033 def pow(self, x, y): return pow(x, y)
34 def add(self, x, y) : return x + y
Tim Peters2c60f7a2003-01-29 03:49:43 +000035
Fredrik Lundhb329b712001-09-17 17:35:21 +000036server = SimpleXMLRPCServer(("localhost", 8000))
Martin v. Löwisd69663d2003-01-15 11:37:23 +000037server.register_introspection_functions()
Fredrik Lundhb329b712001-09-17 17:35:21 +000038server.register_instance(MyFuncs())
39server.serve_forever()
40
413. Install an instance with custom dispatch method:
42
43class Math:
Martin v. Löwisd69663d2003-01-15 11:37:23 +000044 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 ""
Fredrik Lundhb329b712001-09-17 17:35:21 +000059 def _dispatch(self, method, params):
60 if method == 'pow':
Martin v. Löwisd69663d2003-01-15 11:37:23 +000061 return pow(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +000062 elif method == 'add':
63 return params[0] + params[1]
64 else:
65 raise 'bad method'
Martin v. Löwisd69663d2003-01-15 11:37:23 +000066
Fredrik Lundhb329b712001-09-17 17:35:21 +000067server = SimpleXMLRPCServer(("localhost", 8000))
Martin v. Löwisd69663d2003-01-15 11:37:23 +000068server.register_introspection_functions()
Fredrik Lundhb329b712001-09-17 17:35:21 +000069server.register_instance(Math())
70server.serve_forever()
71
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000724. Subclass SimpleXMLRPCServer:
Fredrik Lundhb329b712001-09-17 17:35:21 +000073
Martin v. Löwisd69663d2003-01-15 11:37:23 +000074class MathServer(SimpleXMLRPCServer):
Fredrik Lundhb329b712001-09-17 17:35:21 +000075 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:
Martin v. Löwisd69663d2003-01-15 11:37:23 +000084 return func(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +000085
86 def export_add(self, x, y):
87 return x + y
88
Martin v. Löwisd69663d2003-01-15 11:37:23 +000089server = MathServer(("localhost", 8000))
Fredrik Lundhb329b712001-09-17 17:35:21 +000090server.serve_forever()
Martin v. Löwisd69663d2003-01-15 11:37:23 +000091
925. CGI script:
93
94server = CGIXMLRPCRequestHandler()
95server.register_function(pow)
96server.handle_request()
Fredrik Lundhb329b712001-09-17 17:35:21 +000097"""
98
99# Written by Brian Quinlan (brian@sweetapp.com).
100# Based on code written by Fredrik Lundh.
101
102import xmlrpclib
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000103from xmlrpclib import Fault
Georg Brandle152a772008-05-24 18:31:28 +0000104import SocketServer
Fredrik Lundhb329b712001-09-17 17:35:21 +0000105import BaseHTTPServer
106import sys
Anthony Baxtere29002c2006-04-12 12:07:31 +0000107import os
Facundo Batista7f686fc2007-08-17 19:16:44 +0000108import traceback
Kristján Valur Jónssone0078602009-06-28 21:04:17 +0000109import re
Anthony Baxtere29002c2006-04-12 12:07:31 +0000110try:
111 import fcntl
112except ImportError:
113 fcntl = None
Fredrik Lundhb329b712001-09-17 17:35:21 +0000114
Guido van Rossumd0641422005-02-03 15:01:24 +0000115def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000116 """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
Fredrik Lundhb329b712001-09-17 17:35:21 +0000117
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000118 Resolves a dotted attribute name to an object. Raises
119 an AttributeError if any attribute in the chain starts with a '_'.
Guido van Rossumd0641422005-02-03 15:01:24 +0000120
121 If the optional allow_dotted_names argument is false, dots are not
122 supported and this function operates similar to getattr(obj, attr).
Fredrik Lundhb329b712001-09-17 17:35:21 +0000123 """
124
Guido van Rossumd0641422005-02-03 15:01:24 +0000125 if allow_dotted_names:
126 attrs = attr.split('.')
127 else:
128 attrs = [attr]
129
130 for i in attrs:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000131 if i.startswith('_'):
132 raise AttributeError(
133 'attempt to access private attribute "%s"' % i
134 )
135 else:
136 obj = getattr(obj,i)
137 return obj
Fredrik Lundhb329b712001-09-17 17:35:21 +0000138
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000139def list_public_methods(obj):
140 """Returns a list of attribute strings, found in the specified
141 object, which represent callable attributes"""
142
143 return [member for member in dir(obj)
144 if not member.startswith('_') and
Brett Cannon0a0f6082008-08-03 22:57:23 +0000145 hasattr(getattr(obj, member), '__call__')]
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000146
147def remove_duplicates(lst):
148 """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
149
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.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000153 """
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000154 u = {}
155 for x in lst:
156 u[x] = 1
157
158 return u.keys()
159
160class SimpleXMLRPCDispatcher:
161 """Mix-in class that dispatches XML-RPC requests.
162
163 This class is used to register XML-RPC method handlers
Kristján Valur Jónsson429677e2009-08-27 23:13:18 +0000164 and then to dispatch them. This class doesn't need to be
165 instanced directly when used by SimpleXMLRPCServer but it
Georg Brandle1ff2142010-10-27 07:09:22 +0000166 can be instanced when used by the MultiPathXMLRPCServer.
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000167 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000168
Matthias Klosea5d58c82009-04-05 21:00:48 +0000169 def __init__(self, allow_none=False, encoding=None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000170 self.funcs = {}
171 self.instance = None
Andrew M. Kuchling10a16de2005-12-04 16:34:40 +0000172 self.allow_none = allow_none
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000173 self.encoding = encoding
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000174
Guido van Rossumd0641422005-02-03 15:01:24 +0000175 def register_instance(self, instance, allow_dotted_names=False):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000176 """Registers an instance to respond to XML-RPC requests.
177
178 Only one instance can be installed at a time.
179
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
Georg Brandl7eb4b7d2005-07-22 21:49:32 +0000182 its parameters as a tuple
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000183 e.g. instance._dispatch('add',(2,3))
184
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.
190
191 If a registered function matches a XML-RPC request, then it
192 will be called instead of the registered instance.
Guido van Rossumd0641422005-02-03 15:01:24 +0000193
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 '_'.
198
199 *** SECURITY WARNING: ***
200
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.
205
Fredrik Lundhb329b712001-09-17 17:35:21 +0000206 """
207
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000208 self.instance = instance
Guido van Rossumd0641422005-02-03 15:01:24 +0000209 self.allow_dotted_names = allow_dotted_names
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000210
211 def register_function(self, function, name = None):
212 """Registers a function to respond to XML-RPC requests.
213
214 The optional name argument can be used to set a Unicode name
215 for the function.
216 """
217
218 if name is None:
219 name = function.__name__
220 self.funcs[name] = function
221
222 def register_introspection_functions(self):
223 """Registers the XML-RPC introspection methods in the system
224 namespace.
225
226 see http://xmlrpc.usefulinc.com/doc/reserved.html
227 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000228
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000229 self.funcs.update({'system.listMethods' : self.system_listMethods,
230 'system.methodSignature' : self.system_methodSignature,
231 'system.methodHelp' : self.system_methodHelp})
232
233 def register_multicall_functions(self):
234 """Registers the XML-RPC multicall method in the system
235 namespace.
236
237 see http://www.xmlrpc.com/discuss/msgReader$1208"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000238
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000239 self.funcs.update({'system.multicall' : self.system_multicall})
Tim Peters2c60f7a2003-01-29 03:49:43 +0000240
Kristján Valur Jónsson429677e2009-08-27 23:13:18 +0000241 def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000242 """Dispatches an XML-RPC method from marshalled (XML) data.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000243
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000244 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
Tim Peters2c60f7a2003-01-29 03:49:43 +0000247 function can be provided as an argument (see comment in
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000248 SimpleXMLRPCRequestHandler.do_POST) but overriding the
249 existing method through subclassing is the prefered means
250 of changing method dispatch behavior.
251 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000252
Fredrik Lundhb329b712001-09-17 17:35:21 +0000253 try:
Georg Brandlb9120e72006-06-01 12:30:46 +0000254 params, method = xmlrpclib.loads(data)
255
256 # generate response
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000257 if dispatch_method is not None:
258 response = dispatch_method(method, params)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000259 else:
Fredrik Lundhb329b712001-09-17 17:35:21 +0000260 response = self._dispatch(method, params)
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000261 # wrap response in a singleton tuple
262 response = (response,)
Tim Peters536cf992005-12-25 23:18:31 +0000263 response = xmlrpclib.dumps(response, methodresponse=1,
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000264 allow_none=self.allow_none, encoding=self.encoding)
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000265 except Fault, fault:
Tim Peters536cf992005-12-25 23:18:31 +0000266 response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000267 encoding=self.encoding)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000268 except:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000269 # report exception back to server
Andrew M. Kuchlinga5453c42006-09-05 13:15:41 +0000270 exc_type, exc_value, exc_tb = sys.exc_info()
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000271 response = xmlrpclib.dumps(
Andrew M. Kuchlinga5453c42006-09-05 13:15:41 +0000272 xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000273 encoding=self.encoding, allow_none=self.allow_none,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000274 )
275
276 return response
277
278 def system_listMethods(self):
279 """system.listMethods() => ['add', 'subtract', 'multiple']
280
281 Returns a list of the methods supported by the server."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000282
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000283 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()
290 )
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)
297 )
298 methods.sort()
299 return methods
Tim Peters2c60f7a2003-01-29 03:49:43 +0000300
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000301 def system_methodSignature(self, method_name):
302 """system.methodSignature('add') => [double, int, int]
303
Brett Cannonb9b5f162004-10-03 23:21:44 +0000304 Returns a list describing the signature of the method. In the
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000305 above example, the add method takes two integers as arguments
306 and returns a double result.
307
308 This server does NOT support system.methodSignature."""
309
310 # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
Tim Peters2c60f7a2003-01-29 03:49:43 +0000311
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000312 return 'signatures not supported'
313
314 def system_methodHelp(self, method_name):
315 """system.methodHelp('add') => "Adds two integers together"
316
317 Returns a string containing documentation for the specified method."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000318
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000319 method = None
Brett Cannon0a0f6082008-08-03 22:57:23 +0000320 if method_name in self.funcs:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000321 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,
Guido van Rossumd0641422005-02-03 15:01:24 +0000332 method_name,
333 self.allow_dotted_names
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000334 )
335 except AttributeError:
336 pass
337
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 ""
Fredrik Lundhb329b712001-09-17 17:35:21 +0000342 else:
Neal Norwitz732911f2003-06-29 04:16:28 +0000343 import pydoc
Neal Norwitz3f401f02003-06-29 04:19:37 +0000344 return pydoc.getdoc(method)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000345
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000346 def system_multicall(self, call_list):
347 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
348[[4], ...]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000349
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000350 Allows the caller to package multiple XML-RPC calls into a single
351 request.
352
Tim Peters2c60f7a2003-01-29 03:49:43 +0000353 See http://www.xmlrpc.com/discuss/msgReader$1208
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000354 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000355
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000356 results = []
357 for call in call_list:
358 method_name = call['methodName']
359 params = call['params']
360
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}
369 )
370 except:
Andrew M. Kuchlinga5453c42006-09-05 13:15:41 +0000371 exc_type, exc_value, exc_tb = sys.exc_info()
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000372 results.append(
373 {'faultCode' : 1,
Andrew M. Kuchlinga5453c42006-09-05 13:15:41 +0000374 'faultString' : "%s:%s" % (exc_type, exc_value)}
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000375 )
376 return results
Tim Peters2c60f7a2003-01-29 03:49:43 +0000377
Fredrik Lundhb329b712001-09-17 17:35:21 +0000378 def _dispatch(self, method, params):
379 """Dispatches the XML-RPC method.
380
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.
385
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
Georg Brandl7eb4b7d2005-07-22 21:49:32 +0000388 its parameters as a tuple
Fredrik Lundhb329b712001-09-17 17:35:21 +0000389 e.g. instance._dispatch('add',(2,3))
390
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.
394
395 Methods beginning with an '_' are considered private and will
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000396 not be called.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000397 """
398
Fredrik Lundhb329b712001-09-17 17:35:21 +0000399 func = None
400 try:
401 # check to see if a matching function has been registered
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000402 func = self.funcs[method]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000403 except KeyError:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000404 if self.instance is not None:
Fredrik Lundhb329b712001-09-17 17:35:21 +0000405 # check for a _dispatch method
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000406 if hasattr(self.instance, '_dispatch'):
407 return self.instance._dispatch(method, params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000408 else:
409 # call instance method directly
410 try:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000411 func = resolve_dotted_attribute(
412 self.instance,
Guido van Rossumd0641422005-02-03 15:01:24 +0000413 method,
414 self.allow_dotted_names
Fredrik Lundhb329b712001-09-17 17:35:21 +0000415 )
416 except AttributeError:
417 pass
418
419 if func is not None:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000420 return func(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000421 else:
422 raise Exception('method "%s" is not supported' % method)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000423
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000424class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
425 """Simple XML-RPC request handler class.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000426
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000427 Handles all HTTP POST requests and attempts to decode them as
428 XML-RPC requests.
429 """
430
Andrew M. Kuchling622f1442006-05-31 14:08:48 +0000431 # Class attribute listing the accessible path components;
432 # paths not on this list will result in a 404 error.
433 rpc_paths = ('/', '/RPC2')
434
Kristján Valur Jónssone0078602009-06-28 21:04:17 +0000435 #if not None, encode responses larger than this, if possible
436 encode_threshold = 1400 #a common MTU
437
438 #Override form StreamRequestHandler: full buffering of output
439 #and no Nagle.
440 wbufsize = -1
441 disable_nagle_algorithm = True
442
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)
448
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
459
Andrew M. Kuchling622f1442006-05-31 14:08:48 +0000460 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
466
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000467 def do_POST(self):
468 """Handles the HTTP POST request.
469
470 Attempts to interpret all HTTP POST requests as XML-RPC calls,
471 which are forwarded to the server's _dispatch method for handling.
472 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000473
Andrew M. Kuchling622f1442006-05-31 14:08:48 +0000474 # Check that the path is legal
475 if not self.is_rpc_path_valid():
476 self.report_404()
477 return
Tim Peters5535da02006-06-01 13:41:46 +0000478
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000479 try:
Tim Peters536cf992005-12-25 23:18:31 +0000480 # Get arguments by reading body of request.
481 # We read this in chunks to avoid straining
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000482 # 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)
492
Kristján Valur Jónssone0078602009-06-28 21:04:17 +0000493 data = self.decode_request_content(data)
494 if data is None:
495 return #response has been sent
496
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000497 # 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(
Kristján Valur Jónsson429677e2009-08-27 23:13:18 +0000503 data, getattr(self, '_dispatch', None), self.path
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000504 )
Facundo Batista7f686fc2007-08-17 19:16:44 +0000505 except Exception, e: # This should only happen if the module is buggy
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000506 # internal error, report as HTTP server error
507 self.send_response(500)
Facundo Batista7f686fc2007-08-17 19:16:44 +0000508
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())
514
Kristján Valur Jónssone0078602009-06-28 21:04:17 +0000515 self.send_header("Content-length", "0")
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000516 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")
Kristján Valur Jónssone0078602009-06-28 21:04:17 +0000521 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:
Kristján Valur Jónsson6d755902009-07-19 22:14:00 +0000525 try:
526 response = xmlrpclib.gzip_encode(response)
527 self.send_header("Content-Encoding", "gzip")
528 except NotImplementedError:
529 pass
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000530 self.send_header("Content-length", str(len(response)))
531 self.end_headers()
532 self.wfile.write(response)
533
Kristján Valur Jónssone0078602009-06-28 21:04:17 +0000534 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)
Kristján Valur Jónsson6d755902009-07-19 22:14:00 +0000542 except NotImplementedError:
543 self.send_response(501, "encoding %r not supported" % encoding)
Kristján Valur Jónssone0078602009-06-28 21:04:17 +0000544 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()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000550
Andrew M. Kuchling622f1442006-05-31 14:08:48 +0000551 def report_404 (self):
552 # Report a 404 error
Tim Peters5535da02006-06-01 13:41:46 +0000553 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)
Andrew M. Kuchling622f1442006-05-31 14:08:48 +0000559
Fredrik Lundhb329b712001-09-17 17:35:21 +0000560 def log_request(self, code='-', size='-'):
561 """Selectively log an accepted request."""
562
563 if self.server.logRequests:
564 BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
565
Georg Brandle152a772008-05-24 18:31:28 +0000566class SimpleXMLRPCServer(SocketServer.TCPServer,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000567 SimpleXMLRPCDispatcher):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000568 """Simple XML-RPC server.
569
570 Simple XML-RPC server that allows functions and a single instance
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000571 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.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000575 """
576
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000577 allow_reuse_address = True
578
Facundo Batista7f686fc2007-08-17 19:16:44 +0000579 # 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
584
Fredrik Lundhb329b712001-09-17 17:35:21 +0000585 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Collin Winterae041062007-03-10 14:41:48 +0000586 logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000587 self.logRequests = logRequests
Tim Peters2c60f7a2003-01-29 03:49:43 +0000588
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000589 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
Georg Brandle152a772008-05-24 18:31:28 +0000590 SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000591
Tim Peters536cf992005-12-25 23:18:31 +0000592 # [Bug #1222790] If possible, set close-on-exec flag; if a
593 # method spawns a subprocess, the subprocess shouldn't have
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000594 # the listening socket open.
Anthony Baxtere29002c2006-04-12 12:07:31 +0000595 if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000596 flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
597 flags |= fcntl.FD_CLOEXEC
598 fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
599
Kristján Valur Jónsson429677e2009-08-27 23:13:18 +0000600class 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.
607 """
608 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
609 logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
610
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
616
617 def add_dispatcher(self, path, dispatcher):
618 self.dispatchers[path] = dispatcher
619 return dispatcher
620
621 def get_dispatcher(self, path):
622 return self.dispatchers[path]
623
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
637
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000638class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
639 """Simple handler for XML-RPC data passed through CGI."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000640
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000641 def __init__(self, allow_none=False, encoding=None):
642 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000643
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000644 def handle_xmlrpc(self, request_text):
645 """Handle a single XML-RPC request"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000646
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000647 response = self._marshaled_dispatch(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000648
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000649 print 'Content-Type: text/xml'
650 print 'Content-Length: %d' % len(response)
651 print
Martin v. Löwis9c5ea502003-05-01 05:05:09 +0000652 sys.stdout.write(response)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000653
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000654 def handle_get(self):
655 """Handle a single HTTP GET request.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000656
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000657 Default implementation indicates an error because
658 XML-RPC uses the POST method.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000659 """
660
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000661 code = 400
662 message, explain = \
663 BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
Tim Peters2c60f7a2003-01-29 03:49:43 +0000664
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000665 response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
666 {
Tim Peters2c60f7a2003-01-29 03:49:43 +0000667 'code' : code,
668 'message' : message,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000669 'explain' : explain
670 }
671 print 'Status: %d %s' % (code, message)
Senthil Kumaran20d114c2009-04-01 20:26:33 +0000672 print 'Content-Type: %s' % BaseHTTPServer.DEFAULT_ERROR_CONTENT_TYPE
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000673 print 'Content-Length: %d' % len(response)
674 print
Neal Norwitz732911f2003-06-29 04:16:28 +0000675 sys.stdout.write(response)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000676
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000677 def handle_request(self, request_text = None):
678 """Handle a single XML-RPC request passed through a CGI post method.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000679
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000680 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.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000683 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000684
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000685 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
Georg Brandle92d4b62009-04-01 04:21:14 +0000690 try:
691 length = int(os.environ.get('CONTENT_LENGTH', None))
Georg Brandl61fce382009-04-01 15:23:43 +0000692 except (TypeError, ValueError):
Georg Brandle92d4b62009-04-01 04:21:14 +0000693 length = -1
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000694 if request_text is None:
Georg Brandle92d4b62009-04-01 04:21:14 +0000695 request_text = sys.stdin.read(length)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000696
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000697 self.handle_xmlrpc(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000698
Fredrik Lundhb329b712001-09-17 17:35:21 +0000699if __name__ == '__main__':
Andrew M. Kuchlingb0a1e6b2006-04-21 12:57:35 +0000700 print 'Running XML-RPC server on port 8000'
Fredrik Lundhb329b712001-09-17 17:35:21 +0000701 server = SimpleXMLRPCServer(("localhost", 8000))
702 server.register_function(pow)
703 server.register_function(lambda x,y: x+y, 'add')
704 server.serve_forever()