diff --git a/Lib/SimpleXMLRPCServer.py b/Lib/SimpleXMLRPCServer.py index 7aac89a19bb..4cf0b18a0d4 100644 --- a/Lib/SimpleXMLRPCServer.py +++ b/Lib/SimpleXMLRPCServer.py @@ -161,8 +161,9 @@ class SimpleXMLRPCDispatcher: """Mix-in class that dispatches XML-RPC requests. This class is used to register XML-RPC method handlers - and then to dispatch them. There should never be any - reason to instantiate this class directly. + and then to dispatch them. This class doesn't need to be + instanced directly when used by SimpleXMLRPCServer but it + can be instanced when used by the MultiPathXMLRPCServer """ def __init__(self, allow_none=False, encoding=None): @@ -237,7 +238,7 @@ class SimpleXMLRPCDispatcher: self.funcs.update({'system.multicall' : self.system_multicall}) - def _marshaled_dispatch(self, data, dispatch_method = None): + def _marshaled_dispatch(self, data, dispatch_method = None, path = None): """Dispatches an XML-RPC method from marshalled (XML) data. XML-RPC methods are dispatched from the marshalled (XML) data @@ -499,7 +500,7 @@ class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): # check to see if a subclass implements _dispatch and dispatch # using that method if present. response = self.server._marshaled_dispatch( - data, getattr(self, '_dispatch', None) + data, getattr(self, '_dispatch', None), self.path ) except Exception, e: # This should only happen if the module is buggy # internal error, report as HTTP server error @@ -596,6 +597,44 @@ class SimpleXMLRPCServer(SocketServer.TCPServer, flags |= fcntl.FD_CLOEXEC fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) +class MultiPathXMLRPCServer(SimpleXMLRPCServer): + """Multipath XML-RPC Server + This specialization of SimpleXMLRPCServer allows the user to create + multiple Dispatcher instances and assign them to different + HTTP request paths. This makes it possible to run two or more + 'virtual XML-RPC servers' at the same port. + Make sure that the requestHandler accepts the paths in question. + """ + def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler, + logRequests=True, allow_none=False, encoding=None, bind_and_activate=True): + + SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none, + encoding, bind_and_activate) + self.dispatchers = {} + self.allow_none = allow_none + self.encoding = encoding + + def add_dispatcher(self, path, dispatcher): + self.dispatchers[path] = dispatcher + return dispatcher + + def get_dispatcher(self, path): + return self.dispatchers[path] + + def _marshaled_dispatch(self, data, dispatch_method = None, path = None): + try: + response = self.dispatchers[path]._marshaled_dispatch( + data, dispatch_method, path) + except: + # report low level exception back to server + # (each dispatcher should have handled their own + # exceptions) + exc_type, exc_value = sys.exc_info()[:2] + response = xmlrpclib.dumps( + xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)), + encoding=self.encoding, allow_none=self.allow_none) + return response + class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): """Simple handler for XML-RPC data passed through CGI.""" diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py index abd905a8adb..8f882c0bc8c 100644 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -329,6 +329,66 @@ def http_server(evt, numrequests, requestHandler=None): PORT = None evt.set() +def http_multi_server(evt, numrequests, requestHandler=None): + class TestInstanceClass: + def div(self, x, y): + return x // y + + def _methodHelp(self, name): + if name == 'div': + return 'This is the div function' + + def my_function(): + '''This is my function''' + return True + + class MyXMLRPCServer(SimpleXMLRPCServer.MultiPathXMLRPCServer): + def get_request(self): + # Ensure the socket is always non-blocking. On Linux, socket + # attributes are not inherited like they are on *BSD and Windows. + s, port = self.socket.accept() + s.setblocking(True) + return s, port + + if not requestHandler: + requestHandler = SimpleXMLRPCServer.SimpleXMLRPCRequestHandler + class MyRequestHandler(requestHandler): + rpc_paths = [] + + serv = MyXMLRPCServer(("localhost", 0), MyRequestHandler, + logRequests=False, bind_and_activate=False) + serv.socket.settimeout(3) + serv.server_bind() + try: + global ADDR, PORT, URL + ADDR, PORT = serv.socket.getsockname() + #connect to IP address directly. This avoids socket.create_connection() + #trying to connect to to "localhost" using all address families, which + #causes slowdown e.g. on vista which supports AF_INET6. The server listens + #on AF_INET only. + URL = "http://%s:%d"%(ADDR, PORT) + serv.server_activate() + paths = ["/foo", "/foo/bar"] + for path in paths: + d = serv.add_dispatcher(path, SimpleXMLRPCServer.SimpleXMLRPCDispatcher()) + d.register_introspection_functions() + d.register_multicall_functions() + serv.get_dispatcher(paths[0]).register_function(pow) + serv.get_dispatcher(paths[1]).register_function(lambda x,y: x+y, 'add') + evt.set() + + # handle up to 'numrequests' requests + while numrequests > 0: + serv.handle_request() + numrequests -= 1 + + except socket.timeout: + pass + finally: + serv.socket.close() + PORT = None + evt.set() + # This function prevents errors like: # def is_unavailable_exception(e): @@ -353,6 +413,7 @@ def is_unavailable_exception(e): class BaseServerTestCase(unittest.TestCase): requestHandler = None request_count = 1 + threadFunc = staticmethod(http_server) def setUp(self): # enable traceback reporting SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True @@ -360,7 +421,7 @@ class BaseServerTestCase(unittest.TestCase): self.evt = threading.Event() # start server thread to handle requests serv_args = (self.evt, self.request_count, self.requestHandler) - threading.Thread(target=http_server, args=serv_args).start() + threading.Thread(target=self.threadFunc, args=serv_args).start() # wait for the server to be ready self.evt.wait(10) @@ -517,6 +578,18 @@ class SimpleServerTestCase(BaseServerTestCase): # This avoids waiting for the socket timeout. self.test_simple1() +class MultiPathServerTestCase(BaseServerTestCase): + threadFunc = staticmethod(http_multi_server) + request_count = 2 + def test_path1(self): + p = xmlrpclib.ServerProxy(URL+"/foo") + self.assertEqual(p.pow(6,8), 6**8) + self.assertRaises(xmlrpclib.Fault, p.add, 6, 8) + def test_path2(self): + p = xmlrpclib.ServerProxy(URL+"/foo/bar") + self.assertEqual(p.add(6,8), 6+8) + self.assertRaises(xmlrpclib.Fault, p.pow, 6, 8) + #A test case that verifies that a server using the HTTP/1.1 keep-alive mechanism #does indeed serve subsequent requests on the same connection class BaseKeepaliveServerTestCase(BaseServerTestCase): @@ -923,6 +996,7 @@ def test_main(): xmlrpc_tests.append(GzipServerTestCase) except ImportError: pass #gzip not supported in this build + xmlrpc_tests.append(MultiPathServerTestCase) xmlrpc_tests.append(ServerProxyTestCase) xmlrpc_tests.append(FailingServerTestCase) xmlrpc_tests.append(CGIHandlerTestCase)