blob: bb86fe6872aec35219c1a6a74a091ab96cdce9f9 [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()
Georg Brandl38eceaa2008-05-26 11:14:17 +0000273 response = dumps(
274 Fault(1, "%s:%s" % (exc_type, exc_value)),
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000275 encoding=self.encoding, allow_none=self.allow_none,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000276 )
277
Serhiy Storchakaaebb6d32016-01-20 10:34:27 +0200278 return response.encode(self.encoding, 'xmlcharrefreplace')
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000279
280 def system_listMethods(self):
281 """system.listMethods() => ['add', 'subtract', 'multiple']
282
283 Returns a list of the methods supported by the server."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000284
Hye-Shik Chang96042862007-08-19 10:49:11 +0000285 methods = set(self.funcs.keys())
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000286 if self.instance is not None:
287 # Instance can implement _listMethod to return a list of
288 # methods
289 if hasattr(self.instance, '_listMethods'):
Hye-Shik Chang96042862007-08-19 10:49:11 +0000290 methods |= set(self.instance._listMethods())
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000291 # if the instance has a _dispatch method then we
292 # don't have enough information to provide a list
293 # of methods
294 elif not hasattr(self.instance, '_dispatch'):
Hye-Shik Chang96042862007-08-19 10:49:11 +0000295 methods |= set(list_public_methods(self.instance))
296 return sorted(methods)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000297
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000298 def system_methodSignature(self, method_name):
299 """system.methodSignature('add') => [double, int, int]
300
Brett Cannonb9b5f162004-10-03 23:21:44 +0000301 Returns a list describing the signature of the method. In the
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000302 above example, the add method takes two integers as arguments
303 and returns a double result.
304
305 This server does NOT support system.methodSignature."""
306
307 # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
Tim Peters2c60f7a2003-01-29 03:49:43 +0000308
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000309 return 'signatures not supported'
310
311 def system_methodHelp(self, method_name):
312 """system.methodHelp('add') => "Adds two integers together"
313
314 Returns a string containing documentation for the specified method."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000315
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000316 method = None
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000317 if method_name in self.funcs:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000318 method = self.funcs[method_name]
319 elif self.instance is not None:
320 # Instance can implement _methodHelp to return help for a method
321 if hasattr(self.instance, '_methodHelp'):
322 return self.instance._methodHelp(method_name)
323 # if the instance has a _dispatch method then we
324 # don't have enough information to provide help
325 elif not hasattr(self.instance, '_dispatch'):
326 try:
327 method = resolve_dotted_attribute(
328 self.instance,
Guido van Rossumd0641422005-02-03 15:01:24 +0000329 method_name,
330 self.allow_dotted_names
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000331 )
332 except AttributeError:
333 pass
334
335 # Note that we aren't checking that the method actually
336 # be a callable object of some kind
337 if method is None:
338 return ""
Fredrik Lundhb329b712001-09-17 17:35:21 +0000339 else:
Neal Norwitz3f401f02003-06-29 04:19:37 +0000340 return pydoc.getdoc(method)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000341
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000342 def system_multicall(self, call_list):
343 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
344[[4], ...]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000345
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000346 Allows the caller to package multiple XML-RPC calls into a single
347 request.
348
Tim Peters2c60f7a2003-01-29 03:49:43 +0000349 See http://www.xmlrpc.com/discuss/msgReader$1208
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000350 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000351
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000352 results = []
353 for call in call_list:
354 method_name = call['methodName']
355 params = call['params']
356
357 try:
358 # XXX A marshalling error in any response will fail the entire
359 # multicall. If someone cares they should fix this.
360 results.append([self._dispatch(method_name, params)])
Guido van Rossumb940e112007-01-10 16:19:56 +0000361 except Fault as fault:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000362 results.append(
363 {'faultCode' : fault.faultCode,
364 'faultString' : fault.faultString}
365 )
366 except:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000367 exc_type, exc_value, exc_tb = sys.exc_info()
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000368 results.append(
369 {'faultCode' : 1,
Thomas Wouters89f507f2006-12-13 04:49:30 +0000370 'faultString' : "%s:%s" % (exc_type, exc_value)}
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000371 )
372 return results
Tim Peters2c60f7a2003-01-29 03:49:43 +0000373
Fredrik Lundhb329b712001-09-17 17:35:21 +0000374 def _dispatch(self, method, params):
375 """Dispatches the XML-RPC method.
376
377 XML-RPC calls are forwarded to a registered function that
378 matches the called XML-RPC method name. If no such function
379 exists then the call is forwarded to the registered instance,
380 if available.
381
382 If the registered instance has a _dispatch method then that
383 method will be called with the name of the XML-RPC method and
Georg Brandl7eb4b7d2005-07-22 21:49:32 +0000384 its parameters as a tuple
Fredrik Lundhb329b712001-09-17 17:35:21 +0000385 e.g. instance._dispatch('add',(2,3))
386
387 If the registered instance does not have a _dispatch method
388 then the instance will be searched to find a matching method
389 and, if found, will be called.
390
391 Methods beginning with an '_' are considered private and will
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000392 not be called.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000393 """
394
Fredrik Lundhb329b712001-09-17 17:35:21 +0000395 try:
Petr Motejlek3c6314c2017-03-01 18:21:28 +0100396 # call the matching registered function
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000397 func = self.funcs[method]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000398 except KeyError:
Petr Motejlek3c6314c2017-03-01 18:21:28 +0100399 pass
Fredrik Lundhb329b712001-09-17 17:35:21 +0000400 else:
Petr Motejlek3c6314c2017-03-01 18:21:28 +0100401 if func is not None:
402 return func(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000403 raise Exception('method "%s" is not supported' % method)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000404
Petr Motejlek3c6314c2017-03-01 18:21:28 +0100405 if self.instance is not None:
406 if hasattr(self.instance, '_dispatch'):
407 # call the `_dispatch` method on the instance
408 return self.instance._dispatch(method, params)
409
410 # call the instance's method directly
411 try:
412 func = resolve_dotted_attribute(
413 self.instance,
414 method,
415 self.allow_dotted_names
416 )
417 except AttributeError:
418 pass
419 else:
420 if func is not None:
421 return func(*params)
422
423 raise Exception('method "%s" is not supported' % method)
424
Georg Brandl24420152008-05-26 16:32:26 +0000425class SimpleXMLRPCRequestHandler(BaseHTTPRequestHandler):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000426 """Simple XML-RPC request handler class.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000427
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000428 Handles all HTTP POST requests and attempts to decode them as
429 XML-RPC requests.
430 """
431
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000432 # Class attribute listing the accessible path components;
433 # paths not on this list will result in a 404 error.
434 rpc_paths = ('/', '/RPC2')
435
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000436 #if not None, encode responses larger than this, if possible
437 encode_threshold = 1400 #a common MTU
438
439 #Override form StreamRequestHandler: full buffering of output
440 #and no Nagle.
441 wbufsize = -1
442 disable_nagle_algorithm = True
443
444 # a re to match a gzip Accept-Encoding
445 aepattern = re.compile(r"""
446 \s* ([^\s;]+) \s* #content-coding
447 (;\s* q \s*=\s* ([0-9\.]+))? #q
448 """, re.VERBOSE | re.IGNORECASE)
449
450 def accept_encodings(self):
451 r = {}
452 ae = self.headers.get("Accept-Encoding", "")
453 for e in ae.split(","):
454 match = self.aepattern.match(e)
455 if match:
456 v = match.group(3)
457 v = float(v) if v else 1.0
458 r[match.group(1)] = v
459 return r
460
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000461 def is_rpc_path_valid(self):
462 if self.rpc_paths:
463 return self.path in self.rpc_paths
464 else:
465 # If .rpc_paths is empty, just assume all paths are legal
466 return True
467
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000468 def do_POST(self):
469 """Handles the HTTP POST request.
470
471 Attempts to interpret all HTTP POST requests as XML-RPC calls,
472 which are forwarded to the server's _dispatch method for handling.
473 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000474
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000475 # Check that the path is legal
476 if not self.is_rpc_path_valid():
477 self.report_404()
478 return
479
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000480 try:
Tim Peters536cf992005-12-25 23:18:31 +0000481 # Get arguments by reading body of request.
482 # We read this in chunks to avoid straining
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000483 # socket.read(); around the 10 or 15Mb mark, some platforms
484 # begin to have problems (bug #792570).
485 max_chunk_size = 10*1024*1024
486 size_remaining = int(self.headers["content-length"])
487 L = []
488 while size_remaining:
489 chunk_size = min(size_remaining, max_chunk_size)
Charles-François Nataliec1712a2012-02-18 14:42:57 +0100490 chunk = self.rfile.read(chunk_size)
491 if not chunk:
492 break
493 L.append(chunk)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000494 size_remaining -= len(L[-1])
Hye-Shik Chang96042862007-08-19 10:49:11 +0000495 data = b''.join(L)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000496
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000497 data = self.decode_request_content(data)
498 if data is None:
499 return #response has been sent
500
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000501 # In previous versions of SimpleXMLRPCServer, _dispatch
502 # could be overridden in this class, instead of in
503 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
504 # check to see if a subclass implements _dispatch and dispatch
505 # using that method if present.
506 response = self.server._marshaled_dispatch(
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000507 data, getattr(self, '_dispatch', None), self.path
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000508 )
Guido van Rossum61e21b52007-08-20 19:06:03 +0000509 except Exception as e: # This should only happen if the module is buggy
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000510 # internal error, report as HTTP server error
511 self.send_response(500)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000512
513 # Send information about the exception if requested
514 if hasattr(self.server, '_send_traceback_header') and \
515 self.server._send_traceback_header:
516 self.send_header("X-exception", str(e))
Victor Stinner5bfe1462010-04-16 13:28:05 +0000517 trace = traceback.format_exc()
518 trace = str(trace.encode('ASCII', 'backslashreplace'), 'ASCII')
519 self.send_header("X-traceback", trace)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000520
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000521 self.send_header("Content-length", "0")
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000522 self.end_headers()
523 else:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000524 self.send_response(200)
525 self.send_header("Content-type", "text/xml")
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000526 if self.encode_threshold is not None:
527 if len(response) > self.encode_threshold:
528 q = self.accept_encodings().get("gzip", 0)
529 if q:
Kristján Valur Jónssonaefde242009-07-19 22:29:24 +0000530 try:
531 response = gzip_encode(response)
532 self.send_header("Content-Encoding", "gzip")
533 except NotImplementedError:
534 pass
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000535 self.send_header("Content-length", str(len(response)))
536 self.end_headers()
537 self.wfile.write(response)
538
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000539 def decode_request_content(self, data):
540 #support gzip encoding of request
541 encoding = self.headers.get("content-encoding", "identity").lower()
542 if encoding == "identity":
543 return data
544 if encoding == "gzip":
545 try:
546 return gzip_decode(data)
Kristján Valur Jónssonaefde242009-07-19 22:29:24 +0000547 except NotImplementedError:
548 self.send_response(501, "encoding %r not supported" % encoding)
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000549 except ValueError:
550 self.send_response(400, "error decoding gzip content")
551 else:
552 self.send_response(501, "encoding %r not supported" % encoding)
553 self.send_header("Content-length", "0")
554 self.end_headers()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000555
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000556 def report_404 (self):
557 # Report a 404 error
558 self.send_response(404)
Christian Heimes0aa93cd2007-12-08 18:38:20 +0000559 response = b'No such page'
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000560 self.send_header("Content-type", "text/plain")
561 self.send_header("Content-length", str(len(response)))
562 self.end_headers()
563 self.wfile.write(response)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000564
Fredrik Lundhb329b712001-09-17 17:35:21 +0000565 def log_request(self, code='-', size='-'):
566 """Selectively log an accepted request."""
567
568 if self.server.logRequests:
Georg Brandl24420152008-05-26 16:32:26 +0000569 BaseHTTPRequestHandler.log_request(self, code, size)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000570
Alexandre Vassalottice261952008-05-12 02:31:37 +0000571class SimpleXMLRPCServer(socketserver.TCPServer,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000572 SimpleXMLRPCDispatcher):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000573 """Simple XML-RPC server.
574
575 Simple XML-RPC server that allows functions and a single instance
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000576 to be installed to handle requests. The default implementation
577 attempts to dispatch XML-RPC calls to the functions or instance
Florent Xiclunac4fec932011-10-30 20:19:32 +0100578 installed in the server. Override the _dispatch method inherited
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000579 from SimpleXMLRPCDispatcher to change this behavior.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000580 """
581
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000582 allow_reuse_address = True
583
Guido van Rossum61e21b52007-08-20 19:06:03 +0000584 # Warning: this is for debugging purposes only! Never set this to True in
585 # production code, as will be sending out sensitive information (exception
586 # and stack trace details) when exceptions are raised inside
587 # SimpleXMLRPCRequestHandler.do_POST
588 _send_traceback_header = False
589
Fredrik Lundhb329b712001-09-17 17:35:21 +0000590 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100591 logRequests=True, allow_none=False, encoding=None,
592 bind_and_activate=True, use_builtin_types=False):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000593 self.logRequests = logRequests
Tim Peters2c60f7a2003-01-29 03:49:43 +0000594
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100595 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
Alexandre Vassalottice261952008-05-12 02:31:37 +0000596 socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000597
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000598
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000599class MultiPathXMLRPCServer(SimpleXMLRPCServer):
600 """Multipath XML-RPC Server
601 This specialization of SimpleXMLRPCServer allows the user to create
602 multiple Dispatcher instances and assign them to different
603 HTTP request paths. This makes it possible to run two or more
604 'virtual XML-RPC servers' at the same port.
605 Make sure that the requestHandler accepts the paths in question.
606 """
607 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100608 logRequests=True, allow_none=False, encoding=None,
609 bind_and_activate=True, use_builtin_types=False):
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000610
611 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100612 encoding, bind_and_activate, use_builtin_types)
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000613 self.dispatchers = {}
614 self.allow_none = allow_none
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100615 self.encoding = encoding or 'utf-8'
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000616
617 def add_dispatcher(self, path, dispatcher):
618 self.dispatchers[path] = dispatcher
619 return dispatcher
620
621 def get_dispatcher(self, path):
622 return self.dispatchers[path]
623
624 def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
625 try:
626 response = self.dispatchers[path]._marshaled_dispatch(
627 data, dispatch_method, path)
628 except:
629 # report low level exception back to server
630 # (each dispatcher should have handled their own
631 # exceptions)
632 exc_type, exc_value = sys.exc_info()[:2]
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100633 response = dumps(
634 Fault(1, "%s:%s" % (exc_type, exc_value)),
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000635 encoding=self.encoding, allow_none=self.allow_none)
Serhiy Storchakaaebb6d32016-01-20 10:34:27 +0200636 response = response.encode(self.encoding, 'xmlcharrefreplace')
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000637 return response
638
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000639class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
640 """Simple handler for XML-RPC data passed through CGI."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000641
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100642 def __init__(self, allow_none=False, encoding=None, use_builtin_types=False):
643 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000644
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000645 def handle_xmlrpc(self, request_text):
646 """Handle a single XML-RPC request"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000647
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000648 response = self._marshaled_dispatch(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000649
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000650 print('Content-Type: text/xml')
651 print('Content-Length: %d' % len(response))
652 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000653 sys.stdout.flush()
654 sys.stdout.buffer.write(response)
655 sys.stdout.buffer.flush()
Fredrik Lundhb329b712001-09-17 17:35:21 +0000656
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000657 def handle_get(self):
658 """Handle a single HTTP GET request.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000659
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000660 Default implementation indicates an error because
661 XML-RPC uses the POST method.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000662 """
663
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000664 code = 400
Georg Brandl24420152008-05-26 16:32:26 +0000665 message, explain = BaseHTTPRequestHandler.responses[code]
Tim Peters2c60f7a2003-01-29 03:49:43 +0000666
Georg Brandl24420152008-05-26 16:32:26 +0000667 response = http.server.DEFAULT_ERROR_MESSAGE % \
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000668 {
Tim Peters2c60f7a2003-01-29 03:49:43 +0000669 'code' : code,
670 'message' : message,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000671 'explain' : explain
672 }
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000673 response = response.encode('utf-8')
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000674 print('Status: %d %s' % (code, message))
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000675 print('Content-Type: %s' % http.server.DEFAULT_ERROR_CONTENT_TYPE)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000676 print('Content-Length: %d' % len(response))
677 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000678 sys.stdout.flush()
679 sys.stdout.buffer.write(response)
680 sys.stdout.buffer.flush()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000681
Georg Brandlfe991052009-09-16 15:54:04 +0000682 def handle_request(self, request_text=None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000683 """Handle a single XML-RPC request passed through a CGI post method.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000684
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000685 If no XML data is given then it is read from stdin. The resulting
686 XML-RPC response is printed to stdout along with the correct HTTP
687 headers.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000688 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000689
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000690 if request_text is None and \
691 os.environ.get('REQUEST_METHOD', None) == 'GET':
692 self.handle_get()
693 else:
694 # POST data is normally available through stdin
Georg Brandl99412e52009-04-01 04:27:47 +0000695 try:
696 length = int(os.environ.get('CONTENT_LENGTH', None))
Georg Brandlc7485062009-04-01 15:53:15 +0000697 except (ValueError, TypeError):
Georg Brandl99412e52009-04-01 04:27:47 +0000698 length = -1
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000699 if request_text is None:
Georg Brandl99412e52009-04-01 04:27:47 +0000700 request_text = sys.stdin.read(length)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000701
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000702 self.handle_xmlrpc(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000703
Georg Brandl38eceaa2008-05-26 11:14:17 +0000704
705# -----------------------------------------------------------------------------
706# Self documenting XML-RPC Server.
707
708class ServerHTMLDoc(pydoc.HTMLDoc):
709 """Class used to generate pydoc HTML document for a server"""
710
711 def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
712 """Mark up some plain text, given a context of symbols to look for.
713 Each context dictionary maps object names to anchor names."""
714 escape = escape or self.escape
715 results = []
716 here = 0
717
718 # XXX Note that this regular expression does not allow for the
719 # hyperlinking of arbitrary strings being used as method
720 # names. Only methods with names consisting of word characters
721 # and '.'s are hyperlinked.
722 pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
723 r'RFC[- ]?(\d+)|'
724 r'PEP[- ]?(\d+)|'
725 r'(self\.)?((?:\w|\.)+))\b')
726 while 1:
727 match = pattern.search(text, here)
728 if not match: break
729 start, end = match.span()
730 results.append(escape(text[here:start]))
731
732 all, scheme, rfc, pep, selfdot, name = match.groups()
733 if scheme:
734 url = escape(all).replace('"', '"')
735 results.append('<a href="%s">%s</a>' % (url, url))
736 elif rfc:
737 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
738 results.append('<a href="%s">%s</a>' % (url, escape(all)))
739 elif pep:
740 url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
741 results.append('<a href="%s">%s</a>' % (url, escape(all)))
742 elif text[end:end+1] == '(':
743 results.append(self.namelink(name, methods, funcs, classes))
744 elif selfdot:
745 results.append('self.<strong>%s</strong>' % name)
746 else:
747 results.append(self.namelink(name, classes))
748 here = end
749 results.append(escape(text[here:]))
750 return ''.join(results)
751
752 def docroutine(self, object, name, mod=None,
753 funcs={}, classes={}, methods={}, cl=None):
754 """Produce HTML documentation for a function or method object."""
755
756 anchor = (cl and cl.__name__ or '') + '-' + name
757 note = ''
758
759 title = '<a name="%s"><strong>%s</strong></a>' % (
760 self.escape(anchor), self.escape(name))
761
762 if inspect.ismethod(object):
R David Murrayf22b62e2013-08-10 12:01:47 -0400763 args = inspect.getfullargspec(object)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000764 # exclude the argument bound to the instance, it will be
765 # confusing to the non-Python user
766 argspec = inspect.formatargspec (
R David Murrayf22b62e2013-08-10 12:01:47 -0400767 args.args[1:],
768 args.varargs,
769 args.varkw,
770 args.defaults,
771 annotations=args.annotations,
Georg Brandl38eceaa2008-05-26 11:14:17 +0000772 formatvalue=self.formatvalue
773 )
774 elif inspect.isfunction(object):
R David Murrayf22b62e2013-08-10 12:01:47 -0400775 args = inspect.getfullargspec(object)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000776 argspec = inspect.formatargspec(
R David Murrayf22b62e2013-08-10 12:01:47 -0400777 args.args, args.varargs, args.varkw, args.defaults,
778 annotations=args.annotations,
779 formatvalue=self.formatvalue)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000780 else:
781 argspec = '(...)'
782
783 if isinstance(object, tuple):
784 argspec = object[0] or argspec
785 docstring = object[1] or ""
786 else:
787 docstring = pydoc.getdoc(object)
788
789 decl = title + argspec + (note and self.grey(
790 '<font face="helvetica, arial">%s</font>' % note))
791
792 doc = self.markup(
793 docstring, self.preformat, funcs, classes, methods)
794 doc = doc and '<dd><tt>%s</tt></dd>' % doc
795 return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
796
797 def docserver(self, server_name, package_documentation, methods):
798 """Produce HTML documentation for an XML-RPC server."""
799
800 fdict = {}
801 for key, value in methods.items():
802 fdict[key] = '#-' + key
803 fdict[value] = fdict[key]
804
805 server_name = self.escape(server_name)
806 head = '<big><big><strong>%s</strong></big></big>' % server_name
807 result = self.heading(head, '#ffffff', '#7799ee')
808
809 doc = self.markup(package_documentation, self.preformat, fdict)
810 doc = doc and '<tt>%s</tt>' % doc
811 result = result + '<p>%s</p>\n' % doc
812
813 contents = []
814 method_items = sorted(methods.items())
815 for key, value in method_items:
816 contents.append(self.docroutine(value, key, funcs=fdict))
817 result = result + self.bigsection(
818 'Methods', '#ffffff', '#eeaa77', ''.join(contents))
819
820 return result
821
822class XMLRPCDocGenerator:
823 """Generates documentation for an XML-RPC server.
824
825 This class is designed as mix-in and should not
826 be constructed directly.
827 """
828
829 def __init__(self):
830 # setup variables used for HTML documentation
831 self.server_name = 'XML-RPC Server Documentation'
832 self.server_documentation = \
833 "This server exports the following methods through the XML-RPC "\
834 "protocol."
835 self.server_title = 'XML-RPC Server Documentation'
836
837 def set_server_title(self, server_title):
838 """Set the HTML title of the generated server documentation"""
839
840 self.server_title = server_title
841
842 def set_server_name(self, server_name):
843 """Set the name of the generated HTML server documentation"""
844
845 self.server_name = server_name
846
847 def set_server_documentation(self, server_documentation):
848 """Set the documentation string for the entire server."""
849
850 self.server_documentation = server_documentation
851
852 def generate_html_documentation(self):
853 """generate_html_documentation() => html documentation for the server
854
855 Generates HTML documentation for the server using introspection for
856 installed functions and instances that do not implement the
857 _dispatch method. Alternatively, instances can choose to implement
858 the _get_method_argstring(method_name) method to provide the
859 argument string used in the documentation and the
860 _methodHelp(method_name) method to provide the help text used
861 in the documentation."""
862
863 methods = {}
864
865 for method_name in self.system_listMethods():
866 if method_name in self.funcs:
867 method = self.funcs[method_name]
868 elif self.instance is not None:
869 method_info = [None, None] # argspec, documentation
870 if hasattr(self.instance, '_get_method_argstring'):
871 method_info[0] = self.instance._get_method_argstring(method_name)
872 if hasattr(self.instance, '_methodHelp'):
873 method_info[1] = self.instance._methodHelp(method_name)
874
875 method_info = tuple(method_info)
876 if method_info != (None, None):
877 method = method_info
878 elif not hasattr(self.instance, '_dispatch'):
879 try:
880 method = resolve_dotted_attribute(
881 self.instance,
882 method_name
883 )
884 except AttributeError:
885 method = method_info
886 else:
887 method = method_info
888 else:
889 assert 0, "Could not find method in self.functions and no "\
890 "instance installed"
891
892 methods[method_name] = method
893
894 documenter = ServerHTMLDoc()
895 documentation = documenter.docserver(
896 self.server_name,
897 self.server_documentation,
898 methods
899 )
900
901 return documenter.page(self.server_title, documentation)
902
903class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
904 """XML-RPC and documentation request handler class.
905
906 Handles all HTTP POST requests and attempts to decode them as
907 XML-RPC requests.
908
909 Handles all HTTP GET requests and interprets them as requests
910 for documentation.
911 """
912
913 def do_GET(self):
914 """Handles the HTTP GET request.
915
916 Interpret all HTTP GET requests as requests for server
917 documentation.
918 """
919 # Check that the path is legal
920 if not self.is_rpc_path_valid():
921 self.report_404()
922 return
923
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000924 response = self.server.generate_html_documentation().encode('utf-8')
Georg Brandl38eceaa2008-05-26 11:14:17 +0000925 self.send_response(200)
926 self.send_header("Content-type", "text/html")
927 self.send_header("Content-length", str(len(response)))
928 self.end_headers()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000929 self.wfile.write(response)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000930
Georg Brandl38eceaa2008-05-26 11:14:17 +0000931class DocXMLRPCServer( SimpleXMLRPCServer,
932 XMLRPCDocGenerator):
933 """XML-RPC and HTML documentation server.
934
935 Adds the ability to serve server documentation to the capabilities
936 of SimpleXMLRPCServer.
937 """
938
939 def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
Georg Brandlfe991052009-09-16 15:54:04 +0000940 logRequests=True, allow_none=False, encoding=None,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100941 bind_and_activate=True, use_builtin_types=False):
Georg Brandl38eceaa2008-05-26 11:14:17 +0000942 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100943 allow_none, encoding, bind_and_activate,
944 use_builtin_types)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000945 XMLRPCDocGenerator.__init__(self)
946
947class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,
948 XMLRPCDocGenerator):
949 """Handler for XML-RPC data and documentation requests passed through
950 CGI"""
951
952 def handle_get(self):
953 """Handles the HTTP GET request.
954
955 Interpret all HTTP GET requests as requests for server
956 documentation.
957 """
958
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000959 response = self.generate_html_documentation().encode('utf-8')
Georg Brandl38eceaa2008-05-26 11:14:17 +0000960
961 print('Content-Type: text/html')
962 print('Content-Length: %d' % len(response))
963 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000964 sys.stdout.flush()
965 sys.stdout.buffer.write(response)
966 sys.stdout.buffer.flush()
Georg Brandl38eceaa2008-05-26 11:14:17 +0000967
968 def __init__(self):
969 CGIXMLRPCRequestHandler.__init__(self)
970 XMLRPCDocGenerator.__init__(self)
971
972
Fredrik Lundhb329b712001-09-17 17:35:21 +0000973if __name__ == '__main__':
Senthil Kumaran939e2db2014-01-12 16:06:58 -0800974 import datetime
975
976 class ExampleService:
977 def getData(self):
978 return '42'
979
980 class currentTime:
981 @staticmethod
982 def getCurrentTime():
983 return datetime.datetime.now()
984
Martin Panter0cab9c12016-04-13 00:36:52 +0000985 with SimpleXMLRPCServer(("localhost", 8000)) as server:
986 server.register_function(pow)
987 server.register_function(lambda x,y: x+y, 'add')
988 server.register_instance(ExampleService(), allow_dotted_names=True)
989 server.register_multicall_functions()
990 print('Serving XML-RPC on localhost port 8000')
991 print('It is advisable to run this example server within a secure, closed network.')
992 try:
993 server.serve_forever()
994 except KeyboardInterrupt:
995 print("\nKeyboard interrupt received, exiting.")
996 sys.exit(0)