blob: d27bf5a177e95a45d1269e8640e1d8575040f0d6 [file] [log] [blame]
Éric Araujoc432a9d2012-03-05 15:45:08 +01001r"""XML-RPC Servers.
Fredrik Lundhb329b712001-09-17 17:35:21 +00002
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
Georg Brandl38eceaa2008-05-26 11:14:17 +000011The Doc* classes can be used to create XML-RPC servers that
12serve pydoc-style documentation in response to HTTP
13GET requests. This documentation is dynamically generated
14based on the functions and methods registered with the
15server.
16
Fredrik Lundhb329b712001-09-17 17:35:21 +000017A list of possible usage patterns follows:
18
191. Install functions:
20
21server = SimpleXMLRPCServer(("localhost", 8000))
22server.register_function(pow)
23server.register_function(lambda x,y: x+y, 'add')
24server.serve_forever()
25
262. Install an instance:
27
28class MyFuncs:
29 def __init__(self):
Neal Norwitz9d72bb42007-04-17 08:48:32 +000030 # make all of the sys functions available through sys.func_name
31 import sys
32 self.sys = sys
Martin v. Löwisd69663d2003-01-15 11:37:23 +000033 def _listMethods(self):
34 # implement this method so that system.listMethods
Neal Norwitz9d72bb42007-04-17 08:48:32 +000035 # knows to advertise the sys methods
Martin v. Löwisd69663d2003-01-15 11:37:23 +000036 return list_public_methods(self) + \
Neal Norwitz9d72bb42007-04-17 08:48:32 +000037 ['sys.' + method for method in list_public_methods(self.sys)]
Fredrik Lundhb329b712001-09-17 17:35:21 +000038 def pow(self, x, y): return pow(x, y)
39 def add(self, x, y) : return x + y
Tim Peters2c60f7a2003-01-29 03:49:43 +000040
Fredrik Lundhb329b712001-09-17 17:35:21 +000041server = SimpleXMLRPCServer(("localhost", 8000))
Martin v. Löwisd69663d2003-01-15 11:37:23 +000042server.register_introspection_functions()
Fredrik Lundhb329b712001-09-17 17:35:21 +000043server.register_instance(MyFuncs())
44server.serve_forever()
45
463. Install an instance with custom dispatch method:
47
48class Math:
Martin v. Löwisd69663d2003-01-15 11:37:23 +000049 def _listMethods(self):
50 # this method must be present for system.listMethods
51 # to work
52 return ['add', 'pow']
53 def _methodHelp(self, method):
54 # this method must be present for system.methodHelp
55 # to work
56 if method == 'add':
57 return "add(2,3) => 5"
58 elif method == 'pow':
59 return "pow(x, y[, z]) => number"
60 else:
61 # By convention, return empty
62 # string if no help is available
63 return ""
Fredrik Lundhb329b712001-09-17 17:35:21 +000064 def _dispatch(self, method, params):
65 if method == 'pow':
Martin v. Löwisd69663d2003-01-15 11:37:23 +000066 return pow(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +000067 elif method == 'add':
68 return params[0] + params[1]
69 else:
Benjamin Peterson2d8c9172010-10-31 18:13:04 +000070 raise ValueError('bad method')
Martin v. Löwisd69663d2003-01-15 11:37:23 +000071
Fredrik Lundhb329b712001-09-17 17:35:21 +000072server = SimpleXMLRPCServer(("localhost", 8000))
Martin v. Löwisd69663d2003-01-15 11:37:23 +000073server.register_introspection_functions()
Fredrik Lundhb329b712001-09-17 17:35:21 +000074server.register_instance(Math())
75server.serve_forever()
76
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000774. Subclass SimpleXMLRPCServer:
Fredrik Lundhb329b712001-09-17 17:35:21 +000078
Martin v. Löwisd69663d2003-01-15 11:37:23 +000079class MathServer(SimpleXMLRPCServer):
Fredrik Lundhb329b712001-09-17 17:35:21 +000080 def _dispatch(self, method, params):
81 try:
82 # We are forcing the 'export_' prefix on methods that are
83 # callable through XML-RPC to prevent potential security
84 # problems
85 func = getattr(self, 'export_' + method)
86 except AttributeError:
87 raise Exception('method "%s" is not supported' % method)
88 else:
Martin v. Löwisd69663d2003-01-15 11:37:23 +000089 return func(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +000090
91 def export_add(self, x, y):
92 return x + y
93
Martin v. Löwisd69663d2003-01-15 11:37:23 +000094server = MathServer(("localhost", 8000))
Fredrik Lundhb329b712001-09-17 17:35:21 +000095server.serve_forever()
Martin v. Löwisd69663d2003-01-15 11:37:23 +000096
975. CGI script:
98
99server = CGIXMLRPCRequestHandler()
100server.register_function(pow)
101server.handle_request()
Fredrik Lundhb329b712001-09-17 17:35:21 +0000102"""
103
104# Written by Brian Quinlan (brian@sweetapp.com).
105# Based on code written by Fredrik Lundh.
106
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000107from xmlrpc.client import Fault, dumps, loads, gzip_encode, gzip_decode
Georg Brandl24420152008-05-26 16:32:26 +0000108from http.server import BaseHTTPRequestHandler
109import http.server
Alexandre Vassalottice261952008-05-12 02:31:37 +0000110import socketserver
Fredrik Lundhb329b712001-09-17 17:35:21 +0000111import sys
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000112import os
Georg Brandl38eceaa2008-05-26 11:14:17 +0000113import re
114import pydoc
115import inspect
Guido van Rossum61e21b52007-08-20 19:06:03 +0000116import traceback
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000117try:
118 import fcntl
119except ImportError:
120 fcntl = None
Fredrik Lundhb329b712001-09-17 17:35:21 +0000121
Guido van Rossumd0641422005-02-03 15:01:24 +0000122def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000123 """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
Fredrik Lundhb329b712001-09-17 17:35:21 +0000124
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000125 Resolves a dotted attribute name to an object. Raises
126 an AttributeError if any attribute in the chain starts with a '_'.
Guido van Rossumd0641422005-02-03 15:01:24 +0000127
128 If the optional allow_dotted_names argument is false, dots are not
129 supported and this function operates similar to getattr(obj, attr).
Fredrik Lundhb329b712001-09-17 17:35:21 +0000130 """
131
Guido van Rossumd0641422005-02-03 15:01:24 +0000132 if allow_dotted_names:
133 attrs = attr.split('.')
134 else:
135 attrs = [attr]
136
137 for i in attrs:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000138 if i.startswith('_'):
139 raise AttributeError(
140 'attempt to access private attribute "%s"' % i
141 )
142 else:
143 obj = getattr(obj,i)
144 return obj
Fredrik Lundhb329b712001-09-17 17:35:21 +0000145
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000146def list_public_methods(obj):
147 """Returns a list of attribute strings, found in the specified
148 object, which represent callable attributes"""
149
150 return [member for member in dir(obj)
151 if not member.startswith('_') and
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200152 callable(getattr(obj, member))]
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000153
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000154class SimpleXMLRPCDispatcher:
155 """Mix-in class that dispatches XML-RPC requests.
156
157 This class is used to register XML-RPC method handlers
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000158 and then to dispatch them. This class doesn't need to be
159 instanced directly when used by SimpleXMLRPCServer but it
160 can be instanced when used by the MultiPathXMLRPCServer
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000161 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000162
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100163 def __init__(self, allow_none=False, encoding=None,
164 use_builtin_types=False):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000165 self.funcs = {}
166 self.instance = None
Andrew M. Kuchling10a16de2005-12-04 16:34:40 +0000167 self.allow_none = allow_none
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000168 self.encoding = encoding or 'utf-8'
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100169 self.use_builtin_types = use_builtin_types
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000170
Guido van Rossumd0641422005-02-03 15:01:24 +0000171 def register_instance(self, instance, allow_dotted_names=False):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000172 """Registers an instance to respond to XML-RPC requests.
173
174 Only one instance can be installed at a time.
175
176 If the registered instance has a _dispatch method then that
177 method will be called with the name of the XML-RPC method and
Georg Brandl7eb4b7d2005-07-22 21:49:32 +0000178 its parameters as a tuple
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000179 e.g. instance._dispatch('add',(2,3))
180
181 If the registered instance does not have a _dispatch method
182 then the instance will be searched to find a matching method
183 and, if found, will be called. Methods beginning with an '_'
184 are considered private and will not be called by
185 SimpleXMLRPCServer.
186
187 If a registered function matches a XML-RPC request, then it
188 will be called instead of the registered instance.
Guido van Rossumd0641422005-02-03 15:01:24 +0000189
190 If the optional allow_dotted_names argument is true and the
191 instance does not have a _dispatch method, method names
192 containing dots are supported and resolved, as long as none of
193 the name segments start with an '_'.
194
195 *** SECURITY WARNING: ***
196
197 Enabling the allow_dotted_names options allows intruders
198 to access your module's global variables and may allow
199 intruders to execute arbitrary code on your machine. Only
200 use this option on a secure, closed network.
201
Fredrik Lundhb329b712001-09-17 17:35:21 +0000202 """
203
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000204 self.instance = instance
Guido van Rossumd0641422005-02-03 15:01:24 +0000205 self.allow_dotted_names = allow_dotted_names
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000206
Georg Brandlfe991052009-09-16 15:54:04 +0000207 def register_function(self, function, name=None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000208 """Registers a function to respond to XML-RPC requests.
209
210 The optional name argument can be used to set a Unicode name
211 for the function.
212 """
213
214 if name is None:
215 name = function.__name__
216 self.funcs[name] = function
217
218 def register_introspection_functions(self):
219 """Registers the XML-RPC introspection methods in the system
220 namespace.
221
222 see http://xmlrpc.usefulinc.com/doc/reserved.html
223 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000224
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000225 self.funcs.update({'system.listMethods' : self.system_listMethods,
226 'system.methodSignature' : self.system_methodSignature,
227 'system.methodHelp' : self.system_methodHelp})
228
229 def register_multicall_functions(self):
230 """Registers the XML-RPC multicall method in the system
231 namespace.
232
233 see http://www.xmlrpc.com/discuss/msgReader$1208"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000234
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000235 self.funcs.update({'system.multicall' : self.system_multicall})
Tim Peters2c60f7a2003-01-29 03:49:43 +0000236
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000237 def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000238 """Dispatches an XML-RPC method from marshalled (XML) data.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000239
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000240 XML-RPC methods are dispatched from the marshalled (XML) data
241 using the _dispatch method and the result is returned as
242 marshalled data. For backwards compatibility, a dispatch
Tim Peters2c60f7a2003-01-29 03:49:43 +0000243 function can be provided as an argument (see comment in
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000244 SimpleXMLRPCRequestHandler.do_POST) but overriding the
Ezio Melotti13925002011-03-16 11:05:33 +0200245 existing method through subclassing is the preferred means
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000246 of changing method dispatch behavior.
247 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000248
Fredrik Lundhb329b712001-09-17 17:35:21 +0000249 try:
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100250 params, method = loads(data, use_builtin_types=self.use_builtin_types)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000251
252 # generate response
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000253 if dispatch_method is not None:
254 response = dispatch_method(method, params)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000255 else:
Fredrik Lundhb329b712001-09-17 17:35:21 +0000256 response = self._dispatch(method, params)
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000257 # wrap response in a singleton tuple
258 response = (response,)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000259 response = dumps(response, methodresponse=1,
260 allow_none=self.allow_none, encoding=self.encoding)
Guido van Rossumb940e112007-01-10 16:19:56 +0000261 except Fault as fault:
Georg Brandl38eceaa2008-05-26 11:14:17 +0000262 response = dumps(fault, allow_none=self.allow_none,
263 encoding=self.encoding)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000264 except:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000265 # report exception back to server
Thomas Wouters89f507f2006-12-13 04:49:30 +0000266 exc_type, exc_value, exc_tb = sys.exc_info()
Georg Brandl38eceaa2008-05-26 11:14:17 +0000267 response = dumps(
268 Fault(1, "%s:%s" % (exc_type, exc_value)),
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000269 encoding=self.encoding, allow_none=self.allow_none,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000270 )
271
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000272 return response.encode(self.encoding)
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000273
274 def system_listMethods(self):
275 """system.listMethods() => ['add', 'subtract', 'multiple']
276
277 Returns a list of the methods supported by the server."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000278
Hye-Shik Chang96042862007-08-19 10:49:11 +0000279 methods = set(self.funcs.keys())
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000280 if self.instance is not None:
281 # Instance can implement _listMethod to return a list of
282 # methods
283 if hasattr(self.instance, '_listMethods'):
Hye-Shik Chang96042862007-08-19 10:49:11 +0000284 methods |= set(self.instance._listMethods())
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000285 # if the instance has a _dispatch method then we
286 # don't have enough information to provide a list
287 # of methods
288 elif not hasattr(self.instance, '_dispatch'):
Hye-Shik Chang96042862007-08-19 10:49:11 +0000289 methods |= set(list_public_methods(self.instance))
290 return sorted(methods)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000291
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000292 def system_methodSignature(self, method_name):
293 """system.methodSignature('add') => [double, int, int]
294
Brett Cannonb9b5f162004-10-03 23:21:44 +0000295 Returns a list describing the signature of the method. In the
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000296 above example, the add method takes two integers as arguments
297 and returns a double result.
298
299 This server does NOT support system.methodSignature."""
300
301 # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
Tim Peters2c60f7a2003-01-29 03:49:43 +0000302
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000303 return 'signatures not supported'
304
305 def system_methodHelp(self, method_name):
306 """system.methodHelp('add') => "Adds two integers together"
307
308 Returns a string containing documentation for the specified method."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000309
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000310 method = None
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000311 if method_name in self.funcs:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000312 method = self.funcs[method_name]
313 elif self.instance is not None:
314 # Instance can implement _methodHelp to return help for a method
315 if hasattr(self.instance, '_methodHelp'):
316 return self.instance._methodHelp(method_name)
317 # if the instance has a _dispatch method then we
318 # don't have enough information to provide help
319 elif not hasattr(self.instance, '_dispatch'):
320 try:
321 method = resolve_dotted_attribute(
322 self.instance,
Guido van Rossumd0641422005-02-03 15:01:24 +0000323 method_name,
324 self.allow_dotted_names
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000325 )
326 except AttributeError:
327 pass
328
329 # Note that we aren't checking that the method actually
330 # be a callable object of some kind
331 if method is None:
332 return ""
Fredrik Lundhb329b712001-09-17 17:35:21 +0000333 else:
Neal Norwitz3f401f02003-06-29 04:19:37 +0000334 return pydoc.getdoc(method)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000335
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000336 def system_multicall(self, call_list):
337 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
338[[4], ...]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000339
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000340 Allows the caller to package multiple XML-RPC calls into a single
341 request.
342
Tim Peters2c60f7a2003-01-29 03:49:43 +0000343 See http://www.xmlrpc.com/discuss/msgReader$1208
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000344 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000345
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000346 results = []
347 for call in call_list:
348 method_name = call['methodName']
349 params = call['params']
350
351 try:
352 # XXX A marshalling error in any response will fail the entire
353 # multicall. If someone cares they should fix this.
354 results.append([self._dispatch(method_name, params)])
Guido van Rossumb940e112007-01-10 16:19:56 +0000355 except Fault as fault:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000356 results.append(
357 {'faultCode' : fault.faultCode,
358 'faultString' : fault.faultString}
359 )
360 except:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000361 exc_type, exc_value, exc_tb = sys.exc_info()
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000362 results.append(
363 {'faultCode' : 1,
Thomas Wouters89f507f2006-12-13 04:49:30 +0000364 'faultString' : "%s:%s" % (exc_type, exc_value)}
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000365 )
366 return results
Tim Peters2c60f7a2003-01-29 03:49:43 +0000367
Fredrik Lundhb329b712001-09-17 17:35:21 +0000368 def _dispatch(self, method, params):
369 """Dispatches the XML-RPC method.
370
371 XML-RPC calls are forwarded to a registered function that
372 matches the called XML-RPC method name. If no such function
373 exists then the call is forwarded to the registered instance,
374 if available.
375
376 If the registered instance has a _dispatch method then that
377 method will be called with the name of the XML-RPC method and
Georg Brandl7eb4b7d2005-07-22 21:49:32 +0000378 its parameters as a tuple
Fredrik Lundhb329b712001-09-17 17:35:21 +0000379 e.g. instance._dispatch('add',(2,3))
380
381 If the registered instance does not have a _dispatch method
382 then the instance will be searched to find a matching method
383 and, if found, will be called.
384
385 Methods beginning with an '_' are considered private and will
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000386 not be called.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000387 """
388
Fredrik Lundhb329b712001-09-17 17:35:21 +0000389 func = None
390 try:
391 # check to see if a matching function has been registered
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000392 func = self.funcs[method]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000393 except KeyError:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000394 if self.instance is not None:
Fredrik Lundhb329b712001-09-17 17:35:21 +0000395 # check for a _dispatch method
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000396 if hasattr(self.instance, '_dispatch'):
397 return self.instance._dispatch(method, params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000398 else:
399 # call instance method directly
400 try:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000401 func = resolve_dotted_attribute(
402 self.instance,
Guido van Rossumd0641422005-02-03 15:01:24 +0000403 method,
404 self.allow_dotted_names
Fredrik Lundhb329b712001-09-17 17:35:21 +0000405 )
406 except AttributeError:
407 pass
408
409 if func is not None:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000410 return func(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000411 else:
412 raise Exception('method "%s" is not supported' % method)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000413
Georg Brandl24420152008-05-26 16:32:26 +0000414class SimpleXMLRPCRequestHandler(BaseHTTPRequestHandler):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000415 """Simple XML-RPC request handler class.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000416
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000417 Handles all HTTP POST requests and attempts to decode them as
418 XML-RPC requests.
419 """
420
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000421 # Class attribute listing the accessible path components;
422 # paths not on this list will result in a 404 error.
423 rpc_paths = ('/', '/RPC2')
424
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000425 #if not None, encode responses larger than this, if possible
426 encode_threshold = 1400 #a common MTU
427
428 #Override form StreamRequestHandler: full buffering of output
429 #and no Nagle.
430 wbufsize = -1
431 disable_nagle_algorithm = True
432
433 # a re to match a gzip Accept-Encoding
434 aepattern = re.compile(r"""
435 \s* ([^\s;]+) \s* #content-coding
436 (;\s* q \s*=\s* ([0-9\.]+))? #q
437 """, re.VERBOSE | re.IGNORECASE)
438
439 def accept_encodings(self):
440 r = {}
441 ae = self.headers.get("Accept-Encoding", "")
442 for e in ae.split(","):
443 match = self.aepattern.match(e)
444 if match:
445 v = match.group(3)
446 v = float(v) if v else 1.0
447 r[match.group(1)] = v
448 return r
449
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000450 def is_rpc_path_valid(self):
451 if self.rpc_paths:
452 return self.path in self.rpc_paths
453 else:
454 # If .rpc_paths is empty, just assume all paths are legal
455 return True
456
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000457 def do_POST(self):
458 """Handles the HTTP POST request.
459
460 Attempts to interpret all HTTP POST requests as XML-RPC calls,
461 which are forwarded to the server's _dispatch method for handling.
462 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000463
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000464 # Check that the path is legal
465 if not self.is_rpc_path_valid():
466 self.report_404()
467 return
468
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000469 try:
Tim Peters536cf992005-12-25 23:18:31 +0000470 # Get arguments by reading body of request.
471 # We read this in chunks to avoid straining
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000472 # socket.read(); around the 10 or 15Mb mark, some platforms
473 # begin to have problems (bug #792570).
474 max_chunk_size = 10*1024*1024
475 size_remaining = int(self.headers["content-length"])
476 L = []
477 while size_remaining:
478 chunk_size = min(size_remaining, max_chunk_size)
Charles-François Nataliec1712a2012-02-18 14:42:57 +0100479 chunk = self.rfile.read(chunk_size)
480 if not chunk:
481 break
482 L.append(chunk)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000483 size_remaining -= len(L[-1])
Hye-Shik Chang96042862007-08-19 10:49:11 +0000484 data = b''.join(L)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000485
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000486 data = self.decode_request_content(data)
487 if data is None:
488 return #response has been sent
489
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000490 # In previous versions of SimpleXMLRPCServer, _dispatch
491 # could be overridden in this class, instead of in
492 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
493 # check to see if a subclass implements _dispatch and dispatch
494 # using that method if present.
495 response = self.server._marshaled_dispatch(
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000496 data, getattr(self, '_dispatch', None), self.path
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000497 )
Guido van Rossum61e21b52007-08-20 19:06:03 +0000498 except Exception as e: # This should only happen if the module is buggy
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000499 # internal error, report as HTTP server error
500 self.send_response(500)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000501
502 # Send information about the exception if requested
503 if hasattr(self.server, '_send_traceback_header') and \
504 self.server._send_traceback_header:
505 self.send_header("X-exception", str(e))
Victor Stinner5bfe1462010-04-16 13:28:05 +0000506 trace = traceback.format_exc()
507 trace = str(trace.encode('ASCII', 'backslashreplace'), 'ASCII')
508 self.send_header("X-traceback", trace)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000509
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000510 self.send_header("Content-length", "0")
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000511 self.end_headers()
512 else:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000513 self.send_response(200)
514 self.send_header("Content-type", "text/xml")
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000515 if self.encode_threshold is not None:
516 if len(response) > self.encode_threshold:
517 q = self.accept_encodings().get("gzip", 0)
518 if q:
Kristján Valur Jónssonaefde242009-07-19 22:29:24 +0000519 try:
520 response = gzip_encode(response)
521 self.send_header("Content-Encoding", "gzip")
522 except NotImplementedError:
523 pass
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000524 self.send_header("Content-length", str(len(response)))
525 self.end_headers()
526 self.wfile.write(response)
527
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000528 def decode_request_content(self, data):
529 #support gzip encoding of request
530 encoding = self.headers.get("content-encoding", "identity").lower()
531 if encoding == "identity":
532 return data
533 if encoding == "gzip":
534 try:
535 return gzip_decode(data)
Kristján Valur Jónssonaefde242009-07-19 22:29:24 +0000536 except NotImplementedError:
537 self.send_response(501, "encoding %r not supported" % encoding)
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000538 except ValueError:
539 self.send_response(400, "error decoding gzip content")
540 else:
541 self.send_response(501, "encoding %r not supported" % encoding)
542 self.send_header("Content-length", "0")
543 self.end_headers()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000544
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000545 def report_404 (self):
546 # Report a 404 error
547 self.send_response(404)
Christian Heimes0aa93cd2007-12-08 18:38:20 +0000548 response = b'No such page'
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000549 self.send_header("Content-type", "text/plain")
550 self.send_header("Content-length", str(len(response)))
551 self.end_headers()
552 self.wfile.write(response)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000553
Fredrik Lundhb329b712001-09-17 17:35:21 +0000554 def log_request(self, code='-', size='-'):
555 """Selectively log an accepted request."""
556
557 if self.server.logRequests:
Georg Brandl24420152008-05-26 16:32:26 +0000558 BaseHTTPRequestHandler.log_request(self, code, size)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000559
Alexandre Vassalottice261952008-05-12 02:31:37 +0000560class SimpleXMLRPCServer(socketserver.TCPServer,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000561 SimpleXMLRPCDispatcher):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000562 """Simple XML-RPC server.
563
564 Simple XML-RPC server that allows functions and a single instance
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000565 to be installed to handle requests. The default implementation
566 attempts to dispatch XML-RPC calls to the functions or instance
Florent Xiclunac4fec932011-10-30 20:19:32 +0100567 installed in the server. Override the _dispatch method inherited
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000568 from SimpleXMLRPCDispatcher to change this behavior.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000569 """
570
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000571 allow_reuse_address = True
572
Guido van Rossum61e21b52007-08-20 19:06:03 +0000573 # Warning: this is for debugging purposes only! Never set this to True in
574 # production code, as will be sending out sensitive information (exception
575 # and stack trace details) when exceptions are raised inside
576 # SimpleXMLRPCRequestHandler.do_POST
577 _send_traceback_header = False
578
Fredrik Lundhb329b712001-09-17 17:35:21 +0000579 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100580 logRequests=True, allow_none=False, encoding=None,
581 bind_and_activate=True, use_builtin_types=False):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000582 self.logRequests = logRequests
Tim Peters2c60f7a2003-01-29 03:49:43 +0000583
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100584 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
Alexandre Vassalottice261952008-05-12 02:31:37 +0000585 socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000586
Tim Peters536cf992005-12-25 23:18:31 +0000587 # [Bug #1222790] If possible, set close-on-exec flag; if a
588 # method spawns a subprocess, the subprocess shouldn't have
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000589 # the listening socket open.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000590 if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000591 flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
592 flags |= fcntl.FD_CLOEXEC
593 fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
594
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000595class MultiPathXMLRPCServer(SimpleXMLRPCServer):
596 """Multipath XML-RPC Server
597 This specialization of SimpleXMLRPCServer allows the user to create
598 multiple Dispatcher instances and assign them to different
599 HTTP request paths. This makes it possible to run two or more
600 'virtual XML-RPC servers' at the same port.
601 Make sure that the requestHandler accepts the paths in question.
602 """
603 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100604 logRequests=True, allow_none=False, encoding=None,
605 bind_and_activate=True, use_builtin_types=False):
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000606
607 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100608 encoding, bind_and_activate, use_builtin_types)
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000609 self.dispatchers = {}
610 self.allow_none = allow_none
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100611 self.encoding = encoding or 'utf-8'
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000612
613 def add_dispatcher(self, path, dispatcher):
614 self.dispatchers[path] = dispatcher
615 return dispatcher
616
617 def get_dispatcher(self, path):
618 return self.dispatchers[path]
619
620 def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
621 try:
622 response = self.dispatchers[path]._marshaled_dispatch(
623 data, dispatch_method, path)
624 except:
625 # report low level exception back to server
626 # (each dispatcher should have handled their own
627 # exceptions)
628 exc_type, exc_value = sys.exc_info()[:2]
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100629 response = dumps(
630 Fault(1, "%s:%s" % (exc_type, exc_value)),
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000631 encoding=self.encoding, allow_none=self.allow_none)
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100632 response = response.encode(self.encoding)
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000633 return response
634
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000635class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
636 """Simple handler for XML-RPC data passed through CGI."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000637
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100638 def __init__(self, allow_none=False, encoding=None, use_builtin_types=False):
639 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000640
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000641 def handle_xmlrpc(self, request_text):
642 """Handle a single XML-RPC request"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000643
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000644 response = self._marshaled_dispatch(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000645
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000646 print('Content-Type: text/xml')
647 print('Content-Length: %d' % len(response))
648 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000649 sys.stdout.flush()
650 sys.stdout.buffer.write(response)
651 sys.stdout.buffer.flush()
Fredrik Lundhb329b712001-09-17 17:35:21 +0000652
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000653 def handle_get(self):
654 """Handle a single HTTP GET request.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000655
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000656 Default implementation indicates an error because
657 XML-RPC uses the POST method.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000658 """
659
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000660 code = 400
Georg Brandl24420152008-05-26 16:32:26 +0000661 message, explain = BaseHTTPRequestHandler.responses[code]
Tim Peters2c60f7a2003-01-29 03:49:43 +0000662
Georg Brandl24420152008-05-26 16:32:26 +0000663 response = http.server.DEFAULT_ERROR_MESSAGE % \
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000664 {
Tim Peters2c60f7a2003-01-29 03:49:43 +0000665 'code' : code,
666 'message' : message,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000667 'explain' : explain
668 }
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000669 response = response.encode('utf-8')
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000670 print('Status: %d %s' % (code, message))
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000671 print('Content-Type: %s' % http.server.DEFAULT_ERROR_CONTENT_TYPE)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000672 print('Content-Length: %d' % len(response))
673 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000674 sys.stdout.flush()
675 sys.stdout.buffer.write(response)
676 sys.stdout.buffer.flush()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000677
Georg Brandlfe991052009-09-16 15:54:04 +0000678 def handle_request(self, request_text=None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000679 """Handle a single XML-RPC request passed through a CGI post method.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000680
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000681 If no XML data is given then it is read from stdin. The resulting
682 XML-RPC response is printed to stdout along with the correct HTTP
683 headers.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000684 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000685
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000686 if request_text is None and \
687 os.environ.get('REQUEST_METHOD', None) == 'GET':
688 self.handle_get()
689 else:
690 # POST data is normally available through stdin
Georg Brandl99412e52009-04-01 04:27:47 +0000691 try:
692 length = int(os.environ.get('CONTENT_LENGTH', None))
Georg Brandlc7485062009-04-01 15:53:15 +0000693 except (ValueError, TypeError):
Georg Brandl99412e52009-04-01 04:27:47 +0000694 length = -1
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000695 if request_text is None:
Georg Brandl99412e52009-04-01 04:27:47 +0000696 request_text = sys.stdin.read(length)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000697
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000698 self.handle_xmlrpc(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000699
Georg Brandl38eceaa2008-05-26 11:14:17 +0000700
701# -----------------------------------------------------------------------------
702# Self documenting XML-RPC Server.
703
704class ServerHTMLDoc(pydoc.HTMLDoc):
705 """Class used to generate pydoc HTML document for a server"""
706
707 def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
708 """Mark up some plain text, given a context of symbols to look for.
709 Each context dictionary maps object names to anchor names."""
710 escape = escape or self.escape
711 results = []
712 here = 0
713
714 # XXX Note that this regular expression does not allow for the
715 # hyperlinking of arbitrary strings being used as method
716 # names. Only methods with names consisting of word characters
717 # and '.'s are hyperlinked.
718 pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
719 r'RFC[- ]?(\d+)|'
720 r'PEP[- ]?(\d+)|'
721 r'(self\.)?((?:\w|\.)+))\b')
722 while 1:
723 match = pattern.search(text, here)
724 if not match: break
725 start, end = match.span()
726 results.append(escape(text[here:start]))
727
728 all, scheme, rfc, pep, selfdot, name = match.groups()
729 if scheme:
730 url = escape(all).replace('"', '"')
731 results.append('<a href="%s">%s</a>' % (url, url))
732 elif rfc:
733 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
734 results.append('<a href="%s">%s</a>' % (url, escape(all)))
735 elif pep:
736 url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
737 results.append('<a href="%s">%s</a>' % (url, escape(all)))
738 elif text[end:end+1] == '(':
739 results.append(self.namelink(name, methods, funcs, classes))
740 elif selfdot:
741 results.append('self.<strong>%s</strong>' % name)
742 else:
743 results.append(self.namelink(name, classes))
744 here = end
745 results.append(escape(text[here:]))
746 return ''.join(results)
747
748 def docroutine(self, object, name, mod=None,
749 funcs={}, classes={}, methods={}, cl=None):
750 """Produce HTML documentation for a function or method object."""
751
752 anchor = (cl and cl.__name__ or '') + '-' + name
753 note = ''
754
755 title = '<a name="%s"><strong>%s</strong></a>' % (
756 self.escape(anchor), self.escape(name))
757
758 if inspect.ismethod(object):
R David Murrayf22b62e2013-08-10 12:01:47 -0400759 args = inspect.getfullargspec(object)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000760 # exclude the argument bound to the instance, it will be
761 # confusing to the non-Python user
762 argspec = inspect.formatargspec (
R David Murrayf22b62e2013-08-10 12:01:47 -0400763 args.args[1:],
764 args.varargs,
765 args.varkw,
766 args.defaults,
767 annotations=args.annotations,
Georg Brandl38eceaa2008-05-26 11:14:17 +0000768 formatvalue=self.formatvalue
769 )
770 elif inspect.isfunction(object):
R David Murrayf22b62e2013-08-10 12:01:47 -0400771 args = inspect.getfullargspec(object)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000772 argspec = inspect.formatargspec(
R David Murrayf22b62e2013-08-10 12:01:47 -0400773 args.args, args.varargs, args.varkw, args.defaults,
774 annotations=args.annotations,
775 formatvalue=self.formatvalue)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000776 else:
777 argspec = '(...)'
778
779 if isinstance(object, tuple):
780 argspec = object[0] or argspec
781 docstring = object[1] or ""
782 else:
783 docstring = pydoc.getdoc(object)
784
785 decl = title + argspec + (note and self.grey(
786 '<font face="helvetica, arial">%s</font>' % note))
787
788 doc = self.markup(
789 docstring, self.preformat, funcs, classes, methods)
790 doc = doc and '<dd><tt>%s</tt></dd>' % doc
791 return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
792
793 def docserver(self, server_name, package_documentation, methods):
794 """Produce HTML documentation for an XML-RPC server."""
795
796 fdict = {}
797 for key, value in methods.items():
798 fdict[key] = '#-' + key
799 fdict[value] = fdict[key]
800
801 server_name = self.escape(server_name)
802 head = '<big><big><strong>%s</strong></big></big>' % server_name
803 result = self.heading(head, '#ffffff', '#7799ee')
804
805 doc = self.markup(package_documentation, self.preformat, fdict)
806 doc = doc and '<tt>%s</tt>' % doc
807 result = result + '<p>%s</p>\n' % doc
808
809 contents = []
810 method_items = sorted(methods.items())
811 for key, value in method_items:
812 contents.append(self.docroutine(value, key, funcs=fdict))
813 result = result + self.bigsection(
814 'Methods', '#ffffff', '#eeaa77', ''.join(contents))
815
816 return result
817
818class XMLRPCDocGenerator:
819 """Generates documentation for an XML-RPC server.
820
821 This class is designed as mix-in and should not
822 be constructed directly.
823 """
824
825 def __init__(self):
826 # setup variables used for HTML documentation
827 self.server_name = 'XML-RPC Server Documentation'
828 self.server_documentation = \
829 "This server exports the following methods through the XML-RPC "\
830 "protocol."
831 self.server_title = 'XML-RPC Server Documentation'
832
833 def set_server_title(self, server_title):
834 """Set the HTML title of the generated server documentation"""
835
836 self.server_title = server_title
837
838 def set_server_name(self, server_name):
839 """Set the name of the generated HTML server documentation"""
840
841 self.server_name = server_name
842
843 def set_server_documentation(self, server_documentation):
844 """Set the documentation string for the entire server."""
845
846 self.server_documentation = server_documentation
847
848 def generate_html_documentation(self):
849 """generate_html_documentation() => html documentation for the server
850
851 Generates HTML documentation for the server using introspection for
852 installed functions and instances that do not implement the
853 _dispatch method. Alternatively, instances can choose to implement
854 the _get_method_argstring(method_name) method to provide the
855 argument string used in the documentation and the
856 _methodHelp(method_name) method to provide the help text used
857 in the documentation."""
858
859 methods = {}
860
861 for method_name in self.system_listMethods():
862 if method_name in self.funcs:
863 method = self.funcs[method_name]
864 elif self.instance is not None:
865 method_info = [None, None] # argspec, documentation
866 if hasattr(self.instance, '_get_method_argstring'):
867 method_info[0] = self.instance._get_method_argstring(method_name)
868 if hasattr(self.instance, '_methodHelp'):
869 method_info[1] = self.instance._methodHelp(method_name)
870
871 method_info = tuple(method_info)
872 if method_info != (None, None):
873 method = method_info
874 elif not hasattr(self.instance, '_dispatch'):
875 try:
876 method = resolve_dotted_attribute(
877 self.instance,
878 method_name
879 )
880 except AttributeError:
881 method = method_info
882 else:
883 method = method_info
884 else:
885 assert 0, "Could not find method in self.functions and no "\
886 "instance installed"
887
888 methods[method_name] = method
889
890 documenter = ServerHTMLDoc()
891 documentation = documenter.docserver(
892 self.server_name,
893 self.server_documentation,
894 methods
895 )
896
897 return documenter.page(self.server_title, documentation)
898
899class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
900 """XML-RPC and documentation request handler class.
901
902 Handles all HTTP POST requests and attempts to decode them as
903 XML-RPC requests.
904
905 Handles all HTTP GET requests and interprets them as requests
906 for documentation.
907 """
908
909 def do_GET(self):
910 """Handles the HTTP GET request.
911
912 Interpret all HTTP GET requests as requests for server
913 documentation.
914 """
915 # Check that the path is legal
916 if not self.is_rpc_path_valid():
917 self.report_404()
918 return
919
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000920 response = self.server.generate_html_documentation().encode('utf-8')
Georg Brandl38eceaa2008-05-26 11:14:17 +0000921 self.send_response(200)
922 self.send_header("Content-type", "text/html")
923 self.send_header("Content-length", str(len(response)))
924 self.end_headers()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000925 self.wfile.write(response)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000926
Georg Brandl38eceaa2008-05-26 11:14:17 +0000927class DocXMLRPCServer( SimpleXMLRPCServer,
928 XMLRPCDocGenerator):
929 """XML-RPC and HTML documentation server.
930
931 Adds the ability to serve server documentation to the capabilities
932 of SimpleXMLRPCServer.
933 """
934
935 def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
Georg Brandlfe991052009-09-16 15:54:04 +0000936 logRequests=True, allow_none=False, encoding=None,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100937 bind_and_activate=True, use_builtin_types=False):
Georg Brandl38eceaa2008-05-26 11:14:17 +0000938 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100939 allow_none, encoding, bind_and_activate,
940 use_builtin_types)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000941 XMLRPCDocGenerator.__init__(self)
942
943class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,
944 XMLRPCDocGenerator):
945 """Handler for XML-RPC data and documentation requests passed through
946 CGI"""
947
948 def handle_get(self):
949 """Handles the HTTP GET request.
950
951 Interpret all HTTP GET requests as requests for server
952 documentation.
953 """
954
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000955 response = self.generate_html_documentation().encode('utf-8')
Georg Brandl38eceaa2008-05-26 11:14:17 +0000956
957 print('Content-Type: text/html')
958 print('Content-Length: %d' % len(response))
959 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000960 sys.stdout.flush()
961 sys.stdout.buffer.write(response)
962 sys.stdout.buffer.flush()
Georg Brandl38eceaa2008-05-26 11:14:17 +0000963
964 def __init__(self):
965 CGIXMLRPCRequestHandler.__init__(self)
966 XMLRPCDocGenerator.__init__(self)
967
968
Fredrik Lundhb329b712001-09-17 17:35:21 +0000969if __name__ == '__main__':
Senthil Kumaran939e2db2014-01-12 16:06:58 -0800970 import datetime
971
972 class ExampleService:
973 def getData(self):
974 return '42'
975
976 class currentTime:
977 @staticmethod
978 def getCurrentTime():
979 return datetime.datetime.now()
980
Fredrik Lundhb329b712001-09-17 17:35:21 +0000981 server = SimpleXMLRPCServer(("localhost", 8000))
982 server.register_function(pow)
983 server.register_function(lambda x,y: x+y, 'add')
Senthil Kumaran939e2db2014-01-12 16:06:58 -0800984 server.register_instance(ExampleService(), allow_dotted_names=True)
985 server.register_multicall_functions()
Florent Xicluna75861df2011-10-30 20:39:24 +0100986 print('Serving XML-RPC on localhost port 8000')
Senthil Kumaran939e2db2014-01-12 16:06:58 -0800987 print('It is advisable to run this example server within a secure, closed network.')
Florent Xicluna75861df2011-10-30 20:39:24 +0100988 try:
989 server.serve_forever()
990 except KeyboardInterrupt:
991 print("\nKeyboard interrupt received, exiting.")
992 server.server_close()
993 sys.exit(0)