blob: fc3fa4b9f9d8aca925622ea5bb09280b637aa7cf [file] [log] [blame]
Georg Brandl38eceaa2008-05-26 11:14:17 +00001"""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
187 If a registered function matches a XML-RPC request, then it
188 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
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000272 return response.encode(self.encoding)
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 func = None
390 try:
391 # check to see if a matching function has been registered
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000392 func = self.funcs[method]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000393 except KeyError:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000394 if self.instance is not None:
Fredrik Lundhb329b712001-09-17 17:35:21 +0000395 # check for a _dispatch method
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000396 if hasattr(self.instance, '_dispatch'):
397 return self.instance._dispatch(method, params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000398 else:
399 # call instance method directly
400 try:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000401 func = resolve_dotted_attribute(
402 self.instance,
Guido van Rossumd0641422005-02-03 15:01:24 +0000403 method,
404 self.allow_dotted_names
Fredrik Lundhb329b712001-09-17 17:35:21 +0000405 )
406 except AttributeError:
407 pass
408
409 if func is not None:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000410 return func(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000411 else:
412 raise Exception('method "%s" is not supported' % method)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000413
Georg Brandl24420152008-05-26 16:32:26 +0000414class SimpleXMLRPCRequestHandler(BaseHTTPRequestHandler):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000415 """Simple XML-RPC request handler class.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000416
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000417 Handles all HTTP POST requests and attempts to decode them as
418 XML-RPC requests.
419 """
420
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000421 # Class attribute listing the accessible path components;
422 # paths not on this list will result in a 404 error.
423 rpc_paths = ('/', '/RPC2')
424
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000425 #if not None, encode responses larger than this, if possible
426 encode_threshold = 1400 #a common MTU
427
428 #Override form StreamRequestHandler: full buffering of output
429 #and no Nagle.
430 wbufsize = -1
431 disable_nagle_algorithm = True
432
433 # a re to match a gzip Accept-Encoding
434 aepattern = re.compile(r"""
435 \s* ([^\s;]+) \s* #content-coding
436 (;\s* q \s*=\s* ([0-9\.]+))? #q
437 """, re.VERBOSE | re.IGNORECASE)
438
439 def accept_encodings(self):
440 r = {}
441 ae = self.headers.get("Accept-Encoding", "")
442 for e in ae.split(","):
443 match = self.aepattern.match(e)
444 if match:
445 v = match.group(3)
446 v = float(v) if v else 1.0
447 r[match.group(1)] = v
448 return r
449
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000450 def is_rpc_path_valid(self):
451 if self.rpc_paths:
452 return self.path in self.rpc_paths
453 else:
454 # If .rpc_paths is empty, just assume all paths are legal
455 return True
456
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000457 def do_POST(self):
458 """Handles the HTTP POST request.
459
460 Attempts to interpret all HTTP POST requests as XML-RPC calls,
461 which are forwarded to the server's _dispatch method for handling.
462 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000463
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000464 # Check that the path is legal
465 if not self.is_rpc_path_valid():
466 self.report_404()
467 return
468
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000469 try:
Tim Peters536cf992005-12-25 23:18:31 +0000470 # Get arguments by reading body of request.
471 # We read this in chunks to avoid straining
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000472 # socket.read(); around the 10 or 15Mb mark, some platforms
473 # begin to have problems (bug #792570).
474 max_chunk_size = 10*1024*1024
475 size_remaining = int(self.headers["content-length"])
476 L = []
477 while size_remaining:
478 chunk_size = min(size_remaining, max_chunk_size)
Charles-François Nataliec1712a2012-02-18 14:42:57 +0100479 chunk = self.rfile.read(chunk_size)
480 if not chunk:
481 break
482 L.append(chunk)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000483 size_remaining -= len(L[-1])
Hye-Shik Chang96042862007-08-19 10:49:11 +0000484 data = b''.join(L)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000485
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000486 data = self.decode_request_content(data)
487 if data is None:
488 return #response has been sent
489
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000490 # In previous versions of SimpleXMLRPCServer, _dispatch
491 # could be overridden in this class, instead of in
492 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
493 # check to see if a subclass implements _dispatch and dispatch
494 # using that method if present.
495 response = self.server._marshaled_dispatch(
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000496 data, getattr(self, '_dispatch', None), self.path
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000497 )
Guido van Rossum61e21b52007-08-20 19:06:03 +0000498 except Exception as e: # This should only happen if the module is buggy
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000499 # internal error, report as HTTP server error
500 self.send_response(500)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000501
502 # Send information about the exception if requested
503 if hasattr(self.server, '_send_traceback_header') and \
504 self.server._send_traceback_header:
505 self.send_header("X-exception", str(e))
Victor Stinner5bfe1462010-04-16 13:28:05 +0000506 trace = traceback.format_exc()
507 trace = str(trace.encode('ASCII', 'backslashreplace'), 'ASCII')
508 self.send_header("X-traceback", trace)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000509
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000510 self.send_header("Content-length", "0")
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000511 self.end_headers()
512 else:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000513 self.send_response(200)
514 self.send_header("Content-type", "text/xml")
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000515 if self.encode_threshold is not None:
516 if len(response) > self.encode_threshold:
517 q = self.accept_encodings().get("gzip", 0)
518 if q:
Kristján Valur Jónssonaefde242009-07-19 22:29:24 +0000519 try:
520 response = gzip_encode(response)
521 self.send_header("Content-Encoding", "gzip")
522 except NotImplementedError:
523 pass
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000524 self.send_header("Content-length", str(len(response)))
525 self.end_headers()
526 self.wfile.write(response)
527
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000528 def decode_request_content(self, data):
529 #support gzip encoding of request
530 encoding = self.headers.get("content-encoding", "identity").lower()
531 if encoding == "identity":
532 return data
533 if encoding == "gzip":
534 try:
535 return gzip_decode(data)
Kristján Valur Jónssonaefde242009-07-19 22:29:24 +0000536 except NotImplementedError:
537 self.send_response(501, "encoding %r not supported" % encoding)
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000538 except ValueError:
539 self.send_response(400, "error decoding gzip content")
540 else:
541 self.send_response(501, "encoding %r not supported" % encoding)
542 self.send_header("Content-length", "0")
543 self.end_headers()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000544
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000545 def report_404 (self):
546 # Report a 404 error
547 self.send_response(404)
Christian Heimes0aa93cd2007-12-08 18:38:20 +0000548 response = b'No such page'
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000549 self.send_header("Content-type", "text/plain")
550 self.send_header("Content-length", str(len(response)))
551 self.end_headers()
552 self.wfile.write(response)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000553
Fredrik Lundhb329b712001-09-17 17:35:21 +0000554 def log_request(self, code='-', size='-'):
555 """Selectively log an accepted request."""
556
557 if self.server.logRequests:
Georg Brandl24420152008-05-26 16:32:26 +0000558 BaseHTTPRequestHandler.log_request(self, code, size)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000559
Alexandre Vassalottice261952008-05-12 02:31:37 +0000560class SimpleXMLRPCServer(socketserver.TCPServer,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000561 SimpleXMLRPCDispatcher):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000562 """Simple XML-RPC server.
563
564 Simple XML-RPC server that allows functions and a single instance
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000565 to be installed to handle requests. The default implementation
566 attempts to dispatch XML-RPC calls to the functions or instance
Florent Xiclunac4fec932011-10-30 20:19:32 +0100567 installed in the server. Override the _dispatch method inherited
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000568 from SimpleXMLRPCDispatcher to change this behavior.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000569 """
570
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000571 allow_reuse_address = True
572
Guido van Rossum61e21b52007-08-20 19:06:03 +0000573 # Warning: this is for debugging purposes only! Never set this to True in
574 # production code, as will be sending out sensitive information (exception
575 # and stack trace details) when exceptions are raised inside
576 # SimpleXMLRPCRequestHandler.do_POST
577 _send_traceback_header = False
578
Fredrik Lundhb329b712001-09-17 17:35:21 +0000579 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100580 logRequests=True, allow_none=False, encoding=None,
581 bind_and_activate=True, use_builtin_types=False):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000582 self.logRequests = logRequests
Tim Peters2c60f7a2003-01-29 03:49:43 +0000583
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100584 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
Alexandre Vassalottice261952008-05-12 02:31:37 +0000585 socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000586
Tim Peters536cf992005-12-25 23:18:31 +0000587 # [Bug #1222790] If possible, set close-on-exec flag; if a
588 # method spawns a subprocess, the subprocess shouldn't have
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000589 # the listening socket open.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000590 if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000591 flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
592 flags |= fcntl.FD_CLOEXEC
593 fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
594
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000595class MultiPathXMLRPCServer(SimpleXMLRPCServer):
596 """Multipath XML-RPC Server
597 This specialization of SimpleXMLRPCServer allows the user to create
598 multiple Dispatcher instances and assign them to different
599 HTTP request paths. This makes it possible to run two or more
600 'virtual XML-RPC servers' at the same port.
601 Make sure that the requestHandler accepts the paths in question.
602 """
603 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100604 logRequests=True, allow_none=False, encoding=None,
605 bind_and_activate=True, use_builtin_types=False):
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000606
607 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100608 encoding, bind_and_activate, use_builtin_types)
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000609 self.dispatchers = {}
610 self.allow_none = allow_none
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100611 self.encoding = encoding or 'utf-8'
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000612
613 def add_dispatcher(self, path, dispatcher):
614 self.dispatchers[path] = dispatcher
615 return dispatcher
616
617 def get_dispatcher(self, path):
618 return self.dispatchers[path]
619
620 def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
621 try:
622 response = self.dispatchers[path]._marshaled_dispatch(
623 data, dispatch_method, path)
624 except:
625 # report low level exception back to server
626 # (each dispatcher should have handled their own
627 # exceptions)
628 exc_type, exc_value = sys.exc_info()[:2]
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100629 response = dumps(
630 Fault(1, "%s:%s" % (exc_type, exc_value)),
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000631 encoding=self.encoding, allow_none=self.allow_none)
Florent Xicluna3fa29f72011-10-30 20:18:50 +0100632 response = response.encode(self.encoding)
Kristján Valur Jónsson1f2a1ae2009-12-16 10:50:44 +0000633 return response
634
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000635class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
636 """Simple handler for XML-RPC data passed through CGI."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000637
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100638 def __init__(self, allow_none=False, encoding=None, use_builtin_types=False):
639 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000640
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000641 def handle_xmlrpc(self, request_text):
642 """Handle a single XML-RPC request"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000643
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000644 response = self._marshaled_dispatch(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000645
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000646 print('Content-Type: text/xml')
647 print('Content-Length: %d' % len(response))
648 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000649 sys.stdout.flush()
650 sys.stdout.buffer.write(response)
651 sys.stdout.buffer.flush()
Fredrik Lundhb329b712001-09-17 17:35:21 +0000652
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000653 def handle_get(self):
654 """Handle a single HTTP GET request.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000655
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000656 Default implementation indicates an error because
657 XML-RPC uses the POST method.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000658 """
659
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000660 code = 400
Georg Brandl24420152008-05-26 16:32:26 +0000661 message, explain = BaseHTTPRequestHandler.responses[code]
Tim Peters2c60f7a2003-01-29 03:49:43 +0000662
Georg Brandl24420152008-05-26 16:32:26 +0000663 response = http.server.DEFAULT_ERROR_MESSAGE % \
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000664 {
Tim Peters2c60f7a2003-01-29 03:49:43 +0000665 'code' : code,
666 'message' : message,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000667 'explain' : explain
668 }
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000669 response = response.encode('utf-8')
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000670 print('Status: %d %s' % (code, message))
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000671 print('Content-Type: %s' % http.server.DEFAULT_ERROR_CONTENT_TYPE)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000672 print('Content-Length: %d' % len(response))
673 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000674 sys.stdout.flush()
675 sys.stdout.buffer.write(response)
676 sys.stdout.buffer.flush()
Tim Peters2c60f7a2003-01-29 03:49:43 +0000677
Georg Brandlfe991052009-09-16 15:54:04 +0000678 def handle_request(self, request_text=None):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000679 """Handle a single XML-RPC request passed through a CGI post method.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000680
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000681 If no XML data is given then it is read from stdin. The resulting
682 XML-RPC response is printed to stdout along with the correct HTTP
683 headers.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000684 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000685
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000686 if request_text is None and \
687 os.environ.get('REQUEST_METHOD', None) == 'GET':
688 self.handle_get()
689 else:
690 # POST data is normally available through stdin
Georg Brandl99412e52009-04-01 04:27:47 +0000691 try:
692 length = int(os.environ.get('CONTENT_LENGTH', None))
Georg Brandlc7485062009-04-01 15:53:15 +0000693 except (ValueError, TypeError):
Georg Brandl99412e52009-04-01 04:27:47 +0000694 length = -1
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000695 if request_text is None:
Georg Brandl99412e52009-04-01 04:27:47 +0000696 request_text = sys.stdin.read(length)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000697
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000698 self.handle_xmlrpc(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000699
Georg Brandl38eceaa2008-05-26 11:14:17 +0000700
701# -----------------------------------------------------------------------------
702# Self documenting XML-RPC Server.
703
704class ServerHTMLDoc(pydoc.HTMLDoc):
705 """Class used to generate pydoc HTML document for a server"""
706
707 def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
708 """Mark up some plain text, given a context of symbols to look for.
709 Each context dictionary maps object names to anchor names."""
710 escape = escape or self.escape
711 results = []
712 here = 0
713
714 # XXX Note that this regular expression does not allow for the
715 # hyperlinking of arbitrary strings being used as method
716 # names. Only methods with names consisting of word characters
717 # and '.'s are hyperlinked.
718 pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
719 r'RFC[- ]?(\d+)|'
720 r'PEP[- ]?(\d+)|'
721 r'(self\.)?((?:\w|\.)+))\b')
722 while 1:
723 match = pattern.search(text, here)
724 if not match: break
725 start, end = match.span()
726 results.append(escape(text[here:start]))
727
728 all, scheme, rfc, pep, selfdot, name = match.groups()
729 if scheme:
730 url = escape(all).replace('"', '"')
731 results.append('<a href="%s">%s</a>' % (url, url))
732 elif rfc:
733 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
734 results.append('<a href="%s">%s</a>' % (url, escape(all)))
735 elif pep:
736 url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
737 results.append('<a href="%s">%s</a>' % (url, escape(all)))
738 elif text[end:end+1] == '(':
739 results.append(self.namelink(name, methods, funcs, classes))
740 elif selfdot:
741 results.append('self.<strong>%s</strong>' % name)
742 else:
743 results.append(self.namelink(name, classes))
744 here = end
745 results.append(escape(text[here:]))
746 return ''.join(results)
747
748 def docroutine(self, object, name, mod=None,
749 funcs={}, classes={}, methods={}, cl=None):
750 """Produce HTML documentation for a function or method object."""
751
752 anchor = (cl and cl.__name__ or '') + '-' + name
753 note = ''
754
755 title = '<a name="%s"><strong>%s</strong></a>' % (
756 self.escape(anchor), self.escape(name))
757
758 if inspect.ismethod(object):
759 args, varargs, varkw, defaults = inspect.getargspec(object)
760 # exclude the argument bound to the instance, it will be
761 # confusing to the non-Python user
762 argspec = inspect.formatargspec (
763 args[1:],
764 varargs,
765 varkw,
766 defaults,
767 formatvalue=self.formatvalue
768 )
769 elif inspect.isfunction(object):
770 args, varargs, varkw, defaults = inspect.getargspec(object)
771 argspec = inspect.formatargspec(
772 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
773 else:
774 argspec = '(...)'
775
776 if isinstance(object, tuple):
777 argspec = object[0] or argspec
778 docstring = object[1] or ""
779 else:
780 docstring = pydoc.getdoc(object)
781
782 decl = title + argspec + (note and self.grey(
783 '<font face="helvetica, arial">%s</font>' % note))
784
785 doc = self.markup(
786 docstring, self.preformat, funcs, classes, methods)
787 doc = doc and '<dd><tt>%s</tt></dd>' % doc
788 return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
789
790 def docserver(self, server_name, package_documentation, methods):
791 """Produce HTML documentation for an XML-RPC server."""
792
793 fdict = {}
794 for key, value in methods.items():
795 fdict[key] = '#-' + key
796 fdict[value] = fdict[key]
797
798 server_name = self.escape(server_name)
799 head = '<big><big><strong>%s</strong></big></big>' % server_name
800 result = self.heading(head, '#ffffff', '#7799ee')
801
802 doc = self.markup(package_documentation, self.preformat, fdict)
803 doc = doc and '<tt>%s</tt>' % doc
804 result = result + '<p>%s</p>\n' % doc
805
806 contents = []
807 method_items = sorted(methods.items())
808 for key, value in method_items:
809 contents.append(self.docroutine(value, key, funcs=fdict))
810 result = result + self.bigsection(
811 'Methods', '#ffffff', '#eeaa77', ''.join(contents))
812
813 return result
814
815class XMLRPCDocGenerator:
816 """Generates documentation for an XML-RPC server.
817
818 This class is designed as mix-in and should not
819 be constructed directly.
820 """
821
822 def __init__(self):
823 # setup variables used for HTML documentation
824 self.server_name = 'XML-RPC Server Documentation'
825 self.server_documentation = \
826 "This server exports the following methods through the XML-RPC "\
827 "protocol."
828 self.server_title = 'XML-RPC Server Documentation'
829
830 def set_server_title(self, server_title):
831 """Set the HTML title of the generated server documentation"""
832
833 self.server_title = server_title
834
835 def set_server_name(self, server_name):
836 """Set the name of the generated HTML server documentation"""
837
838 self.server_name = server_name
839
840 def set_server_documentation(self, server_documentation):
841 """Set the documentation string for the entire server."""
842
843 self.server_documentation = server_documentation
844
845 def generate_html_documentation(self):
846 """generate_html_documentation() => html documentation for the server
847
848 Generates HTML documentation for the server using introspection for
849 installed functions and instances that do not implement the
850 _dispatch method. Alternatively, instances can choose to implement
851 the _get_method_argstring(method_name) method to provide the
852 argument string used in the documentation and the
853 _methodHelp(method_name) method to provide the help text used
854 in the documentation."""
855
856 methods = {}
857
858 for method_name in self.system_listMethods():
859 if method_name in self.funcs:
860 method = self.funcs[method_name]
861 elif self.instance is not None:
862 method_info = [None, None] # argspec, documentation
863 if hasattr(self.instance, '_get_method_argstring'):
864 method_info[0] = self.instance._get_method_argstring(method_name)
865 if hasattr(self.instance, '_methodHelp'):
866 method_info[1] = self.instance._methodHelp(method_name)
867
868 method_info = tuple(method_info)
869 if method_info != (None, None):
870 method = method_info
871 elif not hasattr(self.instance, '_dispatch'):
872 try:
873 method = resolve_dotted_attribute(
874 self.instance,
875 method_name
876 )
877 except AttributeError:
878 method = method_info
879 else:
880 method = method_info
881 else:
882 assert 0, "Could not find method in self.functions and no "\
883 "instance installed"
884
885 methods[method_name] = method
886
887 documenter = ServerHTMLDoc()
888 documentation = documenter.docserver(
889 self.server_name,
890 self.server_documentation,
891 methods
892 )
893
894 return documenter.page(self.server_title, documentation)
895
896class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
897 """XML-RPC and documentation request handler class.
898
899 Handles all HTTP POST requests and attempts to decode them as
900 XML-RPC requests.
901
902 Handles all HTTP GET requests and interprets them as requests
903 for documentation.
904 """
905
906 def do_GET(self):
907 """Handles the HTTP GET request.
908
909 Interpret all HTTP GET requests as requests for server
910 documentation.
911 """
912 # Check that the path is legal
913 if not self.is_rpc_path_valid():
914 self.report_404()
915 return
916
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000917 response = self.server.generate_html_documentation().encode('utf-8')
Georg Brandl38eceaa2008-05-26 11:14:17 +0000918 self.send_response(200)
919 self.send_header("Content-type", "text/html")
920 self.send_header("Content-length", str(len(response)))
921 self.end_headers()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000922 self.wfile.write(response)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000923
Georg Brandl38eceaa2008-05-26 11:14:17 +0000924class DocXMLRPCServer( SimpleXMLRPCServer,
925 XMLRPCDocGenerator):
926 """XML-RPC and HTML documentation server.
927
928 Adds the ability to serve server documentation to the capabilities
929 of SimpleXMLRPCServer.
930 """
931
932 def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
Georg Brandlfe991052009-09-16 15:54:04 +0000933 logRequests=True, allow_none=False, encoding=None,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100934 bind_and_activate=True, use_builtin_types=False):
Georg Brandl38eceaa2008-05-26 11:14:17 +0000935 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests,
Florent Xicluna1b7458b2011-12-09 22:35:06 +0100936 allow_none, encoding, bind_and_activate,
937 use_builtin_types)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000938 XMLRPCDocGenerator.__init__(self)
939
940class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,
941 XMLRPCDocGenerator):
942 """Handler for XML-RPC data and documentation requests passed through
943 CGI"""
944
945 def handle_get(self):
946 """Handles the HTTP GET request.
947
948 Interpret all HTTP GET requests as requests for server
949 documentation.
950 """
951
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000952 response = self.generate_html_documentation().encode('utf-8')
Georg Brandl38eceaa2008-05-26 11:14:17 +0000953
954 print('Content-Type: text/html')
955 print('Content-Length: %d' % len(response))
956 print()
Senthil Kumaranb3af08f2009-04-01 20:20:43 +0000957 sys.stdout.flush()
958 sys.stdout.buffer.write(response)
959 sys.stdout.buffer.flush()
Georg Brandl38eceaa2008-05-26 11:14:17 +0000960
961 def __init__(self):
962 CGIXMLRPCRequestHandler.__init__(self)
963 XMLRPCDocGenerator.__init__(self)
964
965
Fredrik Lundhb329b712001-09-17 17:35:21 +0000966if __name__ == '__main__':
Fredrik Lundhb329b712001-09-17 17:35:21 +0000967 server = SimpleXMLRPCServer(("localhost", 8000))
968 server.register_function(pow)
969 server.register_function(lambda x,y: x+y, 'add')
Florent Xicluna75861df2011-10-30 20:39:24 +0100970 print('Serving XML-RPC on localhost port 8000')
971 try:
972 server.serve_forever()
973 except KeyboardInterrupt:
974 print("\nKeyboard interrupt received, exiting.")
975 server.server_close()
976 sys.exit(0)