blob: f1c467eb1b2b87c2e76a46b745985831d67c7acb [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
Nicolas Noé35c08092018-07-16 10:46:04 +0200110from inspect import signature
Georg Brandl24420152008-05-26 16:32:26 +0000111import http.server
Alexandre Vassalottice261952008-05-12 02:31:37 +0000112import socketserver
Fredrik Lundhb329b712001-09-17 17:35:21 +0000113import sys
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000114import os
Georg Brandl38eceaa2008-05-26 11:14:17 +0000115import re
116import pydoc
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
Nicolas Noé35c08092018-07-16 10:46:04 +0200774 if callable(object):
775 argspec = str(signature(object))
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
Martin Panter0cab9c12016-04-13 00:36:52 +0000981 with SimpleXMLRPCServer(("localhost", 8000)) as server:
982 server.register_function(pow)
983 server.register_function(lambda x,y: x+y, 'add')
984 server.register_instance(ExampleService(), allow_dotted_names=True)
985 server.register_multicall_functions()
986 print('Serving XML-RPC on localhost port 8000')
987 print('It is advisable to run this example server within a secure, closed network.')
988 try:
989 server.serve_forever()
990 except KeyboardInterrupt:
991 print("\nKeyboard interrupt received, exiting.")
992 sys.exit(0)