From 994c80fe70b6cbfb555c7a178865d7fda75a7e0c Mon Sep 17 00:00:00 2001 From: Paresh Joshi Date: Tue, 25 Nov 2025 16:44:35 +0530 Subject: [PATCH 01/10] Add error handling in request method Handle exceptions during request transmission and close connection on failure. --- Lib/http/client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/http/client.py b/Lib/http/client.py index 4b9a61cfc1159f..f537c411486cb6 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -1339,7 +1339,13 @@ def endheaders(self, message_body=None, *, encode_chunked=False): def request(self, method, url, body=None, headers={}, *, encode_chunked=False): """Send a complete request to the server.""" + try: self._send_request(method, url, body, headers, encode_chunked) + except: + # If the transmission fails (e.g. timeout), close the connection + # to reset the state machine to _CS_IDLE. + self.close() + raise def _send_request(self, method, url, body, headers, encode_chunked): # Honor explicitly requested Host: and Accept-Encoding: headers. From cfde44b9a4df021beb3fa8bd22d5973d1084eb7d Mon Sep 17 00:00:00 2001 From: Paresh Joshi Date: Tue, 25 Nov 2025 17:13:40 +0530 Subject: [PATCH 02/10] Specify exception type in request method --- Lib/http/client.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/http/client.py b/Lib/http/client.py index f537c411486cb6..32923fdd3d0446 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -1339,13 +1339,13 @@ def endheaders(self, message_body=None, *, encode_chunked=False): def request(self, method, url, body=None, headers={}, *, encode_chunked=False): """Send a complete request to the server.""" - try: - self._send_request(method, url, body, headers, encode_chunked) - except: - # If the transmission fails (e.g. timeout), close the connection - # to reset the state machine to _CS_IDLE. - self.close() - raise + try: + self._send_request(method, url, body, headers, encode_chunked) + except Exception: + # If the transmission fails (e.g. timeout), close the connection + # to reset the state machine to _CS_IDLE + self.close() + raise def _send_request(self, method, url, body, headers, encode_chunked): # Honor explicitly requested Host: and Accept-Encoding: headers. From 5e0b9f3d8292eea93285459ea6f06f4837c9fb79 Mon Sep 17 00:00:00 2001 From: Paresh Joshi Date: Tue, 25 Nov 2025 17:22:50 +0530 Subject: [PATCH 03/10] Catch TimeoutError in send_request method Handle TimeoutError specifically when sending requests. --- Lib/http/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/http/client.py b/Lib/http/client.py index 32923fdd3d0446..505c32f830d209 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -1341,7 +1341,7 @@ def request(self, method, url, body=None, headers={}, *, """Send a complete request to the server.""" try: self._send_request(method, url, body, headers, encode_chunked) - except Exception: + except TimeoutError: # If the transmission fails (e.g. timeout), close the connection # to reset the state machine to _CS_IDLE self.close() From 82885e45ca407d1a4c305361ff85312aea1962db Mon Sep 17 00:00:00 2001 From: Paresh Joshi Date: Tue, 25 Nov 2025 22:17:25 +0530 Subject: [PATCH 04/10] Refactor connection creation and exception handling Updated the connection creation method to use a lambda for better testability and changed exception handling from TimeoutError to OSError. --- Lib/http/client.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Lib/http/client.py b/Lib/http/client.py index 505c32f830d209..7643095c82a81c 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -887,9 +887,12 @@ def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, self._validate_host(self.host) - # This is stored as an instance variable to allow unit - # tests to replace it with a suitable mockup - self._create_connection = socket.create_connection + # Callable used to open sockets. Kept on the instance so tests + # can replace it; use a thin wrapper so the real + # `socket.create_connection` is invoked at `connect()` time. + # This avoids permanently capturing a patched factory at + # construction time. + self._create_connection = (lambda *a, **kw: socket.create_connection(*a, **kw)) def set_tunnel(self, host, port=None, headers=None): """Set up host and port for HTTP CONNECT tunnelling. @@ -1341,7 +1344,7 @@ def request(self, method, url, body=None, headers={}, *, """Send a complete request to the server.""" try: self._send_request(method, url, body, headers, encode_chunked) - except TimeoutError: + except OSError: # If the transmission fails (e.g. timeout), close the connection # to reset the state machine to _CS_IDLE self.close() From 191ff0870d09334b172f80d75e3953030d884caf Mon Sep 17 00:00:00 2001 From: Paresh Joshi Date: Tue, 25 Nov 2025 22:19:32 +0530 Subject: [PATCH 05/10] Add HTTPConnectionStateTests for timeout handling Add tests to verify HTTP connection state resets after a timeout and checks state after a successful request. --- Lib/test/test_httplib.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 47e3914d1dd62e..fedb142d2852f2 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -2568,6 +2568,29 @@ def _create_connection(address, timeout=None, source_address=None): self.assertIsNotNone(exc) self.assertTrue(sock.file_closed) +class HTTPConnectionStateTests(TestCase): + def test_connect_timeout_resets_state(self): + with mock.patch('socket.create_connection', side_effect=TimeoutError("timed out")): + conn = client.HTTPConnection('10.255.255.1', 80, timeout=0.01) + + with self.assertRaises(TimeoutError): + conn.request('GET', '/') + + self.assertEqual(conn._HTTPConnection__state, client._CS_IDLE) + self.assertIsNone(conn.sock) + + with mock.patch('socket.create_connection') as mock_cc: + fake_sock = mock.Mock() + mock_cc.return_value = fake_sock + # Provide a working socket for the second request. + conn.request("GET", "/second") + + # Ensure the connection is in a usable state (either idle or + # request-sent depending on response handling). + self.assertIn(conn._HTTPConnection__state, + (client._CS_REQ_SENT, client._CS_IDLE)) + if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main() + From 303be914eb0fb71b8f9bc9bc6f556d6698555fea Mon Sep 17 00:00:00 2001 From: Paresh Joshi Date: Tue, 25 Nov 2025 22:32:32 +0530 Subject: [PATCH 06/10] Fix unittest.main() call placement in test_httplib.py --- Lib/test/test_httplib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index fedb142d2852f2..244d4de54ff7a1 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -2593,4 +2593,3 @@ def test_connect_timeout_resets_state(self): if __name__ == '__main__': unittest.main() - From afd20983ad9c63859057a6f95c7ce11e7873d45a Mon Sep 17 00:00:00 2001 From: Paresh Joshi Date: Wed, 26 Nov 2025 08:18:14 +0530 Subject: [PATCH 07/10] Increase unittest verbosity to level 2 --- Lib/test/test_httplib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 244d4de54ff7a1..171cd144f75975 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -2592,4 +2592,4 @@ def test_connect_timeout_resets_state(self): if __name__ == '__main__': - unittest.main() + unittest.main(verbosity=2) From a741ac1450e80670ba2116b3ff21f9acc02b26fe Mon Sep 17 00:00:00 2001 From: Paresh Joshi Date: Wed, 26 Nov 2025 09:58:14 +0530 Subject: [PATCH 08/10] Refine OSError handling in HTTP client Handle specific OSError cases to prevent unnecessary connection closure. --- Lib/http/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/http/client.py b/Lib/http/client.py index 7643095c82a81c..06cad3da0ed91c 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -1344,11 +1344,12 @@ def request(self, method, url, body=None, headers={}, *, """Send a complete request to the server.""" try: self._send_request(method, url, body, headers, encode_chunked) - except OSError: + except OSError as e: # If the transmission fails (e.g. timeout), close the connection # to reset the state machine to _CS_IDLE + if getattr(e, "errno", None) != errno.EPIPE: self.close() - raise + raise def _send_request(self, method, url, body, headers, encode_chunked): # Honor explicitly requested Host: and Accept-Encoding: headers. From 9ebed28f958e25b5b460982ff079c17dc0e7b068 Mon Sep 17 00:00:00 2001 From: Paresh Joshi Date: Wed, 26 Nov 2025 10:04:59 +0530 Subject: [PATCH 09/10] Fix indentation in error handling of request method --- Lib/http/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/http/client.py b/Lib/http/client.py index 06cad3da0ed91c..3c15a25459619b 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -1347,9 +1347,9 @@ def request(self, method, url, body=None, headers={}, *, except OSError as e: # If the transmission fails (e.g. timeout), close the connection # to reset the state machine to _CS_IDLE - if getattr(e, "errno", None) != errno.EPIPE: - self.close() - raise + if getattr(e, "errno", None) != errno.EPIPE: + self.close() + raise def _send_request(self, method, url, body, headers, encode_chunked): # Honor explicitly requested Host: and Accept-Encoding: headers. From c71d0f191258090c7d15e441a1683f54ec29f25e Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:32:39 +0000 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Library/2025-11-27-09-32-38.gh-issue-141938.AT4fBQ.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-11-27-09-32-38.gh-issue-141938.AT4fBQ.rst diff --git a/Misc/NEWS.d/next/Library/2025-11-27-09-32-38.gh-issue-141938.AT4fBQ.rst b/Misc/NEWS.d/next/Library/2025-11-27-09-32-38.gh-issue-141938.AT4fBQ.rst new file mode 100644 index 00000000000000..f1048609764665 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-11-27-09-32-38.gh-issue-141938.AT4fBQ.rst @@ -0,0 +1,5 @@ +Fix :mod:`http.client` to properly reset the connection state when an +:exc:`OSError` (such as a :exc:`TimeoutError`) occurs during request sending. +Previously, the state remained inconsistent, causing subsequent +:meth:`~http.client.HTTPConnection.request` calls to raise +:exc:`~http.client.CannotSendRequest`. Patch by pareshjoshij.