PyXR

c:\python24\lib \ SimpleXMLRPCServer.py



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
SourceForge.net Logo