From 6964cf62ff1bb25de101115fbc8516393517e6c1 Mon Sep 17 00:00:00 2001 From: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com> Date: Sat, 23 May 2026 22:54:31 +0530 Subject: [PATCH 1/2] Emit late abort errors --- lib/request.js | 24 +++++++++++++++++------- test/request.js | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/lib/request.js b/lib/request.js index 2c40cdc98..7bf2f4959 100755 --- a/lib/request.js +++ b/lib/request.js @@ -507,14 +507,9 @@ exports = module.exports = internals.Request = class { this._eventContext.request = null; // Disable req events - if (this.response._close) { - if (this.response.statusCode === 500 && - this.response._error) { - - const tags = this.response._error.isDeveloperError ? ['internal', 'implementation', 'error'] : ['internal', 'error']; - this._log(tags, this.response._error, 'error'); - } + this._logErrorResponse(this.response); + if (this.response._close) { this.response._close(); } @@ -538,6 +533,7 @@ exports = module.exports = internals.Request = class { } if (this.info.completed) { + this._logErrorResponse(response); response._close?.(); return; @@ -546,6 +542,20 @@ exports = module.exports = internals.Request = class { this.response = response; } + _logErrorResponse(response) { + + const error = response._error ?? (Boom.isBoom(response) ? response : null); + const statusCode = response.statusCode ?? response.output?.statusCode; + if (statusCode !== 500 || + !error) { + + return; + } + + const tags = error.isDeveloperError ? ['internal', 'implementation', 'error'] : ['internal', 'error']; + this._log(tags, error, 'error'); + } + _setState(name, value, options) { const state = { name, value }; diff --git a/test/request.js b/test/request.js index d2f1760e8..1fb0fb2e2 100755 --- a/test/request.js +++ b/test/request.js @@ -766,6 +766,43 @@ describe('Request', () => { await server.stop(); }); + it('emits request-error when handler fails after abort', { retry: true }, async (flags) => { + + const server = Hapi.server({ debug: false }); + const team = new Teamwork.Team(); + + const handler = async (request) => { + + clientRequest.destroy(); + await Hoek.wait(10); + team.attend(); + throw new Error('late failure'); + }; + + server.route({ method: 'GET', path: '/', handler }); + + const log = server.events.once({ name: 'request', channels: 'error' }); + + await server.start(); + flags.onCleanup = () => server.stop(); + + const clientRequest = Http.request({ + hostname: 'localhost', + port: server.info.port, + method: 'GET' + }); + + clientRequest.on('error', Hoek.ignore); + clientRequest.end(); + + await team.work; + + const [, event] = await log; + expect(event.error.message).to.equal('late failure'); + + await server.stop(); + }); + it('does not fail on abort (onPreHandler)', async () => { const server = Hapi.server(); From 106f641da5c09c83e3ece2b164fb1e03eb82baa6 Mon Sep 17 00:00:00 2001 From: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com> Date: Mon, 25 May 2026 17:24:06 +0530 Subject: [PATCH 2/2] Avoid duplicate abort error events Skip hapi-generated disconnect Boom responses when logging internal 500 request errors, so late handler failures after an abort are still emitted once without also emitting the configured disconnect response. Generated-by: OpenAI Codex Signed-off-by: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com> --- lib/request.js | 11 +++++++++- test/request.js | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/request.js b/lib/request.js index 7bf2f4959..1155dad59 100755 --- a/lib/request.js +++ b/lib/request.js @@ -544,8 +544,17 @@ exports = module.exports = internals.Request = class { _logErrorResponse(response) { - const error = response._error ?? (Boom.isBoom(response) ? response : null); + const isBoom = Boom.isBoom(response); + const error = response._error ?? (isBoom ? response : null); const statusCode = response.statusCode ?? response.output?.statusCode; + + if (isBoom && + response.output.statusCode === this.route.settings.response.disconnectStatusCode && + ['Request aborted', 'Request close', 'Response error'].includes(response.message)) { + + return; + } + if (statusCode !== 500 || !error) { diff --git a/test/request.js b/test/request.js index 1fb0fb2e2..3d4332115 100755 --- a/test/request.js +++ b/test/request.js @@ -803,6 +803,59 @@ describe('Request', () => { await server.stop(); }); + it('does not double emit request-error when disconnectStatusCode is 500', { retry: true }, async (flags) => { + + const server = Hapi.server({ debug: false }); + const team = new Teamwork.Team(); + let emitCount = 0; + const errors = []; + + server.events.on({ name: 'request', channels: 'error' }, (request, event) => { + + emitCount++; + errors.push(event.error.message); + }); + + const handler = async (request) => { + + clientRequest.destroy(); + await Hoek.wait(10); + team.attend(); + throw new Error('late failure'); + }; + + server.route({ + method: 'GET', + path: '/', + handler, + options: { + response: { + disconnectStatusCode: 500 + } + } + }); + + await server.start(); + flags.onCleanup = () => server.stop(); + + const clientRequest = Http.request({ + hostname: 'localhost', + port: server.info.port, + method: 'GET' + }); + + clientRequest.on('error', Hoek.ignore); + clientRequest.end(); + + await team.work; + await Hoek.wait(50); + + expect(emitCount).to.equal(1); + expect(errors).to.equal(['late failure']); + + await server.stop(); + }); + it('does not fail on abort (onPreHandler)', async () => { const server = Hapi.server();