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.
|
||||
|
||||
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."""
|
||||
|
||||
|
|
|
@ -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:
|
||||
# <ProtocolError for localhost:57527/RPC2: 500 Internal Server Error>
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue