@@ -1122,6 +1122,140 @@ def test_date_time_string(self):
11221122 self .assertEqual (self .handler .date_time_string (timestamp = now ), expected )
11231123
11241124
1125+ class HTTPServerTimeoutTestCase (unittest .TestCase ):
1126+ """Test HTTPServer timeout functionality in keep-alive connections.
1127+
1128+ Regression test for Issue #102156: HTTPServer.handle_request() not
1129+ respecting timeout with keep-alive connections.
1130+ """
1131+
1132+ def setUp (self ):
1133+ self ._threads = threading_helper .threading_setup ()
1134+ self .server_started = threading .Event ()
1135+
1136+ def tearDown (self ):
1137+ threading_helper .threading_cleanup (* self ._threads )
1138+
1139+ def test_timeout_in_keepalive_connections (self ):
1140+ """Test that handle_request respects timeout in keep-alive connections."""
1141+
1142+ class request_handler (NoLogRequestHandler , BaseHTTPRequestHandler ):
1143+ def do_GET (self ):
1144+ self .send_response (HTTPStatus .OK )
1145+ self .send_header ('Content-Type' , 'text/plain' )
1146+ self .send_header ('Connection' , 'keep-alive' )
1147+ self .end_headers ()
1148+ self .wfile .write (b'Hello World!' )
1149+
1150+ # Create server with short timeout
1151+ server = HTTPServer (('localhost' , 0 ), request_handler )
1152+ server .timeout = 0.5 # 500ms timeout
1153+ port = server .server_address [1 ]
1154+
1155+ server_ready = threading .Event ()
1156+ client_done = threading .Event ()
1157+
1158+ def client_worker ():
1159+ """Client that creates keep-alive connection and waits."""
1160+ # Wait for server to be ready to accept connections
1161+ if not server_ready .wait (timeout = 2.0 ):
1162+ return
1163+ try :
1164+ import socket
1165+ with socket .socket (socket .AF_INET , socket .SOCK_STREAM ) as sock :
1166+ sock .connect (('localhost' , port ))
1167+ # Send first request
1168+ sock .send (b'GET / HTTP/1.1\r \n Host: localhost\r \n '
1169+ b'Connection: keep-alive\r \n \r \n ' )
1170+ sock .recv (1024 ) # Receive response
1171+ client_done .set () # Signal that first request is complete
1172+ # Keep connection open longer than timeout
1173+ time .sleep (1.0 )
1174+ except Exception :
1175+ pass # Expected if connection times out
1176+
1177+ # Start client thread
1178+ client_thread = threading .Thread (target = client_worker , daemon = True )
1179+ client_thread .start ()
1180+
1181+ # Signal that server is ready and test handle_request with timeout
1182+ server_ready .set ()
1183+ start_time = time .time ()
1184+ server .handle_request ()
1185+ duration = time .time () - start_time
1186+
1187+ server .server_close ()
1188+
1189+ # Should complete within reasonable time (not hang indefinitely)
1190+ # Allow some buffer for timing variations
1191+ self .assertLess (duration , 1.0 ,
1192+ "handle_request should timeout, not hang indefinitely" )
1193+ self .assertGreater (duration , 0.4 ,
1194+ "handle_request should wait for timeout period" )
1195+
1196+ def test_timeout_with_no_requests (self ):
1197+ """Test that handle_request times out when no requests are made."""
1198+
1199+ class request_handler (NoLogRequestHandler , BaseHTTPRequestHandler ):
1200+ def do_GET (self ):
1201+ self .send_response (HTTPStatus .OK )
1202+ self .end_headers ()
1203+
1204+ server = HTTPServer (('localhost' , 0 ), request_handler )
1205+ server .timeout = 0.5 # 500ms timeout
1206+
1207+ start_time = time .time ()
1208+ server .handle_request ()
1209+ duration = time .time () - start_time
1210+
1211+ server .server_close ()
1212+
1213+ # Should timeout after approximately 0.5 seconds
1214+ self .assertGreaterEqual (duration , 0.4 )
1215+ self .assertLess (duration , 0.7 )
1216+
1217+ def test_normal_request_with_timeout (self ):
1218+ """Test that normal requests complete quickly despite timeout setting."""
1219+
1220+ class request_handler (NoLogRequestHandler , BaseHTTPRequestHandler ):
1221+ def do_GET (self ):
1222+ self .send_response (HTTPStatus .OK )
1223+ self .end_headers ()
1224+
1225+ server = HTTPServer (('localhost' , 0 ), request_handler )
1226+ server .timeout = 1.0 # 1 second timeout
1227+ port = server .server_address [1 ]
1228+
1229+ server_ready = threading .Event ()
1230+
1231+ def make_request ():
1232+ # Wait for server to be ready to accept connections
1233+ if not server_ready .wait (timeout = 2.0 ):
1234+ return
1235+ try :
1236+ import socket
1237+ with socket .socket (socket .AF_INET , socket .SOCK_STREAM ) as sock :
1238+ sock .connect (('localhost' , port ))
1239+ sock .send (b'GET / HTTP/1.1\r \n Host: localhost\r \n \r \n ' )
1240+ sock .recv (1024 )
1241+ except Exception :
1242+ pass
1243+
1244+ client_thread = threading .Thread (target = make_request , daemon = True )
1245+ client_thread .start ()
1246+
1247+ # Signal that server is ready and test normal request handling
1248+ server_ready .set ()
1249+ start_time = time .time ()
1250+ server .handle_request ()
1251+ duration = time .time () - start_time
1252+
1253+ server .server_close ()
1254+
1255+ # Should complete quickly when request is made
1256+ self .assertLess (duration , 0.5 , "Normal requests should complete quickly" )
1257+
1258+
11251259class SimpleHTTPRequestHandlerTestCase (unittest .TestCase ):
11261260 """ Test url parsing """
11271261 def setUp (self ):
0 commit comments