Index: Lib/DocXMLRPCServer.py =================================================================== --- Lib/DocXMLRPCServer.py (revision 72833) +++ Lib/DocXMLRPCServer.py (working copy) @@ -239,10 +239,7 @@ self.send_header("Content-length", str(len(response))) self.end_headers() self.wfile.write(response) - - # shut down the connection self.wfile.flush() - self.connection.shutdown(1) class DocXMLRPCServer( SimpleXMLRPCServer, XMLRPCDocGenerator): Index: Lib/SimpleXMLRPCServer.py =================================================================== --- Lib/SimpleXMLRPCServer.py (revision 72833) +++ Lib/SimpleXMLRPCServer.py (working copy) @@ -489,11 +489,8 @@ self.send_header("Content-length", str(len(response))) self.end_headers() self.wfile.write(response) - - # shut down the connection self.wfile.flush() - self.connection.shutdown(1) - + def report_404 (self): # Report a 404 error self.send_response(404) @@ -502,10 +499,8 @@ self.send_header("Content-length", str(len(response))) self.end_headers() self.wfile.write(response) - # shut down the connection self.wfile.flush() - self.connection.shutdown(1) - + def log_request(self, code='-', size='-'): """Selectively log an accepted request.""" Index: Lib/test/test_xmlrpc.py =================================================================== --- Lib/test/test_xmlrpc.py (revision 72833) +++ Lib/test/test_xmlrpc.py (working copy) @@ -273,7 +273,7 @@ # The evt is set twice. First when the server is ready to serve. # Second when the server has been shutdown. The user must clear # the event after it has been set the first time to catch the second set. -def http_server(evt, numrequests): +def http_server(evt, numrequests, requestHandler=None): class TestInstanceClass: def div(self, x, y): return x // y @@ -294,7 +294,9 @@ s.setblocking(True) return s, port - serv = MyXMLRPCServer(("localhost", 0), + if not requestHandler: + requestHandler = SimpleXMLRPCServer.SimpleXMLRPCRequestHandler + serv = MyXMLRPCServer(("localhost", 0), requestHandler, logRequests=False, bind_and_activate=False) try: serv.socket.settimeout(3) @@ -348,34 +350,36 @@ return False -# NOTE: The tests in SimpleServerTestCase will ignore failures caused by -# "temporarily unavailable" exceptions raised in SimpleXMLRPCServer. This -# condition occurs infrequently on some platforms, frequently on others, and -# is apparently caused by using SimpleXMLRPCServer with a non-blocking socket. -# If the server class is updated at some point in the future to handle this -# situation more gracefully, these tests should be modified appropriately. - -class SimpleServerTestCase(unittest.TestCase): +class BaseServerTestCase(unittest.TestCase): + requestHandler = None def setUp(self): # enable traceback reporting SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True self.evt = threading.Event() # start server thread to handle requests - serv_args = (self.evt, 1) + serv_args = (self.evt, 1, self.requestHandler) threading.Thread(target=http_server, args=serv_args).start() # wait for the server to be ready - self.evt.wait() + self.evt.wait(10) self.evt.clear() def tearDown(self): # wait on the server thread to terminate - self.evt.wait() + self.evt.wait(10) # disable traceback reporting SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = False +# NOTE: The tests in SimpleServerTestCase will ignore failures caused by +# "temporarily unavailable" exceptions raised in SimpleXMLRPCServer. This +# condition occurs infrequently on some platforms, frequently on others, and +# is apparently caused by using SimpleXMLRPCServer with a non-blocking socket. +# If the server class is updated at some point in the future to handle this +# situation more gracefully, these tests should be modified appropriately. + +class SimpleServerTestCase(BaseServerTestCase): def test_simple1(self): try: p = xmlrpclib.ServerProxy(URL) @@ -512,6 +516,37 @@ # This avoids waiting for the socket timeout. self.test_simple1() +#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 KeepaliveServerTestCase(BaseServerTestCase): + #a request handler that supports keep-alive and logs requests into a + #class variable + class RequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): + parentClass = SimpleXMLRPCServer.SimpleXMLRPCRequestHandler + protocol_version = 'HTTP/1.1' + myRequests = [] + def handle(self): + self.myRequests.append([]) + return self.parentClass.handle(self) + def handle_one_request(self): + result = self.parentClass.handle_one_request(self) + self.myRequests[-1].append(self.raw_requestline) + return result + + requestHandler = RequestHandler + def setUp(self): + #clear request log + self.RequestHandler.myRequests = [] + return BaseServerTestCase.setUp(self) + + def test_two(self): + p = xmlrpclib.ServerProxy(URL) + self.assertEqual(p.pow(6,8), 6**8) + self.assertEqual(p.pow(6,8), 6**8) + self.assertEqual(len(self.RequestHandler.myRequests), 1) + #we may or may not catch the final "append" with the empty line + self.failUnless(len(self.RequestHandler.myRequests[-1]) >= 2) + # This is a contrived way to make a failure occur on the server side # in order to test the _send_traceback_header flag on the server class FailingMessageClass(mimetools.Message): @@ -703,7 +738,7 @@ def make_connection(self, host): conn = xmlrpclib.Transport.make_connection(self, host) - conn._conn.sock = self.fake_socket = FakeSocket() + conn.sock = self.fake_socket = FakeSocket() return conn class TransportSubclassTestCase(unittest.TestCase): @@ -729,15 +764,15 @@ req = self.issue_request(TestTransport) self.assert_("X-Test: test_custom_user_agent\r\n" in req) - def test_send_host(self): + def test_send_extra_headers(self): class TestTransport(FakeTransport): - def send_host(self, conn, host): - xmlrpclib.Transport.send_host(self, conn, host) - conn.putheader("X-Test", "test_send_host") + def send_extra_headers(self, conn): + xmlrpclib.Transport.send_extra_headers(self, conn) + conn.putheader("X-Test", "test_extra_headers") req = self.issue_request(TestTransport) - self.assert_("X-Test: test_send_host\r\n" in req) + self.assert_("X-Test: test_extra_headers\r\n" in req) def test_send_request(self): class TestTransport(FakeTransport): @@ -763,6 +798,7 @@ xmlrpc_tests = [XMLRPCTestCase, HelperTestCase, DateTimeTestCase, BinaryTestCase, FaultTestCase, TransportSubclassTestCase] xmlrpc_tests.append(SimpleServerTestCase) + xmlrpc_tests.append(KeepaliveServerTestCase) xmlrpc_tests.append(FailingServerTestCase) xmlrpc_tests.append(CGIHandlerTestCase) Index: Lib/xmlrpclib.py =================================================================== --- Lib/xmlrpclib.py (revision 72833) +++ Lib/xmlrpclib.py (working copy) @@ -1212,7 +1212,8 @@ def __init__(self, use_datetime=0): self._use_datetime = use_datetime - + self._connection = None + self._extra_headers = [] ## # Send a complete request, and parse the response. # @@ -1230,22 +1231,22 @@ h.set_debuglevel(1) self.send_request(h, handler, request_body) - self.send_host(h, host) + self.send_extra_headers(h) self.send_user_agent(h) self.send_content(h, request_body) - errcode, errmsg, headers = h.getreply(buffering=True) + response = h.getresponse(buffering=True) - if errcode != 200: + if response.status != 200: raise ProtocolError( host + handler, - errcode, errmsg, - headers + response.status, response.reason, + response.msg, ) self.verbose = verbose - return self.parse_response(h.getfile()) + return self.parse_response(response) ## # Create parser. @@ -1294,10 +1295,13 @@ # @return A connection handle. def make_connection(self, host): + if self._connection: + return self._connection # create a HTTP connection object from a host descriptor import httplib - host, extra_headers, x509 = self.get_host_info(host) - return httplib.HTTP(host) + host, self._extra_headers, x509 = self.get_host_info(host) + self._connection = httplib.HTTPConnection(host) + return self._connection ## # Send request header. @@ -1310,14 +1314,12 @@ connection.putrequest("POST", handler) ## - # Send host name. + # Send extra headers. # # @param connection Connection handle. - # @param host Host name. - def send_host(self, connection, host): - host, extra_headers, x509 = self.get_host_info(host) - connection.putheader("Host", host) + def send_extra_headers(self, connection): + extra_headers = self._extra_headers if extra_headers: if isinstance(extra_headers, DictType): extra_headers = extra_headers.items() @@ -1349,20 +1351,19 @@ # @param file Stream. # @return Response tuple and target method. - def parse_response(self, file): - # read response from input file/socket, and parse it + def parse_response(self, response): + # read response data from httpresponse, and parse it p, u = self.getparser() while 1: - response = file.read(1024) - if not response: + data = response.read(1024) + if not data: break if self.verbose: - print "body:", repr(response) - p.feed(response) + print "body:", repr(data) + p.feed(data) - file.close() p.close() return u.close() @@ -1376,18 +1377,14 @@ # FIXME: mostly untested def make_connection(self, host): + if self._connection: + return self._connection # create a HTTPS connection object from a host descriptor # host may be a string, or a (host, x509-dict) tuple import httplib - host, extra_headers, x509 = self.get_host_info(host) - try: - HTTPS = httplib.HTTPS - except AttributeError: - raise NotImplementedError( - "your version of httplib doesn't support HTTPS" - ) - else: - return HTTPS(host, None, **(x509 or {})) + host, self._extra_headers, x509 = self.get_host_info(host) + self._connection = httplib.HTTPSConnection(host, None, **(x509 or {})) + return self._connection ## # Standard server proxy. This class establishes a virtual connection