From bbb41be3da5f73f7ece9d73e83c426a51f0f0e49 Mon Sep 17 00:00:00 2001 From: Buyeon Hwang Date: Fri, 15 Aug 2025 06:35:00 +0000 Subject: [PATCH 1/2] gh-102156: Fix HTTPServer timeout ignored in keep-alive connections --- Lib/http/server.py | 11 ++- Lib/test/test_httpservers.py | 134 +++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index a2ffbe2e44df64..4f558448e452b0 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -428,7 +428,16 @@ def handle_one_request(self): """ try: - self.raw_requestline = self.rfile.readline(65537) + # Set socket timeout for the readline operation to respect server timeout + prev_timeout = self.connection.gettimeout() + if hasattr(self.server, 'timeout') and self.server.timeout is not None: + self.connection.settimeout(self.server.timeout) + + try: + self.raw_requestline = self.rfile.readline(65537) + finally: + # Restore previous timeout + self.connection.settimeout(prev_timeout) if len(self.raw_requestline) > 65536: self.requestline = '' self.request_version = '' diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 2548a7c5f292f0..fe025b154ac0b1 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1122,6 +1122,140 @@ def test_date_time_string(self): self.assertEqual(self.handler.date_time_string(timestamp=now), expected) +class HTTPServerTimeoutTestCase(unittest.TestCase): + """Test HTTPServer timeout functionality in keep-alive connections. + + Regression test for Issue #102156: HTTPServer.handle_request() not + respecting timeout with keep-alive connections. + """ + + def setUp(self): + self._threads = threading_helper.threading_setup() + self.server_started = threading.Event() + + def tearDown(self): + threading_helper.threading_cleanup(*self._threads) + + def test_timeout_in_keepalive_connections(self): + """Test that handle_request respects timeout in keep-alive connections.""" + + class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(HTTPStatus.OK) + self.send_header('Content-Type', 'text/plain') + self.send_header('Connection', 'keep-alive') + self.end_headers() + self.wfile.write(b'Hello World!') + + # Create server with short timeout + server = HTTPServer(('localhost', 0), request_handler) + server.timeout = 0.5 # 500ms timeout + port = server.server_address[1] + + server_ready = threading.Event() + client_done = threading.Event() + + def client_worker(): + """Client that creates keep-alive connection and waits.""" + # Wait for server to be ready to accept connections + if not server_ready.wait(timeout=2.0): + return + try: + import socket + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect(('localhost', port)) + # Send first request + sock.send(b'GET / HTTP/1.1\r\nHost: localhost\r\n' + b'Connection: keep-alive\r\n\r\n') + sock.recv(1024) # Receive response + client_done.set() # Signal that first request is complete + # Keep connection open longer than timeout + time.sleep(1.0) + except Exception: + pass # Expected if connection times out + + # Start client thread + client_thread = threading.Thread(target=client_worker, daemon=True) + client_thread.start() + + # Signal that server is ready and test handle_request with timeout + server_ready.set() + start_time = time.time() + server.handle_request() + duration = time.time() - start_time + + server.server_close() + + # Should complete within reasonable time (not hang indefinitely) + # Allow some buffer for timing variations + self.assertLess(duration, 1.0, + "handle_request should timeout, not hang indefinitely") + self.assertGreater(duration, 0.4, + "handle_request should wait for timeout period") + + def test_timeout_with_no_requests(self): + """Test that handle_request times out when no requests are made.""" + + class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(HTTPStatus.OK) + self.end_headers() + + server = HTTPServer(('localhost', 0), request_handler) + server.timeout = 0.5 # 500ms timeout + + start_time = time.time() + server.handle_request() + duration = time.time() - start_time + + server.server_close() + + # Should timeout after approximately 0.5 seconds + self.assertGreaterEqual(duration, 0.4) + self.assertLess(duration, 0.7) + + def test_normal_request_with_timeout(self): + """Test that normal requests complete quickly despite timeout setting.""" + + class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(HTTPStatus.OK) + self.end_headers() + + server = HTTPServer(('localhost', 0), request_handler) + server.timeout = 1.0 # 1 second timeout + port = server.server_address[1] + + server_ready = threading.Event() + + def make_request(): + # Wait for server to be ready to accept connections + if not server_ready.wait(timeout=2.0): + return + try: + import socket + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect(('localhost', port)) + sock.send(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + sock.recv(1024) + except Exception: + pass + + client_thread = threading.Thread(target=make_request, daemon=True) + client_thread.start() + + # Signal that server is ready and test normal request handling + server_ready.set() + start_time = time.time() + server.handle_request() + duration = time.time() - start_time + + server.server_close() + + # Should complete quickly when request is made + self.assertLess(duration, 0.5, "Normal requests should complete quickly") + + class SimpleHTTPRequestHandlerTestCase(unittest.TestCase): """ Test url parsing """ def setUp(self): From 829aa420719887150e22d6a2c3732cd36380bc64 Mon Sep 17 00:00:00 2001 From: ArpaAP Date: Fri, 15 Aug 2025 15:58:06 +0900 Subject: [PATCH 2/2] Add NEWS Entry --- .../next/Library/2025-08-15-15-55-00.gh-issue-102156.6thbVz.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-08-15-15-55-00.gh-issue-102156.6thbVz.rst diff --git a/Misc/NEWS.d/next/Library/2025-08-15-15-55-00.gh-issue-102156.6thbVz.rst b/Misc/NEWS.d/next/Library/2025-08-15-15-55-00.gh-issue-102156.6thbVz.rst new file mode 100644 index 00000000000000..f3c77fa2a0bd0e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-15-15-55-00.gh-issue-102156.6thbVz.rst @@ -0,0 +1 @@ +Fix HTTPServer timeout ignored in keep-alive connections