1 | """Simple XML-RPC Server.
|
---|
2 |
|
---|
3 | This module can be used to create simple XML-RPC servers
|
---|
4 | by creating a server and either installing functions, a
|
---|
5 | class instance, or by extending the SimpleXMLRPCServer
|
---|
6 | class.
|
---|
7 |
|
---|
8 | It can also be used to handle XML-RPC requests in a CGI
|
---|
9 | environment using CGIXMLRPCRequestHandler.
|
---|
10 |
|
---|
11 | A list of possible usage patterns follows:
|
---|
12 |
|
---|
13 | 1. Install functions:
|
---|
14 |
|
---|
15 | server = SimpleXMLRPCServer(("localhost", 8000))
|
---|
16 | server.register_function(pow)
|
---|
17 | server.register_function(lambda x,y: x+y, 'add')
|
---|
18 | server.serve_forever()
|
---|
19 |
|
---|
20 | 2. Install an instance:
|
---|
21 |
|
---|
22 | class MyFuncs:
|
---|
23 | def __init__(self):
|
---|
24 | # make all of the string functions available through
|
---|
25 | # string.func_name
|
---|
26 | import string
|
---|
27 | self.string = string
|
---|
28 | def _listMethods(self):
|
---|
29 | # implement this method so that system.listMethods
|
---|
30 | # knows to advertise the strings methods
|
---|
31 | return list_public_methods(self) + \
|
---|
32 | ['string.' + method for method in list_public_methods(self.string)]
|
---|
33 | def pow(self, x, y): return pow(x, y)
|
---|
34 | def add(self, x, y) : return x + y
|
---|
35 |
|
---|
36 | server = SimpleXMLRPCServer(("localhost", 8000))
|
---|
37 | server.register_introspection_functions()
|
---|
38 | server.register_instance(MyFuncs())
|
---|
39 | server.serve_forever()
|
---|
40 |
|
---|
41 | 3. Install an instance with custom dispatch method:
|
---|
42 |
|
---|
43 | class Math:
|
---|
44 | def _listMethods(self):
|
---|
45 | # this method must be present for system.listMethods
|
---|
46 | # to work
|
---|
47 | return ['add', 'pow']
|
---|
48 | def _methodHelp(self, method):
|
---|
49 | # this method must be present for system.methodHelp
|
---|
50 | # to work
|
---|
51 | if method == 'add':
|
---|
52 | return "add(2,3) => 5"
|
---|
53 | elif method == 'pow':
|
---|
54 | return "pow(x, y[, z]) => number"
|
---|
55 | else:
|
---|
56 | # By convention, return empty
|
---|
57 | # string if no help is available
|
---|
58 | return ""
|
---|
59 | def _dispatch(self, method, params):
|
---|
60 | if method == 'pow':
|
---|
61 | return pow(*params)
|
---|
62 | elif method == 'add':
|
---|
63 | return params[0] + params[1]
|
---|
64 | else:
|
---|
65 | raise 'bad method'
|
---|
66 |
|
---|
67 | server = SimpleXMLRPCServer(("localhost", 8000))
|
---|
68 | server.register_introspection_functions()
|
---|
69 | server.register_instance(Math())
|
---|
70 | server.serve_forever()
|
---|
71 |
|
---|
72 | 4. Subclass SimpleXMLRPCServer:
|
---|
73 |
|
---|
74 | class MathServer(SimpleXMLRPCServer):
|
---|
75 | def _dispatch(self, method, params):
|
---|
76 | try:
|
---|
77 | # We are forcing the 'export_' prefix on methods that are
|
---|
78 | # callable through XML-RPC to prevent potential security
|
---|
79 | # problems
|
---|
80 | func = getattr(self, 'export_' + method)
|
---|
81 | except AttributeError:
|
---|
82 | raise Exception('method "%s" is not supported' % method)
|
---|
83 | else:
|
---|
84 | return func(*params)
|
---|
85 |
|
---|
86 | def export_add(self, x, y):
|
---|
87 | return x + y
|
---|
88 |
|
---|
89 | server = MathServer(("localhost", 8000))
|
---|
90 | server.serve_forever()
|
---|
91 |
|
---|
92 | 5. CGI script:
|
---|
93 |
|
---|
94 | server = CGIXMLRPCRequestHandler()
|
---|
95 | server.register_function(pow)
|
---|
96 | server.handle_request()
|
---|
97 | """
|
---|
98 |
|
---|
99 | # Written by Brian Quinlan (brian@sweetapp.com).
|
---|
100 | # Based on code written by Fredrik Lundh.
|
---|
101 |
|
---|
102 | import xmlrpclib
|
---|
103 | from xmlrpclib import Fault
|
---|
104 | import SocketServer
|
---|
105 | import BaseHTTPServer
|
---|
106 | import sys
|
---|
107 | import os
|
---|
108 | import traceback
|
---|
109 | try:
|
---|
110 | import fcntl
|
---|
111 | except ImportError:
|
---|
112 | fcntl = None
|
---|
113 |
|
---|
114 | def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
|
---|
115 | """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
|
---|
116 |
|
---|
117 | Resolves a dotted attribute name to an object. Raises
|
---|
118 | an AttributeError if any attribute in the chain starts with a '_'.
|
---|
119 |
|
---|
120 | If the optional allow_dotted_names argument is false, dots are not
|
---|
121 | supported and this function operates similar to getattr(obj, attr).
|
---|
122 | """
|
---|
123 |
|
---|
124 | if allow_dotted_names:
|
---|
125 | attrs = attr.split('.')
|
---|
126 | else:
|
---|
127 | attrs = [attr]
|
---|
128 |
|
---|
129 | for i in attrs:
|
---|
130 | if i.startswith('_'):
|
---|
131 | raise AttributeError(
|
---|
132 | 'attempt to access private attribute "%s"' % i
|
---|
133 | )
|
---|
134 | else:
|
---|
135 | obj = getattr(obj,i)
|
---|
136 | return obj
|
---|
137 |
|
---|
138 | def list_public_methods(obj):
|
---|
139 | """Returns a list of attribute strings, found in the specified
|
---|
140 | object, which represent callable attributes"""
|
---|
141 |
|
---|
142 | return [member for member in dir(obj)
|
---|
143 | if not member.startswith('_') and
|
---|
144 | hasattr(getattr(obj, member), '__call__')]
|
---|
145 |
|
---|
146 | def remove_duplicates(lst):
|
---|
147 | """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
|
---|
148 |
|
---|
149 | Returns a copy of a list without duplicates. Every list
|
---|
150 | item must be hashable and the order of the items in the
|
---|
151 | resulting list is not defined.
|
---|
152 | """
|
---|
153 | u = {}
|
---|
154 | for x in lst:
|
---|
155 | u[x] = 1
|
---|
156 |
|
---|
157 | return u.keys()
|
---|
158 |
|
---|
159 | class SimpleXMLRPCDispatcher:
|
---|
160 | """Mix-in class that dispatches XML-RPC requests.
|
---|
161 |
|
---|
162 | This class is used to register XML-RPC method handlers
|
---|
163 | and then to dispatch them. There should never be any
|
---|
164 | reason to instantiate this class directly.
|
---|
165 | """
|
---|
166 |
|
---|
167 | def __init__(self, allow_none=False, encoding=None):
|
---|
168 | self.funcs = {}
|
---|
169 | self.instance = None
|
---|
170 | self.allow_none = allow_none
|
---|
171 | self.encoding = encoding
|
---|
172 |
|
---|
173 | def register_instance(self, instance, allow_dotted_names=False):
|
---|
174 | """Registers an instance to respond to XML-RPC requests.
|
---|
175 |
|
---|
176 | Only one instance can be installed at a time.
|
---|
177 |
|
---|
178 | If the registered instance has a _dispatch method then that
|
---|
179 | method will be called with the name of the XML-RPC method and
|
---|
180 | its parameters as a tuple
|
---|
181 | e.g. instance._dispatch('add',(2,3))
|
---|
182 |
|
---|
183 | If the registered instance does not have a _dispatch method
|
---|
184 | then the instance will be searched to find a matching method
|
---|
185 | and, if found, will be called. Methods beginning with an '_'
|
---|
186 | are considered private and will not be called by
|
---|
187 | SimpleXMLRPCServer.
|
---|
188 |
|
---|
189 | If a registered function matches a XML-RPC request, then it
|
---|
190 | will be called instead of the registered instance.
|
---|
191 |
|
---|
192 | If the optional allow_dotted_names argument is true and the
|
---|
193 | instance does not have a _dispatch method, method names
|
---|
194 | containing dots are supported and resolved, as long as none of
|
---|
195 | the name segments start with an '_'.
|
---|
196 |
|
---|
197 | *** SECURITY WARNING: ***
|
---|
198 |
|
---|
199 | Enabling the allow_dotted_names options allows intruders
|
---|
200 | to access your module's global variables and may allow
|
---|
201 | intruders to execute arbitrary code on your machine. Only
|
---|
202 | use this option on a secure, closed network.
|
---|
203 |
|
---|
204 | """
|
---|
205 |
|
---|
206 | self.instance = instance
|
---|
207 | self.allow_dotted_names = allow_dotted_names
|
---|
208 |
|
---|
209 | def register_function(self, function, name = None):
|
---|
210 | """Registers a function to respond to XML-RPC requests.
|
---|
211 |
|
---|
212 | The optional name argument can be used to set a Unicode name
|
---|
213 | for the function.
|
---|
214 | """
|
---|
215 |
|
---|
216 | if name is None:
|
---|
217 | name = function.__name__
|
---|
218 | self.funcs[name] = function
|
---|
219 |
|
---|
220 | def register_introspection_functions(self):
|
---|
221 | """Registers the XML-RPC introspection methods in the system
|
---|
222 | namespace.
|
---|
223 |
|
---|
224 | see http://xmlrpc.usefulinc.com/doc/reserved.html
|
---|
225 | """
|
---|
226 |
|
---|
227 | self.funcs.update({'system.listMethods' : self.system_listMethods,
|
---|
228 | 'system.methodSignature' : self.system_methodSignature,
|
---|
229 | 'system.methodHelp' : self.system_methodHelp})
|
---|
230 |
|
---|
231 | def register_multicall_functions(self):
|
---|
232 | """Registers the XML-RPC multicall method in the system
|
---|
233 | namespace.
|
---|
234 |
|
---|
235 | see http://www.xmlrpc.com/discuss/msgReader$1208"""
|
---|
236 |
|
---|
237 | self.funcs.update({'system.multicall' : self.system_multicall})
|
---|
238 |
|
---|
239 | def _marshaled_dispatch(self, data, dispatch_method = None):
|
---|
240 | """Dispatches an XML-RPC method from marshalled (XML) data.
|
---|
241 |
|
---|
242 | XML-RPC methods are dispatched from the marshalled (XML) data
|
---|
243 | using the _dispatch method and the result is returned as
|
---|
244 | marshalled data. For backwards compatibility, a dispatch
|
---|
245 | function can be provided as an argument (see comment in
|
---|
246 | SimpleXMLRPCRequestHandler.do_POST) but overriding the
|
---|
247 | existing method through subclassing is the prefered means
|
---|
248 | of changing method dispatch behavior.
|
---|
249 | """
|
---|
250 |
|
---|
251 | try:
|
---|
252 | params, method = xmlrpclib.loads(data)
|
---|
253 |
|
---|
254 | # generate response
|
---|
255 | if dispatch_method is not None:
|
---|
256 | response = dispatch_method(method, params)
|
---|
257 | else:
|
---|
258 | response = self._dispatch(method, params)
|
---|
259 | # wrap response in a singleton tuple
|
---|
260 | response = (response,)
|
---|
261 | response = xmlrpclib.dumps(response, methodresponse=1,
|
---|
262 | allow_none=self.allow_none, encoding=self.encoding)
|
---|
263 | except Fault, fault:
|
---|
264 | response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
|
---|
265 | encoding=self.encoding)
|
---|
266 | except:
|
---|
267 | # report exception back to server
|
---|
268 | exc_type, exc_value, exc_tb = sys.exc_info()
|
---|
269 | response = xmlrpclib.dumps(
|
---|
270 | xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),
|
---|
271 | encoding=self.encoding, allow_none=self.allow_none,
|
---|
272 | )
|
---|
273 |
|
---|
274 | return response
|
---|
275 |
|
---|
276 | def system_listMethods(self):
|
---|
277 | """system.listMethods() => ['add', 'subtract', 'multiple']
|
---|
278 |
|
---|
279 | Returns a list of the methods supported by the server."""
|
---|
280 |
|
---|
281 | methods = self.funcs.keys()
|
---|
282 | if self.instance is not None:
|
---|
283 | # Instance can implement _listMethod to return a list of
|
---|
284 | # methods
|
---|
285 | if hasattr(self.instance, '_listMethods'):
|
---|
286 | methods = remove_duplicates(
|
---|
287 | methods + self.instance._listMethods()
|
---|
288 | )
|
---|
289 | # if the instance has a _dispatch method then we
|
---|
290 | # don't have enough information to provide a list
|
---|
291 | # of methods
|
---|
292 | elif not hasattr(self.instance, '_dispatch'):
|
---|
293 | methods = remove_duplicates(
|
---|
294 | methods + list_public_methods(self.instance)
|
---|
295 | )
|
---|
296 | methods.sort()
|
---|
297 | return methods
|
---|
298 |
|
---|
299 | def system_methodSignature(self, method_name):
|
---|
300 | """system.methodSignature('add') => [double, int, int]
|
---|
301 |
|
---|
302 | Returns a list describing the signature of the method. In the
|
---|
303 | above example, the add method takes two integers as arguments
|
---|
304 | and returns a double result.
|
---|
305 |
|
---|
306 | This server does NOT support system.methodSignature."""
|
---|
307 |
|
---|
308 | # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
|
---|
309 |
|
---|
310 | return 'signatures not supported'
|
---|
311 |
|
---|
312 | def system_methodHelp(self, method_name):
|
---|
313 | """system.methodHelp('add') => "Adds two integers together"
|
---|
314 |
|
---|
315 | Returns a string containing documentation for the specified method."""
|
---|
316 |
|
---|
317 | method = None
|
---|
318 | if method_name in self.funcs:
|
---|
319 | method = self.funcs[method_name]
|
---|
320 | elif self.instance is not None:
|
---|
321 | # Instance can implement _methodHelp to return help for a method
|
---|
322 | if hasattr(self.instance, '_methodHelp'):
|
---|
323 | return self.instance._methodHelp(method_name)
|
---|
324 | # if the instance has a _dispatch method then we
|
---|
325 | # don't have enough information to provide help
|
---|
326 | elif not hasattr(self.instance, '_dispatch'):
|
---|
327 | try:
|
---|
328 | method = resolve_dotted_attribute(
|
---|
329 | self.instance,
|
---|
330 | method_name,
|
---|
331 | self.allow_dotted_names
|
---|
332 | )
|
---|
333 | except AttributeError:
|
---|
334 | pass
|
---|
335 |
|
---|
336 | # Note that we aren't checking that the method actually
|
---|
337 | # be a callable object of some kind
|
---|
338 | if method is None:
|
---|
339 | return ""
|
---|
340 | else:
|
---|
341 | import pydoc
|
---|
342 | return pydoc.getdoc(method)
|
---|
343 |
|
---|
344 | def system_multicall(self, call_list):
|
---|
345 | """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
|
---|
346 | [[4], ...]
|
---|
347 |
|
---|
348 | Allows the caller to package multiple XML-RPC calls into a single
|
---|
349 | request.
|
---|
350 |
|
---|
351 | See http://www.xmlrpc.com/discuss/msgReader$1208
|
---|
352 | """
|
---|
353 |
|
---|
354 | results = []
|
---|
355 | for call in call_list:
|
---|
356 | method_name = call['methodName']
|
---|
357 | params = call['params']
|
---|
358 |
|
---|
359 | try:
|
---|
360 | # XXX A marshalling error in any response will fail the entire
|
---|
361 | # multicall. If someone cares they should fix this.
|
---|
362 | results.append([self._dispatch(method_name, params)])
|
---|
363 | except Fault, fault:
|
---|
364 | results.append(
|
---|
365 | {'faultCode' : fault.faultCode,
|
---|
366 | 'faultString' : fault.faultString}
|
---|
367 | )
|
---|
368 | except:
|
---|
369 | exc_type, exc_value, exc_tb = sys.exc_info()
|
---|
370 | results.append(
|
---|
371 | {'faultCode' : 1,
|
---|
372 | 'faultString' : "%s:%s" % (exc_type, exc_value)}
|
---|
373 | )
|
---|
374 | return results
|
---|
375 |
|
---|
376 | def _dispatch(self, method, params):
|
---|
377 | """Dispatches the XML-RPC method.
|
---|
378 |
|
---|
379 | XML-RPC calls are forwarded to a registered function that
|
---|
380 | matches the called XML-RPC method name. If no such function
|
---|
381 | exists then the call is forwarded to the registered instance,
|
---|
382 | if available.
|
---|
383 |
|
---|
384 | If the registered instance has a _dispatch method then that
|
---|
385 | method will be called with the name of the XML-RPC method and
|
---|
386 | its parameters as a tuple
|
---|
387 | e.g. instance._dispatch('add',(2,3))
|
---|
388 |
|
---|
389 | If the registered instance does not have a _dispatch method
|
---|
390 | then the instance will be searched to find a matching method
|
---|
391 | and, if found, will be called.
|
---|
392 |
|
---|
393 | Methods beginning with an '_' are considered private and will
|
---|
394 | not be called.
|
---|
395 | """
|
---|
396 |
|
---|
397 | func = None
|
---|
398 | try:
|
---|
399 | # check to see if a matching function has been registered
|
---|
400 | func = self.funcs[method]
|
---|
401 | except KeyError:
|
---|
402 | if self.instance is not None:
|
---|
403 | # check for a _dispatch method
|
---|
404 | if hasattr(self.instance, '_dispatch'):
|
---|
405 | return self.instance._dispatch(method, params)
|
---|
406 | else:
|
---|
407 | # call instance method directly
|
---|
408 | try:
|
---|
409 | func = resolve_dotted_attribute(
|
---|
410 | self.instance,
|
---|
411 | method,
|
---|
412 | self.allow_dotted_names
|
---|
413 | )
|
---|
414 | except AttributeError:
|
---|
415 | pass
|
---|
416 |
|
---|
417 | if func is not None:
|
---|
418 | return func(*params)
|
---|
419 | else:
|
---|
420 | raise Exception('method "%s" is not supported' % method)
|
---|
421 |
|
---|
422 | class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
---|
423 | """Simple XML-RPC request handler class.
|
---|
424 |
|
---|
425 | Handles all HTTP POST requests and attempts to decode them as
|
---|
426 | XML-RPC requests.
|
---|
427 | """
|
---|
428 |
|
---|
429 | # Class attribute listing the accessible path components;
|
---|
430 | # paths not on this list will result in a 404 error.
|
---|
431 | rpc_paths = ('/', '/RPC2')
|
---|
432 |
|
---|
433 | def is_rpc_path_valid(self):
|
---|
434 | if self.rpc_paths:
|
---|
435 | return self.path in self.rpc_paths
|
---|
436 | else:
|
---|
437 | # If .rpc_paths is empty, just assume all paths are legal
|
---|
438 | return True
|
---|
439 |
|
---|
440 | def do_POST(self):
|
---|
441 | """Handles the HTTP POST request.
|
---|
442 |
|
---|
443 | Attempts to interpret all HTTP POST requests as XML-RPC calls,
|
---|
444 | which are forwarded to the server's _dispatch method for handling.
|
---|
445 | """
|
---|
446 |
|
---|
447 | # Check that the path is legal
|
---|
448 | if not self.is_rpc_path_valid():
|
---|
449 | self.report_404()
|
---|
450 | return
|
---|
451 |
|
---|
452 | try:
|
---|
453 | # Get arguments by reading body of request.
|
---|
454 | # We read this in chunks to avoid straining
|
---|
455 | # socket.read(); around the 10 or 15Mb mark, some platforms
|
---|
456 | # begin to have problems (bug #792570).
|
---|
457 | max_chunk_size = 10*1024*1024
|
---|
458 | size_remaining = int(self.headers["content-length"])
|
---|
459 | L = []
|
---|
460 | while size_remaining:
|
---|
461 | chunk_size = min(size_remaining, max_chunk_size)
|
---|
462 | L.append(self.rfile.read(chunk_size))
|
---|
463 | size_remaining -= len(L[-1])
|
---|
464 | data = ''.join(L)
|
---|
465 |
|
---|
466 | # In previous versions of SimpleXMLRPCServer, _dispatch
|
---|
467 | # could be overridden in this class, instead of in
|
---|
468 | # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
|
---|
469 | # check to see if a subclass implements _dispatch and dispatch
|
---|
470 | # using that method if present.
|
---|
471 | response = self.server._marshaled_dispatch(
|
---|
472 | data, getattr(self, '_dispatch', None)
|
---|
473 | )
|
---|
474 | except Exception, e: # This should only happen if the module is buggy
|
---|
475 | # internal error, report as HTTP server error
|
---|
476 | self.send_response(500)
|
---|
477 |
|
---|
478 | # Send information about the exception if requested
|
---|
479 | if hasattr(self.server, '_send_traceback_header') and \
|
---|
480 | self.server._send_traceback_header:
|
---|
481 | self.send_header("X-exception", str(e))
|
---|
482 | self.send_header("X-traceback", traceback.format_exc())
|
---|
483 |
|
---|
484 | self.end_headers()
|
---|
485 | else:
|
---|
486 | # got a valid XML RPC response
|
---|
487 | self.send_response(200)
|
---|
488 | self.send_header("Content-type", "text/xml")
|
---|
489 | self.send_header("Content-length", str(len(response)))
|
---|
490 | self.end_headers()
|
---|
491 | self.wfile.write(response)
|
---|
492 |
|
---|
493 | # shut down the connection
|
---|
494 | self.wfile.flush()
|
---|
495 | self.connection.shutdown(1)
|
---|
496 |
|
---|
497 | def report_404 (self):
|
---|
498 | # Report a 404 error
|
---|
499 | self.send_response(404)
|
---|
500 | response = 'No such page'
|
---|
501 | self.send_header("Content-type", "text/plain")
|
---|
502 | self.send_header("Content-length", str(len(response)))
|
---|
503 | self.end_headers()
|
---|
504 | self.wfile.write(response)
|
---|
505 | # shut down the connection
|
---|
506 | self.wfile.flush()
|
---|
507 | self.connection.shutdown(1)
|
---|
508 |
|
---|
509 | def log_request(self, code='-', size='-'):
|
---|
510 | """Selectively log an accepted request."""
|
---|
511 |
|
---|
512 | if self.server.logRequests:
|
---|
513 | BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
|
---|
514 |
|
---|
515 | class SimpleXMLRPCServer(SocketServer.TCPServer,
|
---|
516 | SimpleXMLRPCDispatcher):
|
---|
517 | """Simple XML-RPC server.
|
---|
518 |
|
---|
519 | Simple XML-RPC server that allows functions and a single instance
|
---|
520 | to be installed to handle requests. The default implementation
|
---|
521 | attempts to dispatch XML-RPC calls to the functions or instance
|
---|
522 | installed in the server. Override the _dispatch method inhereted
|
---|
523 | from SimpleXMLRPCDispatcher to change this behavior.
|
---|
524 | """
|
---|
525 |
|
---|
526 | allow_reuse_address = True
|
---|
527 |
|
---|
528 | # Warning: this is for debugging purposes only! Never set this to True in
|
---|
529 | # production code, as will be sending out sensitive information (exception
|
---|
530 | # and stack trace details) when exceptions are raised inside
|
---|
531 | # SimpleXMLRPCRequestHandler.do_POST
|
---|
532 | _send_traceback_header = False
|
---|
533 |
|
---|
534 | def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
|
---|
535 | logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
|
---|
536 | self.logRequests = logRequests
|
---|
537 |
|
---|
538 | SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
---|
539 | SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
|
---|
540 |
|
---|
541 | # [Bug #1222790] If possible, set close-on-exec flag; if a
|
---|
542 | # method spawns a subprocess, the subprocess shouldn't have
|
---|
543 | # the listening socket open.
|
---|
544 | if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
|
---|
545 | flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
|
---|
546 | flags |= fcntl.FD_CLOEXEC
|
---|
547 | fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
|
---|
548 |
|
---|
549 | class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
|
---|
550 | """Simple handler for XML-RPC data passed through CGI."""
|
---|
551 |
|
---|
552 | def __init__(self, allow_none=False, encoding=None):
|
---|
553 | SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
---|
554 |
|
---|
555 | def handle_xmlrpc(self, request_text):
|
---|
556 | """Handle a single XML-RPC request"""
|
---|
557 |
|
---|
558 | response = self._marshaled_dispatch(request_text)
|
---|
559 |
|
---|
560 | print 'Content-Type: text/xml'
|
---|
561 | print 'Content-Length: %d' % len(response)
|
---|
562 | print
|
---|
563 | sys.stdout.write(response)
|
---|
564 |
|
---|
565 | def handle_get(self):
|
---|
566 | """Handle a single HTTP GET request.
|
---|
567 |
|
---|
568 | Default implementation indicates an error because
|
---|
569 | XML-RPC uses the POST method.
|
---|
570 | """
|
---|
571 |
|
---|
572 | code = 400
|
---|
573 | message, explain = \
|
---|
574 | BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
|
---|
575 |
|
---|
576 | response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
|
---|
577 | {
|
---|
578 | 'code' : code,
|
---|
579 | 'message' : message,
|
---|
580 | 'explain' : explain
|
---|
581 | }
|
---|
582 | print 'Status: %d %s' % (code, message)
|
---|
583 | print 'Content-Type: text/html'
|
---|
584 | print 'Content-Length: %d' % len(response)
|
---|
585 | print
|
---|
586 | sys.stdout.write(response)
|
---|
587 |
|
---|
588 | def handle_request(self, request_text = None):
|
---|
589 | """Handle a single XML-RPC request passed through a CGI post method.
|
---|
590 |
|
---|
591 | If no XML data is given then it is read from stdin. The resulting
|
---|
592 | XML-RPC response is printed to stdout along with the correct HTTP
|
---|
593 | headers.
|
---|
594 | """
|
---|
595 |
|
---|
596 | if request_text is None and \
|
---|
597 | os.environ.get('REQUEST_METHOD', None) == 'GET':
|
---|
598 | self.handle_get()
|
---|
599 | else:
|
---|
600 | # POST data is normally available through stdin
|
---|
601 | try:
|
---|
602 | length = int(os.environ.get('CONTENT_LENGTH', None))
|
---|
603 | except (TypeError, ValueError):
|
---|
604 | length = -1
|
---|
605 | if request_text is None:
|
---|
606 | request_text = sys.stdin.read(length)
|
---|
607 |
|
---|
608 | self.handle_xmlrpc(request_text)
|
---|
609 |
|
---|
610 | if __name__ == '__main__':
|
---|
611 | print 'Running XML-RPC server on port 8000'
|
---|
612 | server = SimpleXMLRPCServer(("localhost", 8000))
|
---|
613 | server.register_function(pow)
|
---|
614 | server.register_function(lambda x,y: x+y, 'add')
|
---|
615 | server.serve_forever()
|
---|