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