From 998f35de60b9e13c4af9b6e7c859b5a3d2e4819e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Furga=C5=82a?= <83299832+00200200@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:49:11 +0200 Subject: [PATCH] Return 408 when a connection times out before headers arrive. Chrome often opens an extra idle socket; Node then fires ERR_HTTP_REQUEST_TIMEOUT and hapi answered with 400 Bad Request. Match Node's default and send 408 Request Timeout with Connection: close. Fixes #4447 --- lib/core.js | 6 ++++-- test/request.js | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/core.js b/lib/core.js index 202f6dbe0..d44c577d6 100755 --- a/lib/core.js +++ b/lib/core.js @@ -44,7 +44,9 @@ const internals = { 'closing', 'stop' ], - badRequestResponse: Buffer.from('HTTP/1.1 400 Bad Request\r\n\r\n', 'ascii') + badRequestResponse: Buffer.from('HTTP/1.1 400 Bad Request\r\n\r\n', 'ascii'), + requestTimeoutResponse: Buffer.from('HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n', 'ascii'), + clientTimeoutCodes: new Set(['ERR_HTTP_REQUEST_TIMEOUT', 'HPE_HEADER_TIMEOUT']) }; @@ -575,7 +577,7 @@ exports = module.exports = internals.Core = class { request._reply(error); } else { - socket.end(internals.badRequestResponse); + socket.end(internals.clientTimeoutCodes.has(err.code) ? internals.requestTimeoutResponse : internals.badRequestResponse); } } else { diff --git a/test/request.js b/test/request.js index d2f1760e8..dafc67d55 100755 --- a/test/request.js +++ b/test/request.js @@ -2369,6 +2369,46 @@ describe('Request', () => { await server.stop({ timeout: 1 }); }); + it('returns a request timeout when the client sends no headers', async () => { + + const listener = Http.createServer({ + connectionsCheckingInterval: 20, + headersTimeout: 50, + requestTimeout: 0 + }); + + const server = Hapi.server({ listener, routes: { timeout: { server: false } } }); + await server.start(); + + let responseCount = 0; + server.events.on('response', () => { + + responseCount += 1; + }); + + const client = Net.connect(server.info.port); + const clientEnded = new Promise((resolve, reject) => { + + let response = ''; + client.on('data', (chunk) => { + + response = response + chunk.toString(); + }); + + client.on('end', () => resolve(response)); + client.on('error', reject); + }); + + await new Promise((resolve) => client.on('connect', resolve)); + + const clientResponse = await clientEnded; + expect(clientResponse).to.contain('408 Request Timeout'); + expect(clientResponse).to.contain('Connection: close'); + expect(responseCount).to.equal(0); + + await server.stop({ timeout: 1 }); + }); + it('returns normal response when parser fails with bad method after request is setup', async () => { const server = Hapi.server({ routes: { timeout: { server: false } } });