summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorKristján Valur Jónsson <kristjan@ccpgames.com>2009-08-27 23:13:18 (GMT)
committerKristján Valur Jónsson <kristjan@ccpgames.com>2009-08-27 23:13:18 (GMT)
commit429677ec38586d61cee479da9addf31ed1b2b1c0 (patch)
treeb8a8b50fba06d36fc9bb6e8c3611f98c446aa84b /Lib
parente2a77980b630dfcfdd2c934534c4a8847b93b927 (diff)
downloadcpython-429677ec38586d61cee479da9addf31ed1b2b1c0.zip
cpython-429677ec38586d61cee479da9addf31ed1b2b1c0.tar.gz
cpython-429677ec38586d61cee479da9addf31ed1b2b1c0.tar.bz2
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.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/SimpleXMLRPCServer.py47
-rw-r--r--Lib/test/test_xmlrpc.py76
2 files changed, 118 insertions, 5 deletions
diff --git a/Lib/SimpleXMLRPCServer.py b/Lib/SimpleXMLRPCServer.py
index 7aac89a..4cf0b18 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 abd905a..8f882c0 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:
# <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)