Issue 6654

Allow the XML-RPC server to use the HTTP request path when dispatching.  Added a MultiPathXMLRPCServer class that uses the feature, plus unit tests.
This commit is contained in:
Kristján Valur Jónsson 2009-08-27 23:13:18 +00:00
parent e2a77980b6
commit 429677ec38
2 changed files with 118 additions and 5 deletions

View File

@ -161,8 +161,9 @@ class SimpleXMLRPCDispatcher:
"""Mix-in class that dispatches XML-RPC requests. """Mix-in class that dispatches XML-RPC requests.
This class is used to register XML-RPC method handlers This class is used to register XML-RPC method handlers
and then to dispatch them. There should never be any and then to dispatch them. This class doesn't need to be
reason to instantiate this class directly. instanced directly when used by SimpleXMLRPCServer but it
can be instanced when used by the MultiPathXMLRPCServer
""" """
def __init__(self, allow_none=False, encoding=None): def __init__(self, allow_none=False, encoding=None):
@ -237,7 +238,7 @@ class SimpleXMLRPCDispatcher:
self.funcs.update({'system.multicall' : self.system_multicall}) 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. """Dispatches an XML-RPC method from marshalled (XML) data.
XML-RPC methods are dispatched from the 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 # check to see if a subclass implements _dispatch and dispatch
# using that method if present. # using that method if present.
response = self.server._marshaled_dispatch( 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 except Exception, e: # This should only happen if the module is buggy
# internal error, report as HTTP server error # internal error, report as HTTP server error
@ -596,6 +597,44 @@ class SimpleXMLRPCServer(SocketServer.TCPServer,
flags |= fcntl.FD_CLOEXEC flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) 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): class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
"""Simple handler for XML-RPC data passed through CGI.""" """Simple handler for XML-RPC data passed through CGI."""

View File

@ -329,6 +329,66 @@ def http_server(evt, numrequests, requestHandler=None):
PORT = None PORT = None
evt.set() 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: # This function prevents errors like:
# <ProtocolError for localhost:57527/RPC2: 500 Internal Server Error> # <ProtocolError for localhost:57527/RPC2: 500 Internal Server Error>
def is_unavailable_exception(e): def is_unavailable_exception(e):
@ -353,6 +413,7 @@ def is_unavailable_exception(e):
class BaseServerTestCase(unittest.TestCase): class BaseServerTestCase(unittest.TestCase):
requestHandler = None requestHandler = None
request_count = 1 request_count = 1
threadFunc = staticmethod(http_server)
def setUp(self): def setUp(self):
# enable traceback reporting # enable traceback reporting
SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True
@ -360,7 +421,7 @@ class BaseServerTestCase(unittest.TestCase):
self.evt = threading.Event() self.evt = threading.Event()
# start server thread to handle requests # start server thread to handle requests
serv_args = (self.evt, self.request_count, self.requestHandler) 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 # wait for the server to be ready
self.evt.wait(10) self.evt.wait(10)
@ -517,6 +578,18 @@ class SimpleServerTestCase(BaseServerTestCase):
# This avoids waiting for the socket timeout. # This avoids waiting for the socket timeout.
self.test_simple1() 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 #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 #does indeed serve subsequent requests on the same connection
class BaseKeepaliveServerTestCase(BaseServerTestCase): class BaseKeepaliveServerTestCase(BaseServerTestCase):
@ -923,6 +996,7 @@ def test_main():
xmlrpc_tests.append(GzipServerTestCase) xmlrpc_tests.append(GzipServerTestCase)
except ImportError: except ImportError:
pass #gzip not supported in this build pass #gzip not supported in this build
xmlrpc_tests.append(MultiPathServerTestCase)
xmlrpc_tests.append(ServerProxyTestCase) xmlrpc_tests.append(ServerProxyTestCase)
xmlrpc_tests.append(FailingServerTestCase) xmlrpc_tests.append(FailingServerTestCase)
xmlrpc_tests.append(CGIHandlerTestCase) xmlrpc_tests.append(CGIHandlerTestCase)