node:http: enforce server headersTimeout and requestTimeout
What
Server.headersTimeout and Server.requestTimeout (node:http/https) were validated and stored on the server object, but never enforced: the node:http server is created with idleTimeout: 0, so a connection with stalled request headers, a stalled request body, or that never sent a byte was kept open indefinitely.
This wires both values into uWS as per-socket receive deadlines:
- From connection open (or the completion of the previous keep-alive exchange) until a message's header section is complete, the socket timer is armed with
headersTimeout(default 60s). - While the message body is still incomplete, it is armed with
requestTimeout(default 300s). - When such a deadline expires, the server emits
'clientError'withERR_HTTP_REQUEST_TIMEOUT("Request timeout"), writesHTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\nwhen nothing has been written for that message yet, and closes the socket. - Once the message is fully received, the deadline is dropped: handlers, in-flight responses, and idle keep-alive sockets have no receive deadline.
0disables either value.
All of it is gated on a node-only uWS context option (setNodeReceiveTimeouts), so Bun.serve idleTimeout, keep-alive, and upgrade behavior is untouched (Bun.serve sockets keep the legacy 10s pre-request timeout).
How it was verified
Behavior was derived from real Node (v26.3.0) fixtures and matched case by case: stalled first headers, zero-byte connection, stalled body after dispatch (handler runs, then 'clientError' + req 'aborted' + 408), stalled second keep-alive request, fully-received request with a slow handler (not killed), idle keep-alive socket (not reaped by these timeouts), both values 0 (disabled), and a user 'clientError' listener taking over teardown.
Known bounded deviations from Node: expiry granularity is uWS's ~4s timer sweep rather than connectionsCheckingInterval (Node's default check interval is 30s, so both are coarse); the deadline is re-armed per received chunk (inactivity-based) instead of an absolute per-message deadline; the canned 408 is written from native code even when a synchronous 'clientError' listener has not yet destroyed the socket (same as the existing native 400/431 parse-error responses); values are applied at listen() time; per-socket values above ~940s are capped (uWS timer slot range).
Tests
New cases in test/js/node/http/node-http.test.ts ("server headersTimeout/requestTimeout enforcement"): http+https stalled headers => exact 408 bytes + close; 'clientError' ERR_HTTP_REQUEST_TIMEOUT; stalled body => handler dispatched then aborted with a 408; stalled second keep-alive request; fully-received request with a slow handler still gets its 200; timeouts disabled with 0. All except the two negative-contract cases fail on the previous behavior (USE_SYSTEM_BUN=1).
Also re-ran: the full test/js/node/http/ suite, the vendored test-http(s)-server-*timeout*, keep-alive, upgrade and CONNECT node parallel tests, test/js/bun/http/serve.test.ts, and the Bun.serve websocket suites.