0001 """Simple XML-RPC Server. 0002 0003 This module can be used to create simple XML-RPC servers 0004 by creating a server and either installing functions, a 0005 class instance, or by extending the SimpleXMLRPCServer 0006 class. 0007 0008 It can also be used to handle XML-RPC requests in a CGI 0009 environment using CGIXMLRPCRequestHandler. 0010 0011 A list of possible usage patterns follows: 0012 0013 1. Install functions: 0014 0015 server = SimpleXMLRPCServer(("localhost", 8000)) 0016 server.register_function(pow) 0017 server.register_function(lambda x,y: x+y, 'add') 0018 server.serve_forever() 0019 0020 2. Install an instance: 0021 0022 class MyFuncs: 0023 def __init__(self): 0024 # make all of the string functions available through 0025 # string.func_name 0026 import string 0027 self.string = string 0028 def _listMethods(self): 0029 # implement this method so that system.listMethods 0030 # knows to advertise the strings methods 0031 return list_public_methods(self) + \ 0032 ['string.' + method for method in list_public_methods(self.string)] 0033 def pow(self, x, y): return pow(x, y) 0034 def add(self, x, y) : return x + y 0035 0036 server = SimpleXMLRPCServer(("localhost", 8000)) 0037 server.register_introspection_functions() 0038 server.register_instance(MyFuncs()) 0039 server.serve_forever() 0040 0041 3. Install an instance with custom dispatch method: 0042 0043 class Math: 0044 def _listMethods(self): 0045 # this method must be present for system.listMethods 0046 # to work 0047 return ['add', 'pow'] 0048 def _methodHelp(self, method): 0049 # this method must be present for system.methodHelp 0050 # to work 0051 if method == 'add': 0052 return "add(2,3) => 5" 0053 elif method == 'pow': 0054 return "pow(x, y[, z]) => number" 0055 else: 0056 # By convention, return empty 0057 # string if no help is available 0058 return "" 0059 def _dispatch(self, method, params): 0060 if method == 'pow': 0061 return pow(*params) 0062 elif method == 'add': 0063 return params[0] + params[1] 0064 else: 0065 raise 'bad method' 0066 0067 server = SimpleXMLRPCServer(("localhost", 8000)) 0068 server.register_introspection_functions() 0069 server.register_instance(Math()) 0070 server.serve_forever() 0071 0072 4. Subclass SimpleXMLRPCServer: 0073 0074 class MathServer(SimpleXMLRPCServer): 0075 def _dispatch(self, method, params): 0076 try: 0077 # We are forcing the 'export_' prefix on methods that are 0078 # callable through XML-RPC to prevent potential security 0079 # problems 0080 func = getattr(self, 'export_' + method) 0081 except AttributeError: 0082 raise Exception('method "%s" is not supported' % method) 0083 else: 0084 return func(*params) 0085 0086 def export_add(self, x, y): 0087 return x + y 0088 0089 server = MathServer(("localhost", 8000)) 0090 server.serve_forever() 0091 0092 5. CGI script: 0093 0094 server = CGIXMLRPCRequestHandler() 0095 server.register_function(pow) 0096 server.handle_request() 0097 """ 0098 0099 # Written by Brian Quinlan (brian@sweetapp.com). 0100 # Based on code written by Fredrik Lundh. 0101 0102 import xmlrpclib 0103 from xmlrpclib import Fault 0104 import SocketServer 0105 import BaseHTTPServer 0106 import sys 0107 import os 0108 0109 def resolve_dotted_attribute(obj, attr): 0110 """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d 0111 0112 Resolves a dotted attribute name to an object. Raises 0113 an AttributeError if any attribute in the chain starts with a '_'. 0114 """ 0115 0116 for i in attr.split('.'): 0117 if i.startswith('_'): 0118 raise AttributeError( 0119 'attempt to access private attribute "%s"' % i 0120 ) 0121 else: 0122 obj = getattr(obj,i) 0123 return obj 0124 0125 def list_public_methods(obj): 0126 """Returns a list of attribute strings, found in the specified 0127 object, which represent callable attributes""" 0128 0129 return [member for member in dir(obj) 0130 if not member.startswith('_') and 0131 callable(getattr(obj, member))] 0132 0133 def remove_duplicates(lst): 0134 """remove_duplicates([2,2,2,1,3,3]) => [3,1,2] 0135 0136 Returns a copy of a list without duplicates. Every list 0137 item must be hashable and the order of the items in the 0138 resulting list is not defined. 0139 """ 0140 u = {} 0141 for x in lst: 0142 u[x] = 1 0143 0144 return u.keys() 0145 0146 class SimpleXMLRPCDispatcher: 0147 """Mix-in class that dispatches XML-RPC requests. 0148 0149 This class is used to register XML-RPC method handlers 0150 and then to dispatch them. There should never be any 0151 reason to instantiate this class directly. 0152 """ 0153 0154 def __init__(self): 0155 self.funcs = {} 0156 self.instance = None 0157 0158 def register_instance(self, instance): 0159 """Registers an instance to respond to XML-RPC requests. 0160 0161 Only one instance can be installed at a time. 0162 0163 If the registered instance has a _dispatch method then that 0164 method will be called with the name of the XML-RPC method and 0165 it's parameters as a tuple 0166 e.g. instance._dispatch('add',(2,3)) 0167 0168 If the registered instance does not have a _dispatch method 0169 then the instance will be searched to find a matching method 0170 and, if found, will be called. Methods beginning with an '_' 0171 are considered private and will not be called by 0172 SimpleXMLRPCServer. 0173 0174 If a registered function matches a XML-RPC request, then it 0175 will be called instead of the registered instance. 0176 """ 0177 0178 self.instance = instance 0179 0180 def register_function(self, function, name = None): 0181 """Registers a function to respond to XML-RPC requests. 0182 0183 The optional name argument can be used to set a Unicode name 0184 for the function. 0185 """ 0186 0187 if name is None: 0188 name = function.__name__ 0189 self.funcs[name] = function 0190 0191 def register_introspection_functions(self): 0192 """Registers the XML-RPC introspection methods in the system 0193 namespace. 0194 0195 see http://xmlrpc.usefulinc.com/doc/reserved.html 0196 """ 0197 0198 self.funcs.update({'system.listMethods' : self.system_listMethods, 0199 'system.methodSignature' : self.system_methodSignature, 0200 'system.methodHelp' : self.system_methodHelp}) 0201 0202 def register_multicall_functions(self): 0203 """Registers the XML-RPC multicall method in the system 0204 namespace. 0205 0206 see http://www.xmlrpc.com/discuss/msgReader$1208""" 0207 0208 self.funcs.update({'system.multicall' : self.system_multicall}) 0209 0210 def _marshaled_dispatch(self, data, dispatch_method = None): 0211 """Dispatches an XML-RPC method from marshalled (XML) data. 0212 0213 XML-RPC methods are dispatched from the marshalled (XML) data 0214 using the _dispatch method and the result is returned as 0215 marshalled data. For backwards compatibility, a dispatch 0216 function can be provided as an argument (see comment in 0217 SimpleXMLRPCRequestHandler.do_POST) but overriding the 0218 existing method through subclassing is the prefered means 0219 of changing method dispatch behavior. 0220 """ 0221 0222 params, method = xmlrpclib.loads(data) 0223 0224 # generate response 0225 try: 0226 if dispatch_method is not None: 0227 response = dispatch_method(method, params) 0228 else: 0229 response = self._dispatch(method, params) 0230 # wrap response in a singleton tuple 0231 response = (response,) 0232 response = xmlrpclib.dumps(response, methodresponse=1) 0233 except Fault, fault: 0234 response = xmlrpclib.dumps(fault) 0235 except: 0236 # report exception back to server 0237 response = xmlrpclib.dumps( 0238 xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)) 0239 ) 0240 0241 return response 0242 0243 def system_listMethods(self): 0244 """system.listMethods() => ['add', 'subtract', 'multiple'] 0245 0246 Returns a list of the methods supported by the server.""" 0247 0248 methods = self.funcs.keys() 0249 if self.instance is not None: 0250 # Instance can implement _listMethod to return a list of 0251 # methods 0252 if hasattr(self.instance, '_listMethods'): 0253 methods = remove_duplicates( 0254 methods + self.instance._listMethods() 0255 ) 0256 # if the instance has a _dispatch method then we 0257 # don't have enough information to provide a list 0258 # of methods 0259 elif not hasattr(self.instance, '_dispatch'): 0260 methods = remove_duplicates( 0261 methods + list_public_methods(self.instance) 0262 ) 0263 methods.sort() 0264 return methods 0265 0266 def system_methodSignature(self, method_name): 0267 """system.methodSignature('add') => [double, int, int] 0268 0269 Returns a list describing the signature of the method. In the 0270 above example, the add method takes two integers as arguments 0271 and returns a double result. 0272 0273 This server does NOT support system.methodSignature.""" 0274 0275 # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html 0276 0277 return 'signatures not supported' 0278 0279 def system_methodHelp(self, method_name): 0280 """system.methodHelp('add') => "Adds two integers together" 0281 0282 Returns a string containing documentation for the specified method.""" 0283 0284 method = None 0285 if self.funcs.has_key(method_name): 0286 method = self.funcs[method_name] 0287 elif self.instance is not None: 0288 # Instance can implement _methodHelp to return help for a method 0289 if hasattr(self.instance, '_methodHelp'): 0290 return self.instance._methodHelp(method_name) 0291 # if the instance has a _dispatch method then we 0292 # don't have enough information to provide help 0293 elif not hasattr(self.instance, '_dispatch'): 0294 try: 0295 method = resolve_dotted_attribute( 0296 self.instance, 0297 method_name 0298 ) 0299 except AttributeError: 0300 pass 0301 0302 # Note that we aren't checking that the method actually 0303 # be a callable object of some kind 0304 if method is None: 0305 return "" 0306 else: 0307 import pydoc 0308 return pydoc.getdoc(method) 0309 0310 def system_multicall(self, call_list): 0311 """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \ 0312 [[4], ...] 0313 0314 Allows the caller to package multiple XML-RPC calls into a single 0315 request. 0316 0317 See http://www.xmlrpc.com/discuss/msgReader$1208 0318 """ 0319 0320 results = [] 0321 for call in call_list: 0322 method_name = call['methodName'] 0323 params = call['params'] 0324 0325 try: 0326 # XXX A marshalling error in any response will fail the entire 0327 # multicall. If someone cares they should fix this. 0328 results.append([self._dispatch(method_name, params)]) 0329 except Fault, fault: 0330 results.append( 0331 {'faultCode' : fault.faultCode, 0332 'faultString' : fault.faultString} 0333 ) 0334 except: 0335 results.append( 0336 {'faultCode' : 1, 0337 'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)} 0338 ) 0339 return results 0340 0341 def _dispatch(self, method, params): 0342 """Dispatches the XML-RPC method. 0343 0344 XML-RPC calls are forwarded to a registered function that 0345 matches the called XML-RPC method name. If no such function 0346 exists then the call is forwarded to the registered instance, 0347 if available. 0348 0349 If the registered instance has a _dispatch method then that 0350 method will be called with the name of the XML-RPC method and 0351 it's parameters as a tuple 0352 e.g. instance._dispatch('add',(2,3)) 0353 0354 If the registered instance does not have a _dispatch method 0355 then the instance will be searched to find a matching method 0356 and, if found, will be called. 0357 0358 Methods beginning with an '_' are considered private and will 0359 not be called. 0360 """ 0361 0362 func = None 0363 try: 0364 # check to see if a matching function has been registered 0365 func = self.funcs[method] 0366 except KeyError: 0367 if self.instance is not None: 0368 # check for a _dispatch method 0369 if hasattr(self.instance, '_dispatch'): 0370 return self.instance._dispatch(method, params) 0371 else: 0372 # call instance method directly 0373 try: 0374 func = resolve_dotted_attribute( 0375 self.instance, 0376 method 0377 ) 0378 except AttributeError: 0379 pass 0380 0381 if func is not None: 0382 return func(*params) 0383 else: 0384 raise Exception('method "%s" is not supported' % method) 0385 0386 class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 0387 """Simple XML-RPC request handler class. 0388 0389 Handles all HTTP POST requests and attempts to decode them as 0390 XML-RPC requests. 0391 """ 0392 0393 def do_POST(self): 0394 """Handles the HTTP POST request. 0395 0396 Attempts to interpret all HTTP POST requests as XML-RPC calls, 0397 which are forwarded to the server's _dispatch method for handling. 0398 """ 0399 0400 try: 0401 # get arguments 0402 data = self.rfile.read(int(self.headers["content-length"])) 0403 # In previous versions of SimpleXMLRPCServer, _dispatch 0404 # could be overridden in this class, instead of in 0405 # SimpleXMLRPCDispatcher. To maintain backwards compatibility, 0406 # check to see if a subclass implements _dispatch and dispatch 0407 # using that method if present. 0408 response = self.server._marshaled_dispatch( 0409 data, getattr(self, '_dispatch', None) 0410 ) 0411 except: # This should only happen if the module is buggy 0412 # internal error, report as HTTP server error 0413 self.send_response(500) 0414 self.end_headers() 0415 else: 0416 # got a valid XML RPC response 0417 self.send_response(200) 0418 self.send_header("Content-type", "text/xml") 0419 self.send_header("Content-length", str(len(response))) 0420 self.end_headers() 0421 self.wfile.write(response) 0422 0423 # shut down the connection 0424 self.wfile.flush() 0425 self.connection.shutdown(1) 0426 0427 def log_request(self, code='-', size='-'): 0428 """Selectively log an accepted request.""" 0429 0430 if self.server.logRequests: 0431 BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size) 0432 0433 class SimpleXMLRPCServer(SocketServer.TCPServer, 0434 SimpleXMLRPCDispatcher): 0435 """Simple XML-RPC server. 0436 0437 Simple XML-RPC server that allows functions and a single instance 0438 to be installed to handle requests. The default implementation 0439 attempts to dispatch XML-RPC calls to the functions or instance 0440 installed in the server. Override the _dispatch method inhereted 0441 from SimpleXMLRPCDispatcher to change this behavior. 0442 """ 0443 0444 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler, 0445 logRequests=1): 0446 self.logRequests = logRequests 0447 0448 SimpleXMLRPCDispatcher.__init__(self) 0449 SocketServer.TCPServer.__init__(self, addr, requestHandler) 0450 0451 class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): 0452 """Simple handler for XML-RPC data passed through CGI.""" 0453 0454 def __init__(self): 0455 SimpleXMLRPCDispatcher.__init__(self) 0456 0457 def handle_xmlrpc(self, request_text): 0458 """Handle a single XML-RPC request""" 0459 0460 response = self._marshaled_dispatch(request_text) 0461 0462 print 'Content-Type: text/xml' 0463 print 'Content-Length: %d' % len(response) 0464 print 0465 sys.stdout.write(response) 0466 0467 def handle_get(self): 0468 """Handle a single HTTP GET request. 0469 0470 Default implementation indicates an error because 0471 XML-RPC uses the POST method. 0472 """ 0473 0474 code = 400 0475 message, explain = \ 0476 BaseHTTPServer.BaseHTTPRequestHandler.responses[code] 0477 0478 response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \ 0479 { 0480 'code' : code, 0481 'message' : message, 0482 'explain' : explain 0483 } 0484 print 'Status: %d %s' % (code, message) 0485 print 'Content-Type: text/html' 0486 print 'Content-Length: %d' % len(response) 0487 print 0488 sys.stdout.write(response) 0489 0490 def handle_request(self, request_text = None): 0491 """Handle a single XML-RPC request passed through a CGI post method. 0492 0493 If no XML data is given then it is read from stdin. The resulting 0494 XML-RPC response is printed to stdout along with the correct HTTP 0495 headers. 0496 """ 0497 0498 if request_text is None and \ 0499 os.environ.get('REQUEST_METHOD', None) == 'GET': 0500 self.handle_get() 0501 else: 0502 # POST data is normally available through stdin 0503 if request_text is None: 0504 request_text = sys.stdin.read() 0505 0506 self.handle_xmlrpc(request_text) 0507 0508 if __name__ == '__main__': 0509 server = SimpleXMLRPCServer(("localhost", 8000)) 0510 server.register_function(pow) 0511 server.register_function(lambda x,y: x+y, 'add') 0512 server.serve_forever() 0513
Generated by PyXR 0.9.4