diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py index d142a15b6f9..831a5a5aaed 100644 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -7,6 +7,7 @@ from unittest import mock import xmlrpc.client as xmlrpclib import xmlrpc.server import http.client +import http, http.server import socket import os import re @@ -244,6 +245,42 @@ class XMLRPCTestCase(unittest.TestCase): except OSError: self.assertTrue(has_ssl) + @unittest.skipUnless(threading, "Threading required for this test.") + def test_keepalive_disconnect(self): + class RequestHandler(http.server.BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + handled = False + + def do_POST(self): + length = int(self.headers.get("Content-Length")) + self.rfile.read(length) + if self.handled: + self.close_connection = True + return + response = xmlrpclib.dumps((5,), methodresponse=True) + response = response.encode() + self.send_response(http.HTTPStatus.OK) + self.send_header("Content-Length", len(response)) + self.end_headers() + self.wfile.write(response) + self.handled = True + self.close_connection = False + + def run_server(): + server.socket.settimeout(float(1)) # Don't hang if client fails + server.handle_request() # First request and attempt at second + server.handle_request() # Retried second request + + server = http.server.HTTPServer((support.HOST, 0), RequestHandler) + self.addCleanup(server.server_close) + thread = threading.Thread(target=run_server) + thread.start() + self.addCleanup(thread.join) + url = "http://{}:{}/".format(*server.server_address) + with xmlrpclib.ServerProxy(url) as p: + self.assertEqual(p.method(), 5) + self.assertEqual(p.method(), 5) + class HelperTestCase(unittest.TestCase): def test_escape(self): self.assertEqual(xmlrpclib.escape("a&b"), "a&b") diff --git a/Lib/xmlrpc/client.py b/Lib/xmlrpc/client.py index bf428358439..e7daa0793b7 100644 --- a/Lib/xmlrpc/client.py +++ b/Lib/xmlrpc/client.py @@ -1129,13 +1129,13 @@ class Transport: for i in (0, 1): try: return self.single_request(host, handler, request_body, verbose) + except http.client.RemoteDisconnected: + if i: + raise except OSError as e: if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE): raise - except http.client.RemoteDisconnected: - if i: - raise def single_request(self, host, handler, request_body, verbose=False): # issue XML-RPC request diff --git a/Misc/NEWS b/Misc/NEWS index 56f771fc763..029b334e568 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -79,6 +79,10 @@ Core and Builtins Library ------- +- Issue #26402: Fix XML-RPC client to retry when the server shuts down a + persistent connection. This was a regression related to the new + http.client.RemoteDisconnected exception in 3.5.0a4. + - Issue #25913: Leading ``<~`` is optional now in base64.a85decode() with adobe=True. Patch by Swati Jaiswal.