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