Home | History | Annotate | Download | only in Lib
      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 (at] 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 import re
    110 try:
    111     import fcntl
    112 except ImportError:
    113     fcntl = None
    114 
    115 def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
    116     """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
    117 
    118     Resolves a dotted attribute name to an object.  Raises
    119     an AttributeError if any attribute in the chain starts with a '_'.
    120 
    121     If the optional allow_dotted_names argument is false, dots are not
    122     supported and this function operates similar to getattr(obj, attr).
    123     """
    124 
    125     if allow_dotted_names:
    126         attrs = attr.split('.')
    127     else:
    128         attrs = [attr]
    129 
    130     for i in attrs:
    131         if i.startswith('_'):
    132             raise AttributeError(
    133                 'attempt to access private attribute "%s"' % i
    134                 )
    135         else:
    136             obj = getattr(obj,i)
    137     return obj
    138 
    139 def list_public_methods(obj):
    140     """Returns a list of attribute strings, found in the specified
    141     object, which represent callable attributes"""
    142 
    143     return [member for member in dir(obj)
    144                 if not member.startswith('_') and
    145                     hasattr(getattr(obj, member), '__call__')]
    146 
    147 def remove_duplicates(lst):
    148     """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
    149 
    150     Returns a copy of a list without duplicates. Every list
    151     item must be hashable and the order of the items in the
    152     resulting list is not defined.
    153     """
    154     u = {}
    155     for x in lst:
    156         u[x] = 1
    157 
    158     return u.keys()
    159 
    160 class SimpleXMLRPCDispatcher:
    161     """Mix-in class that dispatches XML-RPC requests.
    162 
    163     This class is used to register XML-RPC method handlers
    164     and then to dispatch them. This class doesn't need to be
    165     instanced directly when used by SimpleXMLRPCServer but it
    166     can be instanced when used by the MultiPathXMLRPCServer.
    167     """
    168 
    169     def __init__(self, allow_none=False, encoding=None):
    170         self.funcs = {}
    171         self.instance = None
    172         self.allow_none = allow_none
    173         self.encoding = encoding
    174 
    175     def register_instance(self, instance, allow_dotted_names=False):
    176         """Registers an instance to respond to XML-RPC requests.
    177 
    178         Only one instance can be installed at a time.
    179 
    180         If the registered instance has a _dispatch method then that
    181         method will be called with the name of the XML-RPC method and
    182         its parameters as a tuple
    183         e.g. instance._dispatch('add',(2,3))
    184 
    185         If the registered instance does not have a _dispatch method
    186         then the instance will be searched to find a matching method
    187         and, if found, will be called. Methods beginning with an '_'
    188         are considered private and will not be called by
    189         SimpleXMLRPCServer.
    190 
    191         If a registered function matches a XML-RPC request, then it
    192         will be called instead of the registered instance.
    193 
    194         If the optional allow_dotted_names argument is true and the
    195         instance does not have a _dispatch method, method names
    196         containing dots are supported and resolved, as long as none of
    197         the name segments start with an '_'.
    198 
    199             *** SECURITY WARNING: ***
    200 
    201             Enabling the allow_dotted_names options allows intruders
    202             to access your module's global variables and may allow
    203             intruders to execute arbitrary code on your machine.  Only
    204             use this option on a secure, closed network.
    205 
    206         """
    207 
    208         self.instance = instance
    209         self.allow_dotted_names = allow_dotted_names
    210 
    211     def register_function(self, function, name = None):
    212         """Registers a function to respond to XML-RPC requests.
    213 
    214         The optional name argument can be used to set a Unicode name
    215         for the function.
    216         """
    217 
    218         if name is None:
    219             name = function.__name__
    220         self.funcs[name] = function
    221 
    222     def register_introspection_functions(self):
    223         """Registers the XML-RPC introspection methods in the system
    224         namespace.
    225 
    226         see http://xmlrpc.usefulinc.com/doc/reserved.html
    227         """
    228 
    229         self.funcs.update({'system.listMethods' : self.system_listMethods,
    230                       'system.methodSignature' : self.system_methodSignature,
    231                       'system.methodHelp' : self.system_methodHelp})
    232 
    233     def register_multicall_functions(self):
    234         """Registers the XML-RPC multicall method in the system
    235         namespace.
    236 
    237         see http://www.xmlrpc.com/discuss/msgReader$1208"""
    238 
    239         self.funcs.update({'system.multicall' : self.system_multicall})
    240 
    241     def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
    242         """Dispatches an XML-RPC method from marshalled (XML) data.
    243 
    244         XML-RPC methods are dispatched from the marshalled (XML) data
    245         using the _dispatch method and the result is returned as
    246         marshalled data. For backwards compatibility, a dispatch
    247         function can be provided as an argument (see comment in
    248         SimpleXMLRPCRequestHandler.do_POST) but overriding the
    249         existing method through subclassing is the preferred means
    250         of changing method dispatch behavior.
    251         """
    252 
    253         try:
    254             params, method = xmlrpclib.loads(data)
    255 
    256             # generate response

    257             if dispatch_method is not None:
    258                 response = dispatch_method(method, params)
    259             else:
    260                 response = self._dispatch(method, params)
    261             # wrap response in a singleton tuple

    262             response = (response,)
    263             response = xmlrpclib.dumps(response, methodresponse=1,
    264                                        allow_none=self.allow_none, encoding=self.encoding)
    265         except Fault, fault:
    266             response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
    267                                        encoding=self.encoding)
    268         except:
    269             # report exception back to server

    270             exc_type, exc_value, exc_tb = sys.exc_info()
    271             response = xmlrpclib.dumps(
    272                 xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),
    273                 encoding=self.encoding, allow_none=self.allow_none,
    274                 )
    275 
    276         return response
    277 
    278     def system_listMethods(self):
    279         """system.listMethods() => ['add', 'subtract', 'multiple']
    280 
    281         Returns a list of the methods supported by the server."""
    282 
    283         methods = self.funcs.keys()
    284         if self.instance is not None:
    285             # Instance can implement _listMethod to return a list of

    286             # methods

    287             if hasattr(self.instance, '_listMethods'):
    288                 methods = remove_duplicates(
    289                         methods + self.instance._listMethods()
    290                     )
    291             # if the instance has a _dispatch method then we

    292             # don't have enough information to provide a list

    293             # of methods

    294             elif not hasattr(self.instance, '_dispatch'):
    295                 methods = remove_duplicates(
    296                         methods + list_public_methods(self.instance)
    297                     )
    298         methods.sort()
    299         return methods
    300 
    301     def system_methodSignature(self, method_name):
    302         """system.methodSignature('add') => [double, int, int]
    303 
    304         Returns a list describing the signature of the method. In the
    305         above example, the add method takes two integers as arguments
    306         and returns a double result.
    307 
    308         This server does NOT support system.methodSignature."""
    309 
    310         # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html

    311 
    312         return 'signatures not supported'
    313 
    314     def system_methodHelp(self, method_name):
    315         """system.methodHelp('add') => "Adds two integers together"
    316 
    317         Returns a string containing documentation for the specified method."""
    318 
    319         method = None
    320         if method_name in self.funcs:
    321             method = self.funcs[method_name]
    322         elif self.instance is not None:
    323             # Instance can implement _methodHelp to return help for a method

    324             if hasattr(self.instance, '_methodHelp'):
    325                 return self.instance._methodHelp(method_name)
    326             # if the instance has a _dispatch method then we

    327             # don't have enough information to provide help

    328             elif not hasattr(self.instance, '_dispatch'):
    329                 try:
    330                     method = resolve_dotted_attribute(
    331                                 self.instance,
    332                                 method_name,
    333                                 self.allow_dotted_names
    334                                 )
    335                 except AttributeError:
    336                     pass
    337 
    338         # Note that we aren't checking that the method actually

    339         # be a callable object of some kind

    340         if method is None:
    341             return ""
    342         else:
    343             import pydoc
    344             return pydoc.getdoc(method)
    345 
    346     def system_multicall(self, call_list):
    347         """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
    348 [[4], ...]
    349 
    350         Allows the caller to package multiple XML-RPC calls into a single
    351         request.
    352 
    353         See http://www.xmlrpc.com/discuss/msgReader$1208
    354         """
    355 
    356         results = []
    357         for call in call_list:
    358             method_name = call['methodName']
    359             params = call['params']
    360 
    361             try:
    362                 # XXX A marshalling error in any response will fail the entire

    363                 # multicall. If someone cares they should fix this.

    364                 results.append([self._dispatch(method_name, params)])
    365             except Fault, fault:
    366                 results.append(
    367                     {'faultCode' : fault.faultCode,
    368                      'faultString' : fault.faultString}
    369                     )
    370             except:
    371                 exc_type, exc_value, exc_tb = sys.exc_info()
    372                 results.append(
    373                     {'faultCode' : 1,
    374                      'faultString' : "%s:%s" % (exc_type, exc_value)}
    375                     )
    376         return results
    377 
    378     def _dispatch(self, method, params):
    379         """Dispatches the XML-RPC method.
    380 
    381         XML-RPC calls are forwarded to a registered function that
    382         matches the called XML-RPC method name. If no such function
    383         exists then the call is forwarded to the registered instance,
    384         if available.
    385 
    386         If the registered instance has a _dispatch method then that
    387         method will be called with the name of the XML-RPC method and
    388         its parameters as a tuple
    389         e.g. instance._dispatch('add',(2,3))
    390 
    391         If the registered instance does not have a _dispatch method
    392         then the instance will be searched to find a matching method
    393         and, if found, will be called.
    394 
    395         Methods beginning with an '_' are considered private and will
    396         not be called.
    397         """
    398 
    399         func = None
    400         try:
    401             # check to see if a matching function has been registered

    402             func = self.funcs[method]
    403         except KeyError:
    404             if self.instance is not None:
    405                 # check for a _dispatch method

    406                 if hasattr(self.instance, '_dispatch'):
    407                     return self.instance._dispatch(method, params)
    408                 else:
    409                     # call instance method directly

    410                     try:
    411                         func = resolve_dotted_attribute(
    412                             self.instance,
    413                             method,
    414                             self.allow_dotted_names
    415                             )
    416                     except AttributeError:
    417                         pass
    418 
    419         if func is not None:
    420             return func(*params)
    421         else:
    422             raise Exception('method "%s" is not supported' % method)
    423 
    424 class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    425     """Simple XML-RPC request handler class.
    426 
    427     Handles all HTTP POST requests and attempts to decode them as
    428     XML-RPC requests.
    429     """
    430 
    431     # Class attribute listing the accessible path components;

    432     # paths not on this list will result in a 404 error.

    433     rpc_paths = ('/', '/RPC2')
    434 
    435     #if not None, encode responses larger than this, if possible

    436     encode_threshold = 1400 #a common MTU

    437 
    438     #Override form StreamRequestHandler: full buffering of output

    439     #and no Nagle.

    440     wbufsize = -1
    441     disable_nagle_algorithm = True
    442 
    443     # a re to match a gzip Accept-Encoding

    444     aepattern = re.compile(r"""
    445                             \s* ([^\s;]+) \s*            #content-coding
    446                             (;\s* q \s*=\s* ([0-9\.]+))? #q
    447                             """, re.VERBOSE | re.IGNORECASE)
    448 
    449     def accept_encodings(self):
    450         r = {}
    451         ae = self.headers.get("Accept-Encoding", "")
    452         for e in ae.split(","):
    453             match = self.aepattern.match(e)
    454             if match:
    455                 v = match.group(3)
    456                 v = float(v) if v else 1.0
    457                 r[match.group(1)] = v
    458         return r
    459 
    460     def is_rpc_path_valid(self):
    461         if self.rpc_paths:
    462             return self.path in self.rpc_paths
    463         else:
    464             # If .rpc_paths is empty, just assume all paths are legal

    465             return True
    466 
    467     def do_POST(self):
    468         """Handles the HTTP POST request.
    469 
    470         Attempts to interpret all HTTP POST requests as XML-RPC calls,
    471         which are forwarded to the server's _dispatch method for handling.
    472         """
    473 
    474         # Check that the path is legal

    475         if not self.is_rpc_path_valid():
    476             self.report_404()
    477             return
    478 
    479         try:
    480             # Get arguments by reading body of request.

    481             # We read this in chunks to avoid straining

    482             # socket.read(); around the 10 or 15Mb mark, some platforms

    483             # begin to have problems (bug #792570).

    484             max_chunk_size = 10*1024*1024
    485             size_remaining = int(self.headers["content-length"])
    486             L = []
    487             while size_remaining:
    488                 chunk_size = min(size_remaining, max_chunk_size)
    489                 L.append(self.rfile.read(chunk_size))
    490                 size_remaining -= len(L[-1])
    491             data = ''.join(L)
    492 
    493             data = self.decode_request_content(data)
    494             if data is None:
    495                 return #response has been sent

    496 
    497             # In previous versions of SimpleXMLRPCServer, _dispatch

    498             # could be overridden in this class, instead of in

    499             # SimpleXMLRPCDispatcher. To maintain backwards compatibility,

    500             # check to see if a subclass implements _dispatch and dispatch

    501             # using that method if present.

    502             response = self.server._marshaled_dispatch(
    503                     data, getattr(self, '_dispatch', None), self.path
    504                 )
    505         except Exception, e: # This should only happen if the module is buggy

    506             # internal error, report as HTTP server error

    507             self.send_response(500)
    508 
    509             # Send information about the exception if requested

    510             if hasattr(self.server, '_send_traceback_header') and \
    511                     self.server._send_traceback_header:
    512                 self.send_header("X-exception", str(e))
    513                 self.send_header("X-traceback", traceback.format_exc())
    514 
    515             self.send_header("Content-length", "0")
    516             self.end_headers()
    517         else:
    518             # got a valid XML RPC response

    519             self.send_response(200)
    520             self.send_header("Content-type", "text/xml")
    521             if self.encode_threshold is not None:
    522                 if len(response) > self.encode_threshold:
    523                     q = self.accept_encodings().get("gzip", 0)
    524                     if q:
    525                         try:
    526                             response = xmlrpclib.gzip_encode(response)
    527                             self.send_header("Content-Encoding", "gzip")
    528                         except NotImplementedError:
    529                             pass
    530             self.send_header("Content-length", str(len(response)))
    531             self.end_headers()
    532             self.wfile.write(response)
    533 
    534     def decode_request_content(self, data):
    535         #support gzip encoding of request

    536         encoding = self.headers.get("content-encoding", "identity").lower()
    537         if encoding == "identity":
    538             return data
    539         if encoding == "gzip":
    540             try:
    541                 return xmlrpclib.gzip_decode(data)
    542             except NotImplementedError:
    543                 self.send_response(501, "encoding %r not supported" % encoding)
    544             except ValueError:
    545                 self.send_response(400, "error decoding gzip content")
    546         else:
    547             self.send_response(501, "encoding %r not supported" % encoding)
    548         self.send_header("Content-length", "0")
    549         self.end_headers()
    550 
    551     def report_404 (self):
    552             # Report a 404 error

    553         self.send_response(404)
    554         response = 'No such page'
    555         self.send_header("Content-type", "text/plain")
    556         self.send_header("Content-length", str(len(response)))
    557         self.end_headers()
    558         self.wfile.write(response)
    559 
    560     def log_request(self, code='-', size='-'):
    561         """Selectively log an accepted request."""
    562 
    563         if self.server.logRequests:
    564             BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
    565 
    566 class SimpleXMLRPCServer(SocketServer.TCPServer,
    567                          SimpleXMLRPCDispatcher):
    568     """Simple XML-RPC server.
    569 
    570     Simple XML-RPC server that allows functions and a single instance
    571     to be installed to handle requests. The default implementation
    572     attempts to dispatch XML-RPC calls to the functions or instance
    573     installed in the server. Override the _dispatch method inhereted
    574     from SimpleXMLRPCDispatcher to change this behavior.
    575     """
    576 
    577     allow_reuse_address = True
    578 
    579     # Warning: this is for debugging purposes only! Never set this to True in

    580     # production code, as will be sending out sensitive information (exception

    581     # and stack trace details) when exceptions are raised inside

    582     # SimpleXMLRPCRequestHandler.do_POST

    583     _send_traceback_header = False
    584 
    585     def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
    586                  logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
    587         self.logRequests = logRequests
    588 
    589         SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
    590         SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
    591 
    592         # [Bug #1222790] If possible, set close-on-exec flag; if a

    593         # method spawns a subprocess, the subprocess shouldn't have

    594         # the listening socket open.

    595         if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
    596             flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
    597             flags |= fcntl.FD_CLOEXEC
    598             fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
    599 
    600 class MultiPathXMLRPCServer(SimpleXMLRPCServer):
    601     """Multipath XML-RPC Server
    602     This specialization of SimpleXMLRPCServer allows the user to create
    603     multiple Dispatcher instances and assign them to different
    604     HTTP request paths.  This makes it possible to run two or more
    605     'virtual XML-RPC servers' at the same port.
    606     Make sure that the requestHandler accepts the paths in question.
    607     """
    608     def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
    609                  logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
    610 
    611         SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none,
    612                                     encoding, bind_and_activate)
    613         self.dispatchers = {}
    614         self.allow_none = allow_none
    615         self.encoding = encoding
    616 
    617     def add_dispatcher(self, path, dispatcher):
    618         self.dispatchers[path] = dispatcher
    619         return dispatcher
    620 
    621     def get_dispatcher(self, path):
    622         return self.dispatchers[path]
    623 
    624     def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
    625         try:
    626             response = self.dispatchers[path]._marshaled_dispatch(
    627                data, dispatch_method, path)
    628         except:
    629             # report low level exception back to server

    630             # (each dispatcher should have handled their own

    631             # exceptions)

    632             exc_type, exc_value = sys.exc_info()[:2]
    633             response = xmlrpclib.dumps(
    634                 xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),
    635                 encoding=self.encoding, allow_none=self.allow_none)
    636         return response
    637 
    638 class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
    639     """Simple handler for XML-RPC data passed through CGI."""
    640 
    641     def __init__(self, allow_none=False, encoding=None):
    642         SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
    643 
    644     def handle_xmlrpc(self, request_text):
    645         """Handle a single XML-RPC request"""
    646 
    647         response = self._marshaled_dispatch(request_text)
    648 
    649         print 'Content-Type: text/xml'
    650         print 'Content-Length: %d' % len(response)
    651         print
    652         sys.stdout.write(response)
    653 
    654     def handle_get(self):
    655         """Handle a single HTTP GET request.
    656 
    657         Default implementation indicates an error because
    658         XML-RPC uses the POST method.
    659         """
    660 
    661         code = 400
    662         message, explain = \
    663                  BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
    664 
    665         response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
    666             {
    667              'code' : code,
    668              'message' : message,
    669              'explain' : explain
    670             }
    671         print 'Status: %d %s' % (code, message)
    672         print 'Content-Type: %s' % BaseHTTPServer.DEFAULT_ERROR_CONTENT_TYPE
    673         print 'Content-Length: %d' % len(response)
    674         print
    675         sys.stdout.write(response)
    676 
    677     def handle_request(self, request_text = None):
    678         """Handle a single XML-RPC request passed through a CGI post method.
    679 
    680         If no XML data is given then it is read from stdin. The resulting
    681         XML-RPC response is printed to stdout along with the correct HTTP
    682         headers.
    683         """
    684 
    685         if request_text is None and \
    686             os.environ.get('REQUEST_METHOD', None) == 'GET':
    687             self.handle_get()
    688         else:
    689             # POST data is normally available through stdin

    690             try:
    691                 length = int(os.environ.get('CONTENT_LENGTH', None))
    692             except (TypeError, ValueError):
    693                 length = -1
    694             if request_text is None:
    695                 request_text = sys.stdin.read(length)
    696 
    697             self.handle_xmlrpc(request_text)
    698 
    699 if __name__ == '__main__':
    700     print 'Running XML-RPC server on port 8000'
    701     server = SimpleXMLRPCServer(("localhost", 8000))
    702     server.register_function(pow)
    703     server.register_function(lambda x,y: x+y, 'add')
    704     server.serve_forever()
    705