blob: 88d0eec786b018a10afa5ed0665a892cfd5713ae [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
Xiang Zhang267b9d22017-02-28 17:12:52 +0800109from functools import partial
Georg Brandl24420152008-05-26 16:32:26 +0000110import http.server
Alexandre Vassalottice261952008-05-12 02:31:37 +0000111import socketserver
Fredrik Lundhb329b712001-09-17 17:35:21 +0000112import sys
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000113import os
Georg Brandl38eceaa2008-05-26 11:14:17 +0000114import re
115import pydoc
116import inspect
Guido van Rossum61e21b52007-08-20 19:06:03 +0000117import traceback
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000118try:
119 import fcntl
120except ImportError:
121 fcntl = None
Fredrik Lundhb329b712001-09-17 17:35:21 +0000122
Guido van Rossumd0641422005-02-03 15:01:24 +0000123def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000124 """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
Fredrik Lundhb329b712001-09-17 17:35:21 +0000125
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000126 Resolves a dotted attribute name to an object. Raises
127 an AttributeError if any attribute in the chain starts with a '_'.
Guido van Rossumd0641422005-02-03 15:01:24 +0000128
129 If the optional allow_dotted_names argument is false, dots are not
130 supported and this function operates similar to getattr(obj, attr).
Fredrik Lundhb329b712001-09-17 17:35:21 +0000131 """
132
Guido van Rossumd0641422005-02-03 15:01:24 +0000133 if allow_dotted_names:
134 attrs = attr.split('.')
135 else:
136 attrs = [attr]
137
138 for i in attrs:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000139 if i.startswith('_'):
140 raise AttributeError(
141 'attempt to access private attribute "%s"' % i
142 )
143 else:
144 obj = getattr(obj,i)
145 return obj
Fredrik Lundhb329b712001-09-17 17:35:21 +0000146
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000147def list_public_methods(obj):
148 """Returns a list of attribute strings, found in the specified
149 object, which represent callable attributes"""
150
151 return [member for member in dir(obj)
152 if not member.startswith('_') and
Florent Xicluna5d1155c2011-10-28 14:45:05 +0200153 callable(getattr(obj, member))]
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000154
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000155class SimpleXMLRPCDispatcher:
156 """Mix-in class that dispatches XML-RPC requests.
157
158 This class is used to register XML-RPC method handlers
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000159 and then to dispatch them. This class doesn't need to be
160 instanced directly when used by SimpleXMLRPCServer but it
161 can be instanced when used by the MultiPathXMLRPCServer
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000162 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000163
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100164 def __init__(self, allow_none=False, encoding=None,
165 use_builtin_types=False):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000166 self.funcs = {}
167 self.instance = None
Andrew M. Kuchling10a16de2005-12-04 16:34:40 +0000168 self.allow_none = allow_none
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000169 self.encoding = encoding or 'utf-8'
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100170 self.use_builtin_types = use_builtin_types
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000171
Guido van Rossumd0641422005-02-03 15:01:24 +0000172 def register_instance(self, instance, allow_dotted_names=False):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000173 """Registers an instance to respond to XML-RPC requests.
174
175 Only one instance can be installed at a time.
176
177 If the registered instance has a _dispatch method then that
178 method will be called with the name of the XML-RPC method and
Georg Brandl7eb4b7d2005-07-22 21:49:32 +0000179 its parameters as a tuple
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000180 e.g. instance._dispatch('add',(2,3))
181
182 If the registered instance does not have a _dispatch method
183 then the instance will be searched to find a matching method
184 and, if found, will be called. Methods beginning with an '_'
185 are considered private and will not be called by
186 SimpleXMLRPCServer.
187
Martin Panter204bf0b2016-07-11 07:51:37 +0000188 If a registered function matches an XML-RPC request, then it
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000189 will be called instead of the registered instance.
Guido van Rossumd0641422005-02-03 15:01:24 +0000190
191 If the optional allow_dotted_names argument is true and the
192 instance does not have a _dispatch method, method names
193 containing dots are supported and resolved, as long as none of
194 the name segments start with an '_'.
195
196 *** SECURITY WARNING: ***
197
198 Enabling the allow_dotted_names options allows intruders
199 to access your module's global variables and may allow
200 intruders to execute arbitrary code on your machine. Only
201 use this option on a secure, closed network.
202
Fredrik Lundhb329b712001-09-17 17:35:21 +0000203 """
204
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000205 self.instance = instance
Guido van Rossumd0641422005-02-03 15:01:24 +0000206 self.allow_dotted_names = allow_dotted_names
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000207
Xiang Zhang267b9d22017-02-28 17:12:52 +0800208 def register_function(self, function=None, name=None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000209 """Registers a function to respond to XML-RPC requests.
210
211 The optional name argument can be used to set a Unicode name
212 for the function.
213 """
Xiang Zhang267b9d22017-02-28 17:12:52 +0800214 # decorator factory
215 if function is None:
216 return partial(self.register_function, name=name)
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000217
218 if name is None:
219 name = function.__name__
220 self.funcs[name] = function
221
Xiang Zhang267b9d22017-02-28 17:12:52 +0800222 return function
223
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000224 def register_introspection_functions(self):
225 """Registers the XML-RPC introspection methods in the system
226 namespace.
227
228 see http://xmlrpc.usefulinc.com/doc/reserved.html
229 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000230
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000231 self.funcs.update({'system.listMethods' : self.system_listMethods,
232 'system.methodSignature' : self.system_methodSignature,
233 'system.methodHelp' : self.system_methodHelp})
234
235 def register_multicall_functions(self):
236 """Registers the XML-RPC multicall method in the system
237 namespace.
238
239 see http://www.xmlrpc.com/discuss/msgReader$1208"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000240
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000241 self.funcs.update({'system.multicall' : self.system_multicall})
Tim Peters2c60f7a2003-01-29 03:49:43 +0000242
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000243 def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000244 """Dispatches an XML-RPC method from marshalled (XML) data.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000245
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000246 XML-RPC methods are dispatched from the marshalled (XML) data
247 using the _dispatch method and the result is returned as
248 marshalled data. For backwards compatibility, a dispatch
Tim Peters2c60f7a2003-01-29 03:49:43 +0000249 function can be provided as an argument (see comment in
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000250 SimpleXMLRPCRequestHandler.do_POST) but overriding the
Ezio Melotti13925002011-03-16 11:05:33 +0200251 existing method through subclassing is the preferred means
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000252 of changing method dispatch behavior.
253 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000254
Fredrik Lundhb329b712001-09-17 17:35:21 +0000255 try:
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100256 params, method = loads(data, use_builtin_types=self.use_builtin_types)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000257
258 # generate response
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000259 if dispatch_method is not None:
260 response = dispatch_method(method, params)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000261 else:
Fredrik Lundhb329b712001-09-17 17:35:21 +0000262 response = self._dispatch(method, params)
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000263 # wrap response in a singleton tuple
264 response = (response,)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000265 response = dumps(response, methodresponse=1,
266 allow_none=self.allow_none, encoding=self.encoding)
Guido van Rossumb940e112007-01-10 16:19:56 +0000267 except Fault as fault:
Georg Brandl38eceaa2008-05-26 11:14:17 +0000268 response = dumps(fault, allow_none=self.allow_none,
269 encoding=self.encoding)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000270 except:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000271 # report exception back to server
Thomas Wouters89f507f2006-12-13 04:49:30 +0000272 exc_type, exc_value, exc_tb = sys.exc_info()
Victor Stinner84524452017-08-21 18:12:58 +0200273 try:
274 response = dumps(
275 Fault(1, "%s:%s" % (exc_type, exc_value)),
276 encoding=self.encoding, allow_none=self.allow_none,
277 )
278 finally:
279 # Break reference cycle
280 exc_type = exc_value = exc_tb = None
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000281
Serhiy Storchakaaebb6d32016-01-20 10:34:27 +0200282 return response.encode(self.encoding, 'xmlcharrefreplace')
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000283
284 def system_listMethods(self):
285 """system.listMethods() => ['add', 'subtract', 'multiple']
286
287 Returns a list of the methods supported by the server."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000288
Hye-Shik Chang96042862007-08-19 10:49:11 +0000289 methods = set(self.funcs.keys())
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000290 if self.instance is not None:
291 # Instance can implement _listMethod to return a list of
292 # methods
293 if hasattr(self.instance, '_listMethods'):
Hye-Shik Chang96042862007-08-19 10:49:11 +0000294 methods |= set(self.instance._listMethods())
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000295 # if the instance has a _dispatch method then we
296 # don't have enough information to provide a list
297 # of methods
298 elif not hasattr(self.instance, '_dispatch'):
Hye-Shik Chang96042862007-08-19 10:49:11 +0000299 methods |= set(list_public_methods(self.instance))
300 return sorted(methods)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000301
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000302 def system_methodSignature(self, method_name):
303 """system.methodSignature('add') => [double, int, int]
304
Brett Cannonb9b5f162004-10-03 23:21:44 +0000305 Returns a list describing the signature of the method. In the
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000306 above example, the add method takes two integers as arguments
307 and returns a double result.
308
309 This server does NOT support system.methodSignature."""
310
311 # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
Tim Peters2c60f7a2003-01-29 03:49:43 +0000312
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000313 return 'signatures not supported'
314
315 def system_methodHelp(self, method_name):
316 """system.methodHelp('add') => "Adds two integers together"
317
318 Returns a string containing documentation for the specified method."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000319
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000320 method = None
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000321 if method_name in self.funcs:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000322 method = self.funcs[method_name]
323 elif self.instance is not None:
324 # Instance can implement _methodHelp to return help for a method
325 if hasattr(self.instance, '_methodHelp'):
326 return self.instance._methodHelp(method_name)
327 # if the instance has a _dispatch method then we
328 # don't have enough information to provide help
329 elif not hasattr(self.instance, '_dispatch'):
330 try:
331 method = resolve_dotted_attribute(
332 self.instance,
Guido van Rossumd0641422005-02-03 15:01:24 +0000333 method_name,
334 self.allow_dotted_names
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000335 )
336 except AttributeError:
337 pass
338
339 # Note that we aren't checking that the method actually
340 # be a callable object of some kind
341 if method is None:
342 return ""
Fredrik Lundhb329b712001-09-17 17:35:21 +0000343 else:
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)])
Guido van Rossumb940e112007-01-10 16:19:56 +0000365 except Fault as fault:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000366 results.append(
367 {'faultCode' : fault.faultCode,
368 'faultString' : fault.faultString}
369 )
370 except:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000371 exc_type, exc_value, exc_tb = sys.exc_info()
Victor Stinner84524452017-08-21 18:12:58 +0200372 try:
373 results.append(
374 {'faultCode' : 1,
375 'faultString' : "%s:%s" % (exc_type, exc_value)}
376 )
377 finally:
378 # Break reference cycle
379 exc_type = exc_value = exc_tb = None
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000380 return results
Tim Peters2c60f7a2003-01-29 03:49:43 +0000381
Fredrik Lundhb329b712001-09-17 17:35:21 +0000382 def _dispatch(self, method, params):
383 """Dispatches the XML-RPC method.
384
385 XML-RPC calls are forwarded to a registered function that
386 matches the called XML-RPC method name. If no such function
387 exists then the call is forwarded to the registered instance,
388 if available.
389
390 If the registered instance has a _dispatch method then that
391 method will be called with the name of the XML-RPC method and
Georg Brandl7eb4b7d2005-07-22 21:49:32 +0000392 its parameters as a tuple
Fredrik Lundhb329b712001-09-17 17:35:21 +0000393 e.g. instance._dispatch('add',(2,3))
394
395 If the registered instance does not have a _dispatch method
396 then the instance will be searched to find a matching method
397 and, if found, will be called.
398
399 Methods beginning with an '_' are considered private and will
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000400 not be called.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000401 """
402
Fredrik Lundhb329b712001-09-17 17:35:21 +0000403 try:
Petr Motejlek3c6314c2017-03-01 18:21:28 +0100404 # call the matching registered function
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000405 func = self.funcs[method]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000406 except KeyError:
Petr Motejlek3c6314c2017-03-01 18:21:28 +0100407 pass
Fredrik Lundhb329b712001-09-17 17:35:21 +0000408 else:
Petr Motejlek3c6314c2017-03-01 18:21:28 +0100409 if func is not None:
410 return func(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000411 raise Exception('method "%s" is not supported' % method)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000412
Petr Motejlek3c6314c2017-03-01 18:21:28 +0100413 if self.instance is not None:
414 if hasattr(self.instance, '_dispatch'):
415 # call the `_dispatch` method on the instance
416 return self.instance._dispatch(method, params)
417
418 # call the instance's method directly
419 try:
420 func = resolve_dotted_attribute(
421 self.instance,
422 method,
423 self.allow_dotted_names
424 )
425 except AttributeError:
426 pass
427 else:
428 if func is not None:
429 return func(*params)
430
431 raise Exception('method "%s" is not supported' % method)
432
Georg Brandl24420152008-05-26 16:32:26 +0000433class SimpleXMLRPCRequestHandler(BaseHTTPRequestHandler):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000434 """Simple XML-RPC request handler class.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000435
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000436 Handles all HTTP POST requests and attempts to decode them as
437 XML-RPC requests.
438 """
439
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000440 # Class attribute listing the accessible path components;
441 # paths not on this list will result in a 404 error.
442 rpc_paths = ('/', '/RPC2')
443
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000444 #if not None, encode responses larger than this, if possible
445 encode_threshold = 1400 #a common MTU
446
447 #Override form StreamRequestHandler: full buffering of output
448 #and no Nagle.
449 wbufsize = -1
450 disable_nagle_algorithm = True
451
452 # a re to match a gzip Accept-Encoding
453 aepattern = re.compile(r"""
454 \s* ([^\s;]+) \s* #content-coding
455 (;\s* q \s*=\s* ([0-9\.]+))? #q
456 """, re.VERBOSE | re.IGNORECASE)
457
458 def accept_encodings(self):
459 r = {}
460 ae = self.headers.get("Accept-Encoding", "")
461 for e in ae.split(","):
462 match = self.aepattern.match(e)
463 if match:
464 v = match.group(3)
465 v = float(v) if v else 1.0
466 r[match.group(1)] = v
467 return r
468
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000469 def is_rpc_path_valid(self):
470 if self.rpc_paths:
471 return self.path in self.rpc_paths
472 else:
473 # If .rpc_paths is empty, just assume all paths are legal
474 return True
475
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000476 def do_POST(self):
477 """Handles the HTTP POST request.
478
479 Attempts to interpret all HTTP POST requests as XML-RPC calls,
480 which are forwarded to the server's _dispatch method for handling.
481 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000482
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000483 # Check that the path is legal
484 if not self.is_rpc_path_valid():
485 self.report_404()
486 return
487
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000488 try:
Tim Peters536cf992005-12-25 23:18:31 +0000489 # Get arguments by reading body of request.
490 # We read this in chunks to avoid straining
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000491 # socket.read(); around the 10 or 15Mb mark, some platforms
492 # begin to have problems (bug #792570).
493 max_chunk_size = 10*1024*1024
494 size_remaining = int(self.headers["content-length"])
495 L = []
496 while size_remaining:
497 chunk_size = min(size_remaining, max_chunk_size)
Charles-François Nataliec1712a2012-02-18 14:42:57 +0100498 chunk = self.rfile.read(chunk_size)
499 if not chunk:
500 break
501 L.append(chunk)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000502 size_remaining -= len(L[-1])
Hye-Shik Chang96042862007-08-19 10:49:11 +0000503 data = b''.join(L)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000504
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000505 data = self.decode_request_content(data)
506 if data is None:
507 return #response has been sent
508
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000509 # In previous versions of SimpleXMLRPCServer, _dispatch
510 # could be overridden in this class, instead of in
511 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
512 # check to see if a subclass implements _dispatch and dispatch
513 # using that method if present.
514 response = self.server._marshaled_dispatch(
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000515 data, getattr(self, '_dispatch', None), self.path
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000516 )
Guido van Rossum61e21b52007-08-20 19:06:03 +0000517 except Exception as e: # This should only happen if the module is buggy
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000518 # internal error, report as HTTP server error
519 self.send_response(500)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000520
521 # Send information about the exception if requested
522 if hasattr(self.server, '_send_traceback_header') and \
523 self.server._send_traceback_header:
524 self.send_header("X-exception", str(e))
Victor Stinner5bfe1462010-04-16 13:28:05 +0000525 trace = traceback.format_exc()
526 trace = str(trace.encode('ASCII', 'backslashreplace'), 'ASCII')
527 self.send_header("X-traceback", trace)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000528
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000529 self.send_header("Content-length", "0")
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000530 self.end_headers()
531 else:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000532 self.send_response(200)
533 self.send_header("Content-type", "text/xml")
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000534 if self.encode_threshold is not None:
535 if len(response) > self.encode_threshold:
536 q = self.accept_encodings().get("gzip", 0)
537 if q:
Kristján Valur Jónssonaefde242009-07-19 22:29:24 +0000538 try:
539 response = gzip_encode(response)
540 self.send_header("Content-Encoding", "gzip")
541 except NotImplementedError:
542 pass
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000543 self.send_header("Content-length", str(len(response)))
544 self.end_headers()
545 self.wfile.write(response)
546
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000547 def decode_request_content(self, data):
548 #support gzip encoding of request
549 encoding = self.headers.get("content-encoding", "identity").lower()
550 if encoding == "identity":
551 return data
552 if encoding == "gzip":
553 try:
554 return gzip_decode(data)
Kristján Valur Jónssonaefde242009-07-19 22:29:24 +0000555 except NotImplementedError:
556 self.send_response(501, "encoding %r not supported" % encoding)
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000557 except ValueError:
558 self.send_response(400, "error decoding gzip content")
559 else:
560 self.send_response(501, "encoding %r not supported" % encoding)
561 self.send_header("Content-length", "0")
562 self.end_headers()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000563
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000564 def report_404 (self):
565 # Report a 404 error
566 self.send_response(404)
Christian Heimes0aa93cd2007-12-08 18:38:20 +0000567 response = b'No such page'
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000568 self.send_header("Content-type", "text/plain")
569 self.send_header("Content-length", str(len(response)))
570 self.end_headers()
571 self.wfile.write(response)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000572
Fredrik Lundhb329b712001-09-17 17:35:21 +0000573 def log_request(self, code='-', size='-'):
574 """Selectively log an accepted request."""
575
576 if self.server.logRequests:
Georg Brandl24420152008-05-26 16:32:26 +0000577 BaseHTTPRequestHandler.log_request(self, code, size)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000578
Alexandre Vassalottice261952008-05-12 02:31:37 +0000579class SimpleXMLRPCServer(socketserver.TCPServer,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000580 SimpleXMLRPCDispatcher):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000581 """Simple XML-RPC server.
582
583 Simple XML-RPC server that allows functions and a single instance
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000584 to be installed to handle requests. The default implementation
585 attempts to dispatch XML-RPC calls to the functions or instance
Florent Xiclunac4fec932011-10-30 20:19:32 +0100586 installed in the server. Override the _dispatch method inherited
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000587 from SimpleXMLRPCDispatcher to change this behavior.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000588 """
589
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000590 allow_reuse_address = True
591
Guido van Rossum61e21b52007-08-20 19:06:03 +0000592 # Warning: this is for debugging purposes only! Never set this to True in
593 # production code, as will be sending out sensitive information (exception
594 # and stack trace details) when exceptions are raised inside
595 # SimpleXMLRPCRequestHandler.do_POST
596 _send_traceback_header = False
597
Fredrik Lundhb329b712001-09-17 17:35:21 +0000598 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100599 logRequests=True, allow_none=False, encoding=None,
600 bind_and_activate=True, use_builtin_types=False):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000601 self.logRequests = logRequests
Tim Peters2c60f7a2003-01-29 03:49:43 +0000602
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100603 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
Alexandre Vassalottice261952008-05-12 02:31:37 +0000604 socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000605
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000606
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000607class MultiPathXMLRPCServer(SimpleXMLRPCServer):
608 """Multipath XML-RPC Server
609 This specialization of SimpleXMLRPCServer allows the user to create
610 multiple Dispatcher instances and assign them to different
611 HTTP request paths. This makes it possible to run two or more
612 'virtual XML-RPC servers' at the same port.
613 Make sure that the requestHandler accepts the paths in question.
614 """
615 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100616 logRequests=True, allow_none=False, encoding=None,
617 bind_and_activate=True, use_builtin_types=False):
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000618
619 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100620 encoding, bind_and_activate, use_builtin_types)
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000621 self.dispatchers = {}
622 self.allow_none = allow_none
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100623 self.encoding = encoding or 'utf-8'
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000624
625 def add_dispatcher(self, path, dispatcher):
626 self.dispatchers[path] = dispatcher
627 return dispatcher
628
629 def get_dispatcher(self, path):
630 return self.dispatchers[path]
631
632 def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
633 try:
634 response = self.dispatchers[path]._marshaled_dispatch(
635 data, dispatch_method, path)
636 except:
637 # report low level exception back to server
638 # (each dispatcher should have handled their own
639 # exceptions)
640 exc_type, exc_value = sys.exc_info()[:2]
Victor Stinner84524452017-08-21 18:12:58 +0200641 try:
642 response = dumps(
643 Fault(1, "%s:%s" % (exc_type, exc_value)),
644 encoding=self.encoding, allow_none=self.allow_none)
645 response = response.encode(self.encoding, 'xmlcharrefreplace')
646 finally:
647 # Break reference cycle
648 exc_type = exc_value = None
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000649 return response
650
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000651class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
652 """Simple handler for XML-RPC data passed through CGI."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000653
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100654 def __init__(self, allow_none=False, encoding=None, use_builtin_types=False):
655 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000656
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000657 def handle_xmlrpc(self, request_text):
658 """Handle a single XML-RPC request"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000659
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000660 response = self._marshaled_dispatch(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000661
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000662 print('Content-Type: text/xml')
663 print('Content-Length: %d' % len(response))
664 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000665 sys.stdout.flush()
666 sys.stdout.buffer.write(response)
667 sys.stdout.buffer.flush()
Fredrik Lundhb329b712001-09-17 17:35:21 +0000668
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000669 def handle_get(self):
670 """Handle a single HTTP GET request.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000671
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000672 Default implementation indicates an error because
673 XML-RPC uses the POST method.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000674 """
675
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000676 code = 400
Georg Brandl24420152008-05-26 16:32:26 +0000677 message, explain = BaseHTTPRequestHandler.responses[code]
Tim Peters2c60f7a2003-01-29 03:49:43 +0000678
Georg Brandl24420152008-05-26 16:32:26 +0000679 response = http.server.DEFAULT_ERROR_MESSAGE % \
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000680 {
Tim Peters2c60f7a2003-01-29 03:49:43 +0000681 'code' : code,
682 'message' : message,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000683 'explain' : explain
684 }
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000685 response = response.encode('utf-8')
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000686 print('Status: %d %s' % (code, message))
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000687 print('Content-Type: %s' % http.server.DEFAULT_ERROR_CONTENT_TYPE)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000688 print('Content-Length: %d' % len(response))
689 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000690 sys.stdout.flush()
691 sys.stdout.buffer.write(response)
692 sys.stdout.buffer.flush()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000693
Georg Brandlfe991052009-09-16 15:54:04 +0000694 def handle_request(self, request_text=None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000695 """Handle a single XML-RPC request passed through a CGI post method.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000696
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000697 If no XML data is given then it is read from stdin. The resulting
698 XML-RPC response is printed to stdout along with the correct HTTP
699 headers.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000700 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000701
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000702 if request_text is None and \
703 os.environ.get('REQUEST_METHOD', None) == 'GET':
704 self.handle_get()
705 else:
706 # POST data is normally available through stdin
Georg Brandl99412e52009-04-01 04:27:47 +0000707 try:
708 length = int(os.environ.get('CONTENT_LENGTH', None))
Georg Brandlc7485062009-04-01 15:53:15 +0000709 except (ValueError, TypeError):
Georg Brandl99412e52009-04-01 04:27:47 +0000710 length = -1
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000711 if request_text is None:
Georg Brandl99412e52009-04-01 04:27:47 +0000712 request_text = sys.stdin.read(length)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000713
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000714 self.handle_xmlrpc(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000715
Georg Brandl38eceaa2008-05-26 11:14:17 +0000716
717# -----------------------------------------------------------------------------
718# Self documenting XML-RPC Server.
719
720class ServerHTMLDoc(pydoc.HTMLDoc):
721 """Class used to generate pydoc HTML document for a server"""
722
723 def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
724 """Mark up some plain text, given a context of symbols to look for.
725 Each context dictionary maps object names to anchor names."""
726 escape = escape or self.escape
727 results = []
728 here = 0
729
730 # XXX Note that this regular expression does not allow for the
731 # hyperlinking of arbitrary strings being used as method
732 # names. Only methods with names consisting of word characters
733 # and '.'s are hyperlinked.
734 pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
735 r'RFC[- ]?(\d+)|'
736 r'PEP[- ]?(\d+)|'
737 r'(self\.)?((?:\w|\.)+))\b')
738 while 1:
739 match = pattern.search(text, here)
740 if not match: break
741 start, end = match.span()
742 results.append(escape(text[here:start]))
743
744 all, scheme, rfc, pep, selfdot, name = match.groups()
745 if scheme:
746 url = escape(all).replace('"', '"')
747 results.append('<a href="%s">%s</a>' % (url, url))
748 elif rfc:
749 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
750 results.append('<a href="%s">%s</a>' % (url, escape(all)))
751 elif pep:
752 url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
753 results.append('<a href="%s">%s</a>' % (url, escape(all)))
754 elif text[end:end+1] == '(':
755 results.append(self.namelink(name, methods, funcs, classes))
756 elif selfdot:
757 results.append('self.<strong>%s</strong>' % name)
758 else:
759 results.append(self.namelink(name, classes))
760 here = end
761 results.append(escape(text[here:]))
762 return ''.join(results)
763
764 def docroutine(self, object, name, mod=None,
765 funcs={}, classes={}, methods={}, cl=None):
766 """Produce HTML documentation for a function or method object."""
767
768 anchor = (cl and cl.__name__ or '') + '-' + name
769 note = ''
770
771 title = '<a name="%s"><strong>%s</strong></a>' % (
772 self.escape(anchor), self.escape(name))
773
774 if inspect.ismethod(object):
R David Murrayf22b62e2013-08-10 12:01:47 -0400775 args = inspect.getfullargspec(object)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000776 # exclude the argument bound to the instance, it will be
777 # confusing to the non-Python user
778 argspec = inspect.formatargspec (
R David Murrayf22b62e2013-08-10 12:01:47 -0400779 args.args[1:],
780 args.varargs,
781 args.varkw,
782 args.defaults,
783 annotations=args.annotations,
Georg Brandl38eceaa2008-05-26 11:14:17 +0000784 formatvalue=self.formatvalue
785 )
786 elif inspect.isfunction(object):
R David Murrayf22b62e2013-08-10 12:01:47 -0400787 args = inspect.getfullargspec(object)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000788 argspec = inspect.formatargspec(
R David Murrayf22b62e2013-08-10 12:01:47 -0400789 args.args, args.varargs, args.varkw, args.defaults,
790 annotations=args.annotations,
791 formatvalue=self.formatvalue)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000792 else:
793 argspec = '(...)'
794
795 if isinstance(object, tuple):
796 argspec = object[0] or argspec
797 docstring = object[1] or ""
798 else:
799 docstring = pydoc.getdoc(object)
800
801 decl = title + argspec + (note and self.grey(
802 '<font face="helvetica, arial">%s</font>' % note))
803
804 doc = self.markup(
805 docstring, self.preformat, funcs, classes, methods)
806 doc = doc and '<dd><tt>%s</tt></dd>' % doc
807 return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
808
809 def docserver(self, server_name, package_documentation, methods):
810 """Produce HTML documentation for an XML-RPC server."""
811
812 fdict = {}
813 for key, value in methods.items():
814 fdict[key] = '#-' + key
815 fdict[value] = fdict[key]
816
817 server_name = self.escape(server_name)
818 head = '<big><big><strong>%s</strong></big></big>' % server_name
819 result = self.heading(head, '#ffffff', '#7799ee')
820
821 doc = self.markup(package_documentation, self.preformat, fdict)
822 doc = doc and '<tt>%s</tt>' % doc
823 result = result + '<p>%s</p>\n' % doc
824
825 contents = []
826 method_items = sorted(methods.items())
827 for key, value in method_items:
828 contents.append(self.docroutine(value, key, funcs=fdict))
829 result = result + self.bigsection(
830 'Methods', '#ffffff', '#eeaa77', ''.join(contents))
831
832 return result
833
834class XMLRPCDocGenerator:
835 """Generates documentation for an XML-RPC server.
836
837 This class is designed as mix-in and should not
838 be constructed directly.
839 """
840
841 def __init__(self):
842 # setup variables used for HTML documentation
843 self.server_name = 'XML-RPC Server Documentation'
844 self.server_documentation = \
845 "This server exports the following methods through the XML-RPC "\
846 "protocol."
847 self.server_title = 'XML-RPC Server Documentation'
848
849 def set_server_title(self, server_title):
850 """Set the HTML title of the generated server documentation"""
851
852 self.server_title = server_title
853
854 def set_server_name(self, server_name):
855 """Set the name of the generated HTML server documentation"""
856
857 self.server_name = server_name
858
859 def set_server_documentation(self, server_documentation):
860 """Set the documentation string for the entire server."""
861
862 self.server_documentation = server_documentation
863
864 def generate_html_documentation(self):
865 """generate_html_documentation() => html documentation for the server
866
867 Generates HTML documentation for the server using introspection for
868 installed functions and instances that do not implement the
869 _dispatch method. Alternatively, instances can choose to implement
870 the _get_method_argstring(method_name) method to provide the
871 argument string used in the documentation and the
872 _methodHelp(method_name) method to provide the help text used
873 in the documentation."""
874
875 methods = {}
876
877 for method_name in self.system_listMethods():
878 if method_name in self.funcs:
879 method = self.funcs[method_name]
880 elif self.instance is not None:
881 method_info = [None, None] # argspec, documentation
882 if hasattr(self.instance, '_get_method_argstring'):
883 method_info[0] = self.instance._get_method_argstring(method_name)
884 if hasattr(self.instance, '_methodHelp'):
885 method_info[1] = self.instance._methodHelp(method_name)
886
887 method_info = tuple(method_info)
888 if method_info != (None, None):
889 method = method_info
890 elif not hasattr(self.instance, '_dispatch'):
891 try:
892 method = resolve_dotted_attribute(
893 self.instance,
894 method_name
895 )
896 except AttributeError:
897 method = method_info
898 else:
899 method = method_info
900 else:
901 assert 0, "Could not find method in self.functions and no "\
902 "instance installed"
903
904 methods[method_name] = method
905
906 documenter = ServerHTMLDoc()
907 documentation = documenter.docserver(
908 self.server_name,
909 self.server_documentation,
910 methods
911 )
912
913 return documenter.page(self.server_title, documentation)
914
915class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
916 """XML-RPC and documentation request handler class.
917
918 Handles all HTTP POST requests and attempts to decode them as
919 XML-RPC requests.
920
921 Handles all HTTP GET requests and interprets them as requests
922 for documentation.
923 """
924
925 def do_GET(self):
926 """Handles the HTTP GET request.
927
928 Interpret all HTTP GET requests as requests for server
929 documentation.
930 """
931 # Check that the path is legal
932 if not self.is_rpc_path_valid():
933 self.report_404()
934 return
935
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000936 response = self.server.generate_html_documentation().encode('utf-8')
Georg Brandl38eceaa2008-05-26 11:14:17 +0000937 self.send_response(200)
938 self.send_header("Content-type", "text/html")
939 self.send_header("Content-length", str(len(response)))
940 self.end_headers()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000941 self.wfile.write(response)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000942
Georg Brandl38eceaa2008-05-26 11:14:17 +0000943class DocXMLRPCServer( SimpleXMLRPCServer,
944 XMLRPCDocGenerator):
945 """XML-RPC and HTML documentation server.
946
947 Adds the ability to serve server documentation to the capabilities
948 of SimpleXMLRPCServer.
949 """
950
951 def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
Georg Brandlfe991052009-09-16 15:54:04 +0000952 logRequests=True, allow_none=False, encoding=None,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100953 bind_and_activate=True, use_builtin_types=False):
Georg Brandl38eceaa2008-05-26 11:14:17 +0000954 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100955 allow_none, encoding, bind_and_activate,
956 use_builtin_types)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000957 XMLRPCDocGenerator.__init__(self)
958
959class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,
960 XMLRPCDocGenerator):
961 """Handler for XML-RPC data and documentation requests passed through
962 CGI"""
963
964 def handle_get(self):
965 """Handles the HTTP GET request.
966
967 Interpret all HTTP GET requests as requests for server
968 documentation.
969 """
970
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000971 response = self.generate_html_documentation().encode('utf-8')
Georg Brandl38eceaa2008-05-26 11:14:17 +0000972
973 print('Content-Type: text/html')
974 print('Content-Length: %d' % len(response))
975 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000976 sys.stdout.flush()
977 sys.stdout.buffer.write(response)
978 sys.stdout.buffer.flush()
Georg Brandl38eceaa2008-05-26 11:14:17 +0000979
980 def __init__(self):
981 CGIXMLRPCRequestHandler.__init__(self)
982 XMLRPCDocGenerator.__init__(self)
983
984
Fredrik Lundhb329b712001-09-17 17:35:21 +0000985if __name__ == '__main__':
Senthil Kumaran939e2db2014-01-12 16:06:58 -0800986 import datetime
987
988 class ExampleService:
989 def getData(self):
990 return '42'
991
992 class currentTime:
993 @staticmethod
994 def getCurrentTime():
995 return datetime.datetime.now()
996
Martin Panter0cab9c12016-04-13 00:36:52 +0000997 with SimpleXMLRPCServer(("localhost", 8000)) as server:
998 server.register_function(pow)
999 server.register_function(lambda x,y: x+y, 'add')
1000 server.register_instance(ExampleService(), allow_dotted_names=True)
1001 server.register_multicall_functions()
1002 print('Serving XML-RPC on localhost port 8000')
1003 print('It is advisable to run this example server within a secure, closed network.')
1004 try:
1005 server.serve_forever()
1006 except KeyboardInterrupt:
1007 print("\nKeyboard interrupt received, exiting.")
1008 sys.exit(0)