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:
parent
e2a77980b6
commit
429677ec38
|
@ -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."""
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue