blob: a6275a1b746844fab3d59f03ee1f52fc170cf67f [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 func = None
396 try:
397 # check to see if a matching function has been registered
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000398 func = self.funcs[method]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000399 except KeyError:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000400 if self.instance is not None:
Fredrik Lundhb329b712001-09-17 17:35:21 +0000401 # check for a _dispatch method
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000402 if hasattr(self.instance, '_dispatch'):
403 return self.instance._dispatch(method, params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000404 else:
405 # call instance method directly
406 try:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000407 func = resolve_dotted_attribute(
408 self.instance,
Guido van Rossumd0641422005-02-03 15:01:24 +0000409 method,
410 self.allow_dotted_names
Fredrik Lundhb329b712001-09-17 17:35:21 +0000411 )
412 except AttributeError:
413 pass
414
415 if func is not None:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000416 return func(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000417 else:
418 raise Exception('method "%s" is not supported' % method)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000419
Georg Brandl24420152008-05-26 16:32:26 +0000420class SimpleXMLRPCRequestHandler(BaseHTTPRequestHandler):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000421 """Simple XML-RPC request handler class.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000422
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000423 Handles all HTTP POST requests and attempts to decode them as
424 XML-RPC requests.
425 """
426
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000427 # Class attribute listing the accessible path components;
428 # paths not on this list will result in a 404 error.
429 rpc_paths = ('/', '/RPC2')
430
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000431 #if not None, encode responses larger than this, if possible
432 encode_threshold = 1400 #a common MTU
433
434 #Override form StreamRequestHandler: full buffering of output
435 #and no Nagle.
436 wbufsize = -1
437 disable_nagle_algorithm = True
438
439 # a re to match a gzip Accept-Encoding
440 aepattern = re.compile(r"""
441 \s* ([^\s;]+) \s* #content-coding
442 (;\s* q \s*=\s* ([0-9\.]+))? #q
443 """, re.VERBOSE | re.IGNORECASE)
444
445 def accept_encodings(self):
446 r = {}
447 ae = self.headers.get("Accept-Encoding", "")
448 for e in ae.split(","):
449 match = self.aepattern.match(e)
450 if match:
451 v = match.group(3)
452 v = float(v) if v else 1.0
453 r[match.group(1)] = v
454 return r
455
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000456 def is_rpc_path_valid(self):
457 if self.rpc_paths:
458 return self.path in self.rpc_paths
459 else:
460 # If .rpc_paths is empty, just assume all paths are legal
461 return True
462
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000463 def do_POST(self):
464 """Handles the HTTP POST request.
465
466 Attempts to interpret all HTTP POST requests as XML-RPC calls,
467 which are forwarded to the server's _dispatch method for handling.
468 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000469
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000470 # Check that the path is legal
471 if not self.is_rpc_path_valid():
472 self.report_404()
473 return
474
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000475 try:
Tim Peters536cf992005-12-25 23:18:31 +0000476 # Get arguments by reading body of request.
477 # We read this in chunks to avoid straining
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000478 # socket.read(); around the 10 or 15Mb mark, some platforms
479 # begin to have problems (bug #792570).
480 max_chunk_size = 10*1024*1024
481 size_remaining = int(self.headers["content-length"])
482 L = []
483 while size_remaining:
484 chunk_size = min(size_remaining, max_chunk_size)
Charles-François Nataliec1712a2012-02-18 14:42:57 +0100485 chunk = self.rfile.read(chunk_size)
486 if not chunk:
487 break
488 L.append(chunk)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000489 size_remaining -= len(L[-1])
Hye-Shik Chang96042862007-08-19 10:49:11 +0000490 data = b''.join(L)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000491
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000492 data = self.decode_request_content(data)
493 if data is None:
494 return #response has been sent
495
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000496 # In previous versions of SimpleXMLRPCServer, _dispatch
497 # could be overridden in this class, instead of in
498 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
499 # check to see if a subclass implements _dispatch and dispatch
500 # using that method if present.
501 response = self.server._marshaled_dispatch(
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000502 data, getattr(self, '_dispatch', None), self.path
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000503 )
Guido van Rossum61e21b52007-08-20 19:06:03 +0000504 except Exception as e: # This should only happen if the module is buggy
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000505 # internal error, report as HTTP server error
506 self.send_response(500)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000507
508 # Send information about the exception if requested
509 if hasattr(self.server, '_send_traceback_header') and \
510 self.server._send_traceback_header:
511 self.send_header("X-exception", str(e))
Victor Stinner5bfe1462010-04-16 13:28:05 +0000512 trace = traceback.format_exc()
513 trace = str(trace.encode('ASCII', 'backslashreplace'), 'ASCII')
514 self.send_header("X-traceback", trace)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000515
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000516 self.send_header("Content-length", "0")
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000517 self.end_headers()
518 else:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000519 self.send_response(200)
520 self.send_header("Content-type", "text/xml")
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000521 if self.encode_threshold is not None:
522 if len(response) > self.encode_threshold:
523 q = self.accept_encodings().get("gzip", 0)
524 if q:
Kristján Valur Jónssonaefde242009-07-19 22:29:24 +0000525 try:
526 response = gzip_encode(response)
527 self.send_header("Content-Encoding", "gzip")
528 except NotImplementedError:
529 pass
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000530 self.send_header("Content-length", str(len(response)))
531 self.end_headers()
532 self.wfile.write(response)
533
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000534 def decode_request_content(self, data):
535 #support gzip encoding of request
536 encoding = self.headers.get("content-encoding", "identity").lower()
537 if encoding == "identity":
538 return data
539 if encoding == "gzip":
540 try:
541 return gzip_decode(data)
Kristján Valur Jónssonaefde242009-07-19 22:29:24 +0000542 except NotImplementedError:
543 self.send_response(501, "encoding %r not supported" % encoding)
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000544 except ValueError:
545 self.send_response(400, "error decoding gzip content")
546 else:
547 self.send_response(501, "encoding %r not supported" % encoding)
548 self.send_header("Content-length", "0")
549 self.end_headers()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000550
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000551 def report_404 (self):
552 # Report a 404 error
553 self.send_response(404)
Christian Heimes0aa93cd2007-12-08 18:38:20 +0000554 response = b'No such page'
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000555 self.send_header("Content-type", "text/plain")
556 self.send_header("Content-length", str(len(response)))
557 self.end_headers()
558 self.wfile.write(response)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000559
Fredrik Lundhb329b712001-09-17 17:35:21 +0000560 def log_request(self, code='-', size='-'):
561 """Selectively log an accepted request."""
562
563 if self.server.logRequests:
Georg Brandl24420152008-05-26 16:32:26 +0000564 BaseHTTPRequestHandler.log_request(self, code, size)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000565
Alexandre Vassalottice261952008-05-12 02:31:37 +0000566class SimpleXMLRPCServer(socketserver.TCPServer,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000567 SimpleXMLRPCDispatcher):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000568 """Simple XML-RPC server.
569
570 Simple XML-RPC server that allows functions and a single instance
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000571 to be installed to handle requests. The default implementation
572 attempts to dispatch XML-RPC calls to the functions or instance
Florent Xiclunac4fec932011-10-30 20:19:32 +0100573 installed in the server. Override the _dispatch method inherited
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000574 from SimpleXMLRPCDispatcher to change this behavior.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000575 """
576
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000577 allow_reuse_address = True
578
Guido van Rossum61e21b52007-08-20 19:06:03 +0000579 # Warning: this is for debugging purposes only! Never set this to True in
580 # production code, as will be sending out sensitive information (exception
581 # and stack trace details) when exceptions are raised inside
582 # SimpleXMLRPCRequestHandler.do_POST
583 _send_traceback_header = False
584
Fredrik Lundhb329b712001-09-17 17:35:21 +0000585 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100586 logRequests=True, allow_none=False, encoding=None,
587 bind_and_activate=True, use_builtin_types=False):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000588 self.logRequests = logRequests
Tim Peters2c60f7a2003-01-29 03:49:43 +0000589
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100590 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
Alexandre Vassalottice261952008-05-12 02:31:37 +0000591 socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000592
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000593
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000594class MultiPathXMLRPCServer(SimpleXMLRPCServer):
595 """Multipath XML-RPC Server
596 This specialization of SimpleXMLRPCServer allows the user to create
597 multiple Dispatcher instances and assign them to different
598 HTTP request paths. This makes it possible to run two or more
599 'virtual XML-RPC servers' at the same port.
600 Make sure that the requestHandler accepts the paths in question.
601 """
602 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100603 logRequests=True, allow_none=False, encoding=None,
604 bind_and_activate=True, use_builtin_types=False):
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000605
606 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100607 encoding, bind_and_activate, use_builtin_types)
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000608 self.dispatchers = {}
609 self.allow_none = allow_none
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100610 self.encoding = encoding or 'utf-8'
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000611
612 def add_dispatcher(self, path, dispatcher):
613 self.dispatchers[path] = dispatcher
614 return dispatcher
615
616 def get_dispatcher(self, path):
617 return self.dispatchers[path]
618
619 def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
620 try:
621 response = self.dispatchers[path]._marshaled_dispatch(
622 data, dispatch_method, path)
623 except:
624 # report low level exception back to server
625 # (each dispatcher should have handled their own
626 # exceptions)
627 exc_type, exc_value = sys.exc_info()[:2]
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100628 response = dumps(
629 Fault(1, "%s:%s" % (exc_type, exc_value)),
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000630 encoding=self.encoding, allow_none=self.allow_none)
Serhiy Storchakaaebb6d32016-01-20 10:34:27 +0200631 response = response.encode(self.encoding, 'xmlcharrefreplace')
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000632 return response
633
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000634class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
635 """Simple handler for XML-RPC data passed through CGI."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000636
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100637 def __init__(self, allow_none=False, encoding=None, use_builtin_types=False):
638 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000639
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000640 def handle_xmlrpc(self, request_text):
641 """Handle a single XML-RPC request"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000642
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000643 response = self._marshaled_dispatch(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000644
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000645 print('Content-Type: text/xml')
646 print('Content-Length: %d' % len(response))
647 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000648 sys.stdout.flush()
649 sys.stdout.buffer.write(response)
650 sys.stdout.buffer.flush()
Fredrik Lundhb329b712001-09-17 17:35:21 +0000651
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000652 def handle_get(self):
653 """Handle a single HTTP GET request.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000654
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000655 Default implementation indicates an error because
656 XML-RPC uses the POST method.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000657 """
658
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000659 code = 400
Georg Brandl24420152008-05-26 16:32:26 +0000660 message, explain = BaseHTTPRequestHandler.responses[code]
Tim Peters2c60f7a2003-01-29 03:49:43 +0000661
Georg Brandl24420152008-05-26 16:32:26 +0000662 response = http.server.DEFAULT_ERROR_MESSAGE % \
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000663 {
Tim Peters2c60f7a2003-01-29 03:49:43 +0000664 'code' : code,
665 'message' : message,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000666 'explain' : explain
667 }
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000668 response = response.encode('utf-8')
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000669 print('Status: %d %s' % (code, message))
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000670 print('Content-Type: %s' % http.server.DEFAULT_ERROR_CONTENT_TYPE)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000671 print('Content-Length: %d' % len(response))
672 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000673 sys.stdout.flush()
674 sys.stdout.buffer.write(response)
675 sys.stdout.buffer.flush()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000676
Georg Brandlfe991052009-09-16 15:54:04 +0000677 def handle_request(self, request_text=None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000678 """Handle a single XML-RPC request passed through a CGI post method.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000679
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000680 If no XML data is given then it is read from stdin. The resulting
681 XML-RPC response is printed to stdout along with the correct HTTP
682 headers.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000683 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000684
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000685 if request_text is None and \
686 os.environ.get('REQUEST_METHOD', None) == 'GET':
687 self.handle_get()
688 else:
689 # POST data is normally available through stdin
Georg Brandl99412e52009-04-01 04:27:47 +0000690 try:
691 length = int(os.environ.get('CONTENT_LENGTH', None))
Georg Brandlc7485062009-04-01 15:53:15 +0000692 except (ValueError, TypeError):
Georg Brandl99412e52009-04-01 04:27:47 +0000693 length = -1
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000694 if request_text is None:
Georg Brandl99412e52009-04-01 04:27:47 +0000695 request_text = sys.stdin.read(length)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000696
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000697 self.handle_xmlrpc(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000698
Georg Brandl38eceaa2008-05-26 11:14:17 +0000699
700# -----------------------------------------------------------------------------
701# Self documenting XML-RPC Server.
702
703class ServerHTMLDoc(pydoc.HTMLDoc):
704 """Class used to generate pydoc HTML document for a server"""
705
706 def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
707 """Mark up some plain text, given a context of symbols to look for.
708 Each context dictionary maps object names to anchor names."""
709 escape = escape or self.escape
710 results = []
711 here = 0
712
713 # XXX Note that this regular expression does not allow for the
714 # hyperlinking of arbitrary strings being used as method
715 # names. Only methods with names consisting of word characters
716 # and '.'s are hyperlinked.
717 pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
718 r'RFC[- ]?(\d+)|'
719 r'PEP[- ]?(\d+)|'
720 r'(self\.)?((?:\w|\.)+))\b')
721 while 1:
722 match = pattern.search(text, here)
723 if not match: break
724 start, end = match.span()
725 results.append(escape(text[here:start]))
726
727 all, scheme, rfc, pep, selfdot, name = match.groups()
728 if scheme:
729 url = escape(all).replace('"', '"')
730 results.append('<a href="%s">%s</a>' % (url, url))
731 elif rfc:
732 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
733 results.append('<a href="%s">%s</a>' % (url, escape(all)))
734 elif pep:
735 url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
736 results.append('<a href="%s">%s</a>' % (url, escape(all)))
737 elif text[end:end+1] == '(':
738 results.append(self.namelink(name, methods, funcs, classes))
739 elif selfdot:
740 results.append('self.<strong>%s</strong>' % name)
741 else:
742 results.append(self.namelink(name, classes))
743 here = end
744 results.append(escape(text[here:]))
745 return ''.join(results)
746
747 def docroutine(self, object, name, mod=None,
748 funcs={}, classes={}, methods={}, cl=None):
749 """Produce HTML documentation for a function or method object."""
750
751 anchor = (cl and cl.__name__ or '') + '-' + name
752 note = ''
753
754 title = '<a name="%s"><strong>%s</strong></a>' % (
755 self.escape(anchor), self.escape(name))
756
757 if inspect.ismethod(object):
R David Murrayf22b62e2013-08-10 12:01:47 -0400758 args = inspect.getfullargspec(object)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000759 # exclude the argument bound to the instance, it will be
760 # confusing to the non-Python user
761 argspec = inspect.formatargspec (
R David Murrayf22b62e2013-08-10 12:01:47 -0400762 args.args[1:],
763 args.varargs,
764 args.varkw,
765 args.defaults,
766 annotations=args.annotations,
Georg Brandl38eceaa2008-05-26 11:14:17 +0000767 formatvalue=self.formatvalue
768 )
769 elif inspect.isfunction(object):
R David Murrayf22b62e2013-08-10 12:01:47 -0400770 args = inspect.getfullargspec(object)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000771 argspec = inspect.formatargspec(
R David Murrayf22b62e2013-08-10 12:01:47 -0400772 args.args, args.varargs, args.varkw, args.defaults,
773 annotations=args.annotations,
774 formatvalue=self.formatvalue)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000775 else:
776 argspec = '(...)'
777
778 if isinstance(object, tuple):
779 argspec = object[0] or argspec
780 docstring = object[1] or ""
781 else:
782 docstring = pydoc.getdoc(object)
783
784 decl = title + argspec + (note and self.grey(
785 '<font face="helvetica, arial">%s</font>' % note))
786
787 doc = self.markup(
788 docstring, self.preformat, funcs, classes, methods)
789 doc = doc and '<dd><tt>%s</tt></dd>' % doc
790 return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
791
792 def docserver(self, server_name, package_documentation, methods):
793 """Produce HTML documentation for an XML-RPC server."""
794
795 fdict = {}
796 for key, value in methods.items():
797 fdict[key] = '#-' + key
798 fdict[value] = fdict[key]
799
800 server_name = self.escape(server_name)
801 head = '<big><big><strong>%s</strong></big></big>' % server_name
802 result = self.heading(head, '#ffffff', '#7799ee')
803
804 doc = self.markup(package_documentation, self.preformat, fdict)
805 doc = doc and '<tt>%s</tt>' % doc
806 result = result + '<p>%s</p>\n' % doc
807
808 contents = []
809 method_items = sorted(methods.items())
810 for key, value in method_items:
811 contents.append(self.docroutine(value, key, funcs=fdict))
812 result = result + self.bigsection(
813 'Methods', '#ffffff', '#eeaa77', ''.join(contents))
814
815 return result
816
817class XMLRPCDocGenerator:
818 """Generates documentation for an XML-RPC server.
819
820 This class is designed as mix-in and should not
821 be constructed directly.
822 """
823
824 def __init__(self):
825 # setup variables used for HTML documentation
826 self.server_name = 'XML-RPC Server Documentation'
827 self.server_documentation = \
828 "This server exports the following methods through the XML-RPC "\
829 "protocol."
830 self.server_title = 'XML-RPC Server Documentation'
831
832 def set_server_title(self, server_title):
833 """Set the HTML title of the generated server documentation"""
834
835 self.server_title = server_title
836
837 def set_server_name(self, server_name):
838 """Set the name of the generated HTML server documentation"""
839
840 self.server_name = server_name
841
842 def set_server_documentation(self, server_documentation):
843 """Set the documentation string for the entire server."""
844
845 self.server_documentation = server_documentation
846
847 def generate_html_documentation(self):
848 """generate_html_documentation() => html documentation for the server
849
850 Generates HTML documentation for the server using introspection for
851 installed functions and instances that do not implement the
852 _dispatch method. Alternatively, instances can choose to implement
853 the _get_method_argstring(method_name) method to provide the
854 argument string used in the documentation and the
855 _methodHelp(method_name) method to provide the help text used
856 in the documentation."""
857
858 methods = {}
859
860 for method_name in self.system_listMethods():
861 if method_name in self.funcs:
862 method = self.funcs[method_name]
863 elif self.instance is not None:
864 method_info = [None, None] # argspec, documentation
865 if hasattr(self.instance, '_get_method_argstring'):
866 method_info[0] = self.instance._get_method_argstring(method_name)
867 if hasattr(self.instance, '_methodHelp'):
868 method_info[1] = self.instance._methodHelp(method_name)
869
870 method_info = tuple(method_info)
871 if method_info != (None, None):
872 method = method_info
873 elif not hasattr(self.instance, '_dispatch'):
874 try:
875 method = resolve_dotted_attribute(
876 self.instance,
877 method_name
878 )
879 except AttributeError:
880 method = method_info
881 else:
882 method = method_info
883 else:
884 assert 0, "Could not find method in self.functions and no "\
885 "instance installed"
886
887 methods[method_name] = method
888
889 documenter = ServerHTMLDoc()
890 documentation = documenter.docserver(
891 self.server_name,
892 self.server_documentation,
893 methods
894 )
895
896 return documenter.page(self.server_title, documentation)
897
898class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
899 """XML-RPC and documentation request handler class.
900
901 Handles all HTTP POST requests and attempts to decode them as
902 XML-RPC requests.
903
904 Handles all HTTP GET requests and interprets them as requests
905 for documentation.
906 """
907
908 def do_GET(self):
909 """Handles the HTTP GET request.
910
911 Interpret all HTTP GET requests as requests for server
912 documentation.
913 """
914 # Check that the path is legal
915 if not self.is_rpc_path_valid():
916 self.report_404()
917 return
918
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000919 response = self.server.generate_html_documentation().encode('utf-8')
Georg Brandl38eceaa2008-05-26 11:14:17 +0000920 self.send_response(200)
921 self.send_header("Content-type", "text/html")
922 self.send_header("Content-length", str(len(response)))
923 self.end_headers()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000924 self.wfile.write(response)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000925
Georg Brandl38eceaa2008-05-26 11:14:17 +0000926class DocXMLRPCServer( SimpleXMLRPCServer,
927 XMLRPCDocGenerator):
928 """XML-RPC and HTML documentation server.
929
930 Adds the ability to serve server documentation to the capabilities
931 of SimpleXMLRPCServer.
932 """
933
934 def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
Georg Brandlfe991052009-09-16 15:54:04 +0000935 logRequests=True, allow_none=False, encoding=None,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100936 bind_and_activate=True, use_builtin_types=False):
Georg Brandl38eceaa2008-05-26 11:14:17 +0000937 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100938 allow_none, encoding, bind_and_activate,
939 use_builtin_types)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000940 XMLRPCDocGenerator.__init__(self)
941
942class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,
943 XMLRPCDocGenerator):
944 """Handler for XML-RPC data and documentation requests passed through
945 CGI"""
946
947 def handle_get(self):
948 """Handles the HTTP GET request.
949
950 Interpret all HTTP GET requests as requests for server
951 documentation.
952 """
953
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000954 response = self.generate_html_documentation().encode('utf-8')
Georg Brandl38eceaa2008-05-26 11:14:17 +0000955
956 print('Content-Type: text/html')
957 print('Content-Length: %d' % len(response))
958 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000959 sys.stdout.flush()
960 sys.stdout.buffer.write(response)
961 sys.stdout.buffer.flush()
Georg Brandl38eceaa2008-05-26 11:14:17 +0000962
963 def __init__(self):
964 CGIXMLRPCRequestHandler.__init__(self)
965 XMLRPCDocGenerator.__init__(self)
966
967
Fredrik Lundhb329b712001-09-17 17:35:21 +0000968if __name__ == '__main__':
Senthil Kumaran939e2db2014-01-12 16:06:58 -0800969 import datetime
970
971 class ExampleService:
972 def getData(self):
973 return '42'
974
975 class currentTime:
976 @staticmethod
977 def getCurrentTime():
978 return datetime.datetime.now()
979
Martin Panter0cab9c12016-04-13 00:36:52 +0000980 with SimpleXMLRPCServer(("localhost", 8000)) as server:
981 server.register_function(pow)
982 server.register_function(lambda x,y: x+y, 'add')
983 server.register_instance(ExampleService(), allow_dotted_names=True)
984 server.register_multicall_functions()
985 print('Serving XML-RPC on localhost port 8000')
986 print('It is advisable to run this example server within a secure, closed network.')
987 try:
988 server.serve_forever()
989 except KeyboardInterrupt:
990 print("\nKeyboard interrupt received, exiting.")
991 sys.exit(0)