Skip to content

gh-102156: Fix HTTPServer timeout ignored in keep-alive connections #137806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Lib/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ''
Expand Down
134 changes: 134 additions & 0 deletions Lib/test/test_httpservers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix HTTPServer timeout ignored in keep-alive connections
Loading