blob: 4ddc0049d6702f7059dc1d1c6e1307ff8fcb48ca [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:
70 raise '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
Georg Brandl38eceaa2008-05-26 11:14:17 +0000107from xmlrpc.client import Fault, dumps, loads
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
Guido van Rossumd59da4b2007-05-22 18:11:13 +0000152 hasattr(getattr(obj, member), '__call__')]
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
158 and then to dispatch them. There should never be any
159 reason to instantiate this class directly.
160 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000161
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000162 def __init__(self, allow_none, encoding):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000163 self.funcs = {}
164 self.instance = None
Andrew M. Kuchling10a16de2005-12-04 16:34:40 +0000165 self.allow_none = allow_none
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000166 self.encoding = encoding
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000167
Guido van Rossumd0641422005-02-03 15:01:24 +0000168 def register_instance(self, instance, allow_dotted_names=False):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000169 """Registers an instance to respond to XML-RPC requests.
170
171 Only one instance can be installed at a time.
172
173 If the registered instance has a _dispatch method then that
174 method will be called with the name of the XML-RPC method and
Georg Brandl7eb4b7d2005-07-22 21:49:32 +0000175 its parameters as a tuple
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000176 e.g. instance._dispatch('add',(2,3))
177
178 If the registered instance does not have a _dispatch method
179 then the instance will be searched to find a matching method
180 and, if found, will be called. Methods beginning with an '_'
181 are considered private and will not be called by
182 SimpleXMLRPCServer.
183
184 If a registered function matches a XML-RPC request, then it
185 will be called instead of the registered instance.
Guido van Rossumd0641422005-02-03 15:01:24 +0000186
187 If the optional allow_dotted_names argument is true and the
188 instance does not have a _dispatch method, method names
189 containing dots are supported and resolved, as long as none of
190 the name segments start with an '_'.
191
192 *** SECURITY WARNING: ***
193
194 Enabling the allow_dotted_names options allows intruders
195 to access your module's global variables and may allow
196 intruders to execute arbitrary code on your machine. Only
197 use this option on a secure, closed network.
198
Fredrik Lundhb329b712001-09-17 17:35:21 +0000199 """
200
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000201 self.instance = instance
Guido van Rossumd0641422005-02-03 15:01:24 +0000202 self.allow_dotted_names = allow_dotted_names
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000203
204 def register_function(self, function, name = None):
205 """Registers a function to respond to XML-RPC requests.
206
207 The optional name argument can be used to set a Unicode name
208 for the function.
209 """
210
211 if name is None:
212 name = function.__name__
213 self.funcs[name] = function
214
215 def register_introspection_functions(self):
216 """Registers the XML-RPC introspection methods in the system
217 namespace.
218
219 see http://xmlrpc.usefulinc.com/doc/reserved.html
220 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000221
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000222 self.funcs.update({'system.listMethods' : self.system_listMethods,
223 'system.methodSignature' : self.system_methodSignature,
224 'system.methodHelp' : self.system_methodHelp})
225
226 def register_multicall_functions(self):
227 """Registers the XML-RPC multicall method in the system
228 namespace.
229
230 see http://www.xmlrpc.com/discuss/msgReader$1208"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000231
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000232 self.funcs.update({'system.multicall' : self.system_multicall})
Tim Peters2c60f7a2003-01-29 03:49:43 +0000233
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000234 def _marshaled_dispatch(self, data, dispatch_method = None):
235 """Dispatches an XML-RPC method from marshalled (XML) data.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000236
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000237 XML-RPC methods are dispatched from the marshalled (XML) data
238 using the _dispatch method and the result is returned as
239 marshalled data. For backwards compatibility, a dispatch
Tim Peters2c60f7a2003-01-29 03:49:43 +0000240 function can be provided as an argument (see comment in
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000241 SimpleXMLRPCRequestHandler.do_POST) but overriding the
242 existing method through subclassing is the prefered means
243 of changing method dispatch behavior.
244 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000245
Fredrik Lundhb329b712001-09-17 17:35:21 +0000246 try:
Georg Brandl38eceaa2008-05-26 11:14:17 +0000247 params, method = loads(data)
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000248
249 # generate response
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000250 if dispatch_method is not None:
251 response = dispatch_method(method, params)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000252 else:
Fredrik Lundhb329b712001-09-17 17:35:21 +0000253 response = self._dispatch(method, params)
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000254 # wrap response in a singleton tuple
255 response = (response,)
Georg Brandl38eceaa2008-05-26 11:14:17 +0000256 response = dumps(response, methodresponse=1,
257 allow_none=self.allow_none, encoding=self.encoding)
Guido van Rossumb940e112007-01-10 16:19:56 +0000258 except Fault as fault:
Georg Brandl38eceaa2008-05-26 11:14:17 +0000259 response = dumps(fault, allow_none=self.allow_none,
260 encoding=self.encoding)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000261 except:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000262 # report exception back to server
Thomas Wouters89f507f2006-12-13 04:49:30 +0000263 exc_type, exc_value, exc_tb = sys.exc_info()
Georg Brandl38eceaa2008-05-26 11:14:17 +0000264 response = dumps(
265 Fault(1, "%s:%s" % (exc_type, exc_value)),
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000266 encoding=self.encoding, allow_none=self.allow_none,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000267 )
268
269 return response
270
271 def system_listMethods(self):
272 """system.listMethods() => ['add', 'subtract', 'multiple']
273
274 Returns a list of the methods supported by the server."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000275
Hye-Shik Chang96042862007-08-19 10:49:11 +0000276 methods = set(self.funcs.keys())
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000277 if self.instance is not None:
278 # Instance can implement _listMethod to return a list of
279 # methods
280 if hasattr(self.instance, '_listMethods'):
Hye-Shik Chang96042862007-08-19 10:49:11 +0000281 methods |= set(self.instance._listMethods())
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000282 # if the instance has a _dispatch method then we
283 # don't have enough information to provide a list
284 # of methods
285 elif not hasattr(self.instance, '_dispatch'):
Hye-Shik Chang96042862007-08-19 10:49:11 +0000286 methods |= set(list_public_methods(self.instance))
287 return sorted(methods)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000288
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000289 def system_methodSignature(self, method_name):
290 """system.methodSignature('add') => [double, int, int]
291
Brett Cannonb9b5f162004-10-03 23:21:44 +0000292 Returns a list describing the signature of the method. In the
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000293 above example, the add method takes two integers as arguments
294 and returns a double result.
295
296 This server does NOT support system.methodSignature."""
297
298 # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
Tim Peters2c60f7a2003-01-29 03:49:43 +0000299
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000300 return 'signatures not supported'
301
302 def system_methodHelp(self, method_name):
303 """system.methodHelp('add') => "Adds two integers together"
304
305 Returns a string containing documentation for the specified method."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000306
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000307 method = None
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000308 if method_name in self.funcs:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000309 method = self.funcs[method_name]
310 elif self.instance is not None:
311 # Instance can implement _methodHelp to return help for a method
312 if hasattr(self.instance, '_methodHelp'):
313 return self.instance._methodHelp(method_name)
314 # if the instance has a _dispatch method then we
315 # don't have enough information to provide help
316 elif not hasattr(self.instance, '_dispatch'):
317 try:
318 method = resolve_dotted_attribute(
319 self.instance,
Guido van Rossumd0641422005-02-03 15:01:24 +0000320 method_name,
321 self.allow_dotted_names
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000322 )
323 except AttributeError:
324 pass
325
326 # Note that we aren't checking that the method actually
327 # be a callable object of some kind
328 if method is None:
329 return ""
Fredrik Lundhb329b712001-09-17 17:35:21 +0000330 else:
Neal Norwitz732911f2003-06-29 04:16:28 +0000331 import pydoc
Neal Norwitz3f401f02003-06-29 04:19:37 +0000332 return pydoc.getdoc(method)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000333
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000334 def system_multicall(self, call_list):
335 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
336[[4], ...]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000337
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000338 Allows the caller to package multiple XML-RPC calls into a single
339 request.
340
Tim Peters2c60f7a2003-01-29 03:49:43 +0000341 See http://www.xmlrpc.com/discuss/msgReader$1208
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000342 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000343
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000344 results = []
345 for call in call_list:
346 method_name = call['methodName']
347 params = call['params']
348
349 try:
350 # XXX A marshalling error in any response will fail the entire
351 # multicall. If someone cares they should fix this.
352 results.append([self._dispatch(method_name, params)])
Guido van Rossumb940e112007-01-10 16:19:56 +0000353 except Fault as fault:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000354 results.append(
355 {'faultCode' : fault.faultCode,
356 'faultString' : fault.faultString}
357 )
358 except:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000359 exc_type, exc_value, exc_tb = sys.exc_info()
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000360 results.append(
361 {'faultCode' : 1,
Thomas Wouters89f507f2006-12-13 04:49:30 +0000362 'faultString' : "%s:%s" % (exc_type, exc_value)}
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000363 )
364 return results
Tim Peters2c60f7a2003-01-29 03:49:43 +0000365
Fredrik Lundhb329b712001-09-17 17:35:21 +0000366 def _dispatch(self, method, params):
367 """Dispatches the XML-RPC method.
368
369 XML-RPC calls are forwarded to a registered function that
370 matches the called XML-RPC method name. If no such function
371 exists then the call is forwarded to the registered instance,
372 if available.
373
374 If the registered instance has a _dispatch method then that
375 method will be called with the name of the XML-RPC method and
Georg Brandl7eb4b7d2005-07-22 21:49:32 +0000376 its parameters as a tuple
Fredrik Lundhb329b712001-09-17 17:35:21 +0000377 e.g. instance._dispatch('add',(2,3))
378
379 If the registered instance does not have a _dispatch method
380 then the instance will be searched to find a matching method
381 and, if found, will be called.
382
383 Methods beginning with an '_' are considered private and will
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000384 not be called.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000385 """
386
Fredrik Lundhb329b712001-09-17 17:35:21 +0000387 func = None
388 try:
389 # check to see if a matching function has been registered
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000390 func = self.funcs[method]
Fredrik Lundhb329b712001-09-17 17:35:21 +0000391 except KeyError:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000392 if self.instance is not None:
Fredrik Lundhb329b712001-09-17 17:35:21 +0000393 # check for a _dispatch method
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000394 if hasattr(self.instance, '_dispatch'):
395 return self.instance._dispatch(method, params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000396 else:
397 # call instance method directly
398 try:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000399 func = resolve_dotted_attribute(
400 self.instance,
Guido van Rossumd0641422005-02-03 15:01:24 +0000401 method,
402 self.allow_dotted_names
Fredrik Lundhb329b712001-09-17 17:35:21 +0000403 )
404 except AttributeError:
405 pass
406
407 if func is not None:
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000408 return func(*params)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000409 else:
410 raise Exception('method "%s" is not supported' % method)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000411
Georg Brandl24420152008-05-26 16:32:26 +0000412class SimpleXMLRPCRequestHandler(BaseHTTPRequestHandler):
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000413 """Simple XML-RPC request handler class.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000414
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000415 Handles all HTTP POST requests and attempts to decode them as
416 XML-RPC requests.
417 """
418
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000419 # Class attribute listing the accessible path components;
420 # paths not on this list will result in a 404 error.
421 rpc_paths = ('/', '/RPC2')
422
423 def is_rpc_path_valid(self):
424 if self.rpc_paths:
425 return self.path in self.rpc_paths
426 else:
427 # If .rpc_paths is empty, just assume all paths are legal
428 return True
429
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000430 def do_POST(self):
431 """Handles the HTTP POST request.
432
433 Attempts to interpret all HTTP POST requests as XML-RPC calls,
434 which are forwarded to the server's _dispatch method for handling.
435 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000436
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000437 # Check that the path is legal
438 if not self.is_rpc_path_valid():
439 self.report_404()
440 return
441
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000442 try:
Tim Peters536cf992005-12-25 23:18:31 +0000443 # Get arguments by reading body of request.
444 # We read this in chunks to avoid straining
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000445 # socket.read(); around the 10 or 15Mb mark, some platforms
446 # begin to have problems (bug #792570).
447 max_chunk_size = 10*1024*1024
448 size_remaining = int(self.headers["content-length"])
449 L = []
450 while size_remaining:
451 chunk_size = min(size_remaining, max_chunk_size)
452 L.append(self.rfile.read(chunk_size))
453 size_remaining -= len(L[-1])
Hye-Shik Chang96042862007-08-19 10:49:11 +0000454 data = b''.join(L)
Andrew M. Kuchlinge63fde72005-12-04 15:36:57 +0000455
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000456 # In previous versions of SimpleXMLRPCServer, _dispatch
457 # could be overridden in this class, instead of in
458 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
459 # check to see if a subclass implements _dispatch and dispatch
460 # using that method if present.
461 response = self.server._marshaled_dispatch(
462 data, getattr(self, '_dispatch', None)
463 )
Guido van Rossum61e21b52007-08-20 19:06:03 +0000464 except Exception as e: # This should only happen if the module is buggy
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000465 # internal error, report as HTTP server error
466 self.send_response(500)
Guido van Rossum61e21b52007-08-20 19:06:03 +0000467
468 # Send information about the exception if requested
469 if hasattr(self.server, '_send_traceback_header') and \
470 self.server._send_traceback_header:
471 self.send_header("X-exception", str(e))
472 self.send_header("X-traceback", traceback.format_exc())
473
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000474 self.end_headers()
475 else:
Guido van Rossum8a392d72007-11-21 22:09:45 +0000476 # Got a valid XML RPC response; convert to bytes first
477 response = response.encode("utf-8")
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000478 self.send_response(200)
479 self.send_header("Content-type", "text/xml")
480 self.send_header("Content-length", str(len(response)))
481 self.end_headers()
482 self.wfile.write(response)
483
484 # shut down the connection
485 self.wfile.flush()
486 self.connection.shutdown(1)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000487
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000488 def report_404 (self):
489 # Report a 404 error
490 self.send_response(404)
Christian Heimes0aa93cd2007-12-08 18:38:20 +0000491 response = b'No such page'
Thomas Wouters4d70c3d2006-06-08 14:42:34 +0000492 self.send_header("Content-type", "text/plain")
493 self.send_header("Content-length", str(len(response)))
494 self.end_headers()
495 self.wfile.write(response)
496 # shut down the connection
497 self.wfile.flush()
498 self.connection.shutdown(1)
499
Fredrik Lundhb329b712001-09-17 17:35:21 +0000500 def log_request(self, code='-', size='-'):
501 """Selectively log an accepted request."""
502
503 if self.server.logRequests:
Georg Brandl24420152008-05-26 16:32:26 +0000504 BaseHTTPRequestHandler.log_request(self, code, size)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000505
Alexandre Vassalottice261952008-05-12 02:31:37 +0000506class SimpleXMLRPCServer(socketserver.TCPServer,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000507 SimpleXMLRPCDispatcher):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000508 """Simple XML-RPC server.
509
510 Simple XML-RPC server that allows functions and a single instance
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000511 to be installed to handle requests. The default implementation
512 attempts to dispatch XML-RPC calls to the functions or instance
513 installed in the server. Override the _dispatch method inhereted
514 from SimpleXMLRPCDispatcher to change this behavior.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000515 """
516
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000517 allow_reuse_address = True
518
Guido van Rossum61e21b52007-08-20 19:06:03 +0000519 # Warning: this is for debugging purposes only! Never set this to True in
520 # production code, as will be sending out sensitive information (exception
521 # and stack trace details) when exceptions are raised inside
522 # SimpleXMLRPCRequestHandler.do_POST
523 _send_traceback_header = False
524
Fredrik Lundhb329b712001-09-17 17:35:21 +0000525 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
Guido van Rossumd8faa362007-04-27 19:54:29 +0000526 logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
Fredrik Lundhb329b712001-09-17 17:35:21 +0000527 self.logRequests = logRequests
Tim Peters2c60f7a2003-01-29 03:49:43 +0000528
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000529 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
Alexandre Vassalottice261952008-05-12 02:31:37 +0000530 socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000531
Tim Peters536cf992005-12-25 23:18:31 +0000532 # [Bug #1222790] If possible, set close-on-exec flag; if a
533 # method spawns a subprocess, the subprocess shouldn't have
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000534 # the listening socket open.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000535 if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
Andrew M. Kuchling3a976052005-12-04 15:07:41 +0000536 flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
537 flags |= fcntl.FD_CLOEXEC
538 fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
539
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000540class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
541 """Simple handler for XML-RPC data passed through CGI."""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000542
Andrew M. Kuchling427aedb2005-12-04 17:13:12 +0000543 def __init__(self, allow_none=False, encoding=None):
544 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000545
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000546 def handle_xmlrpc(self, request_text):
547 """Handle a single XML-RPC request"""
Tim Peters2c60f7a2003-01-29 03:49:43 +0000548
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000549 response = self._marshaled_dispatch(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000550
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000551 print('Content-Type: text/xml')
552 print('Content-Length: %d' % len(response))
553 print()
Martin v. Löwis9c5ea502003-05-01 05:05:09 +0000554 sys.stdout.write(response)
Fredrik Lundhb329b712001-09-17 17:35:21 +0000555
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000556 def handle_get(self):
557 """Handle a single HTTP GET request.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000558
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000559 Default implementation indicates an error because
560 XML-RPC uses the POST method.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000561 """
562
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000563 code = 400
Georg Brandl24420152008-05-26 16:32:26 +0000564 message, explain = BaseHTTPRequestHandler.responses[code]
Tim Peters2c60f7a2003-01-29 03:49:43 +0000565
Georg Brandl24420152008-05-26 16:32:26 +0000566 response = http.server.DEFAULT_ERROR_MESSAGE % \
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000567 {
Tim Peters2c60f7a2003-01-29 03:49:43 +0000568 'code' : code,
569 'message' : message,
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000570 'explain' : explain
571 }
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000572 print('Status: %d %s' % (code, message))
573 print('Content-Type: text/html')
574 print('Content-Length: %d' % len(response))
575 print()
Neal Norwitz732911f2003-06-29 04:16:28 +0000576 sys.stdout.write(response)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000577
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000578 def handle_request(self, request_text = None):
579 """Handle a single XML-RPC request passed through a CGI post method.
Tim Peters2c60f7a2003-01-29 03:49:43 +0000580
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000581 If no XML data is given then it is read from stdin. The resulting
582 XML-RPC response is printed to stdout along with the correct HTTP
583 headers.
Fredrik Lundhb329b712001-09-17 17:35:21 +0000584 """
Tim Peters2c60f7a2003-01-29 03:49:43 +0000585
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000586 if request_text is None and \
587 os.environ.get('REQUEST_METHOD', None) == 'GET':
588 self.handle_get()
589 else:
590 # POST data is normally available through stdin
591 if request_text is None:
Tim Peters2c60f7a2003-01-29 03:49:43 +0000592 request_text = sys.stdin.read()
Fredrik Lundhb329b712001-09-17 17:35:21 +0000593
Martin v. Löwisd69663d2003-01-15 11:37:23 +0000594 self.handle_xmlrpc(request_text)
Tim Peters2c60f7a2003-01-29 03:49:43 +0000595
Georg Brandl38eceaa2008-05-26 11:14:17 +0000596
597# -----------------------------------------------------------------------------
598# Self documenting XML-RPC Server.
599
600class ServerHTMLDoc(pydoc.HTMLDoc):
601 """Class used to generate pydoc HTML document for a server"""
602
603 def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
604 """Mark up some plain text, given a context of symbols to look for.
605 Each context dictionary maps object names to anchor names."""
606 escape = escape or self.escape
607 results = []
608 here = 0
609
610 # XXX Note that this regular expression does not allow for the
611 # hyperlinking of arbitrary strings being used as method
612 # names. Only methods with names consisting of word characters
613 # and '.'s are hyperlinked.
614 pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
615 r'RFC[- ]?(\d+)|'
616 r'PEP[- ]?(\d+)|'
617 r'(self\.)?((?:\w|\.)+))\b')
618 while 1:
619 match = pattern.search(text, here)
620 if not match: break
621 start, end = match.span()
622 results.append(escape(text[here:start]))
623
624 all, scheme, rfc, pep, selfdot, name = match.groups()
625 if scheme:
626 url = escape(all).replace('"', '"')
627 results.append('<a href="%s">%s</a>' % (url, url))
628 elif rfc:
629 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
630 results.append('<a href="%s">%s</a>' % (url, escape(all)))
631 elif pep:
632 url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
633 results.append('<a href="%s">%s</a>' % (url, escape(all)))
634 elif text[end:end+1] == '(':
635 results.append(self.namelink(name, methods, funcs, classes))
636 elif selfdot:
637 results.append('self.<strong>%s</strong>' % name)
638 else:
639 results.append(self.namelink(name, classes))
640 here = end
641 results.append(escape(text[here:]))
642 return ''.join(results)
643
644 def docroutine(self, object, name, mod=None,
645 funcs={}, classes={}, methods={}, cl=None):
646 """Produce HTML documentation for a function or method object."""
647
648 anchor = (cl and cl.__name__ or '') + '-' + name
649 note = ''
650
651 title = '<a name="%s"><strong>%s</strong></a>' % (
652 self.escape(anchor), self.escape(name))
653
654 if inspect.ismethod(object):
655 args, varargs, varkw, defaults = inspect.getargspec(object)
656 # exclude the argument bound to the instance, it will be
657 # confusing to the non-Python user
658 argspec = inspect.formatargspec (
659 args[1:],
660 varargs,
661 varkw,
662 defaults,
663 formatvalue=self.formatvalue
664 )
665 elif inspect.isfunction(object):
666 args, varargs, varkw, defaults = inspect.getargspec(object)
667 argspec = inspect.formatargspec(
668 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
669 else:
670 argspec = '(...)'
671
672 if isinstance(object, tuple):
673 argspec = object[0] or argspec
674 docstring = object[1] or ""
675 else:
676 docstring = pydoc.getdoc(object)
677
678 decl = title + argspec + (note and self.grey(
679 '<font face="helvetica, arial">%s</font>' % note))
680
681 doc = self.markup(
682 docstring, self.preformat, funcs, classes, methods)
683 doc = doc and '<dd><tt>%s</tt></dd>' % doc
684 return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
685
686 def docserver(self, server_name, package_documentation, methods):
687 """Produce HTML documentation for an XML-RPC server."""
688
689 fdict = {}
690 for key, value in methods.items():
691 fdict[key] = '#-' + key
692 fdict[value] = fdict[key]
693
694 server_name = self.escape(server_name)
695 head = '<big><big><strong>%s</strong></big></big>' % server_name
696 result = self.heading(head, '#ffffff', '#7799ee')
697
698 doc = self.markup(package_documentation, self.preformat, fdict)
699 doc = doc and '<tt>%s</tt>' % doc
700 result = result + '<p>%s</p>\n' % doc
701
702 contents = []
703 method_items = sorted(methods.items())
704 for key, value in method_items:
705 contents.append(self.docroutine(value, key, funcs=fdict))
706 result = result + self.bigsection(
707 'Methods', '#ffffff', '#eeaa77', ''.join(contents))
708
709 return result
710
711class XMLRPCDocGenerator:
712 """Generates documentation for an XML-RPC server.
713
714 This class is designed as mix-in and should not
715 be constructed directly.
716 """
717
718 def __init__(self):
719 # setup variables used for HTML documentation
720 self.server_name = 'XML-RPC Server Documentation'
721 self.server_documentation = \
722 "This server exports the following methods through the XML-RPC "\
723 "protocol."
724 self.server_title = 'XML-RPC Server Documentation'
725
726 def set_server_title(self, server_title):
727 """Set the HTML title of the generated server documentation"""
728
729 self.server_title = server_title
730
731 def set_server_name(self, server_name):
732 """Set the name of the generated HTML server documentation"""
733
734 self.server_name = server_name
735
736 def set_server_documentation(self, server_documentation):
737 """Set the documentation string for the entire server."""
738
739 self.server_documentation = server_documentation
740
741 def generate_html_documentation(self):
742 """generate_html_documentation() => html documentation for the server
743
744 Generates HTML documentation for the server using introspection for
745 installed functions and instances that do not implement the
746 _dispatch method. Alternatively, instances can choose to implement
747 the _get_method_argstring(method_name) method to provide the
748 argument string used in the documentation and the
749 _methodHelp(method_name) method to provide the help text used
750 in the documentation."""
751
752 methods = {}
753
754 for method_name in self.system_listMethods():
755 if method_name in self.funcs:
756 method = self.funcs[method_name]
757 elif self.instance is not None:
758 method_info = [None, None] # argspec, documentation
759 if hasattr(self.instance, '_get_method_argstring'):
760 method_info[0] = self.instance._get_method_argstring(method_name)
761 if hasattr(self.instance, '_methodHelp'):
762 method_info[1] = self.instance._methodHelp(method_name)
763
764 method_info = tuple(method_info)
765 if method_info != (None, None):
766 method = method_info
767 elif not hasattr(self.instance, '_dispatch'):
768 try:
769 method = resolve_dotted_attribute(
770 self.instance,
771 method_name
772 )
773 except AttributeError:
774 method = method_info
775 else:
776 method = method_info
777 else:
778 assert 0, "Could not find method in self.functions and no "\
779 "instance installed"
780
781 methods[method_name] = method
782
783 documenter = ServerHTMLDoc()
784 documentation = documenter.docserver(
785 self.server_name,
786 self.server_documentation,
787 methods
788 )
789
790 return documenter.page(self.server_title, documentation)
791
792class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
793 """XML-RPC and documentation request handler class.
794
795 Handles all HTTP POST requests and attempts to decode them as
796 XML-RPC requests.
797
798 Handles all HTTP GET requests and interprets them as requests
799 for documentation.
800 """
801
802 def do_GET(self):
803 """Handles the HTTP GET request.
804
805 Interpret all HTTP GET requests as requests for server
806 documentation.
807 """
808 # Check that the path is legal
809 if not self.is_rpc_path_valid():
810 self.report_404()
811 return
812
813 response = self.server.generate_html_documentation()
814 self.send_response(200)
815 self.send_header("Content-type", "text/html")
816 self.send_header("Content-length", str(len(response)))
817 self.end_headers()
818 self.wfile.write(response.encode())
819
820 # shut down the connection
821 self.wfile.flush()
822 self.connection.shutdown(1)
823
824class DocXMLRPCServer( SimpleXMLRPCServer,
825 XMLRPCDocGenerator):
826 """XML-RPC and HTML documentation server.
827
828 Adds the ability to serve server documentation to the capabilities
829 of SimpleXMLRPCServer.
830 """
831
832 def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
833 logRequests=1, allow_none=False, encoding=None,
834 bind_and_activate=True):
835 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests,
836 allow_none, encoding, bind_and_activate)
837 XMLRPCDocGenerator.__init__(self)
838
839class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,
840 XMLRPCDocGenerator):
841 """Handler for XML-RPC data and documentation requests passed through
842 CGI"""
843
844 def handle_get(self):
845 """Handles the HTTP GET request.
846
847 Interpret all HTTP GET requests as requests for server
848 documentation.
849 """
850
851 response = self.generate_html_documentation()
852
853 print('Content-Type: text/html')
854 print('Content-Length: %d' % len(response))
855 print()
856 sys.stdout.write(response)
857
858 def __init__(self):
859 CGIXMLRPCRequestHandler.__init__(self)
860 XMLRPCDocGenerator.__init__(self)
861
862
Fredrik Lundhb329b712001-09-17 17:35:21 +0000863if __name__ == '__main__':
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000864 print('Running XML-RPC server on port 8000')
Fredrik Lundhb329b712001-09-17 17:35:21 +0000865 server = SimpleXMLRPCServer(("localhost", 8000))
866 server.register_function(pow)
867 server.register_function(lambda x,y: x+y, 'add')
868 server.serve_forever()