From f4379f5d0142f20c2e2e2887d6ab7418838321ea Mon Sep 17 00:00:00 2001 From: SapirBaruch Date: Mon, 18 May 2026 19:44:56 +0300 Subject: [PATCH 1/2] fix: replace deprecated 'aborted' event with 'close' on IncomingMessage --- lib/request.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/request.js b/lib/request.js index 2c40cdc98..4b05c8a38 100755 --- a/lib/request.js +++ b/lib/request.js @@ -325,7 +325,7 @@ exports = module.exports = internals.Request = class { this.raw.res.on('close', internals.event.bind(this.raw.res, this._eventContext, 'close')); this.raw.req.on('error', internals.event.bind(this.raw.req, this._eventContext, 'error')); - this.raw.req.on('aborted', internals.event.bind(this.raw.req, this._eventContext, 'abort')); + this.raw.req.on('close', internals.aborted.bind(this.raw.req, this._eventContext, this.raw.res)); this.raw.res.once('close', internals.closed.bind(this.raw.res, this)); } @@ -713,6 +713,19 @@ internals.closed = function (request) { request._closed = true; }; + +internals.aborted = function (eventContext, res) { + + // The 'aborted' event on IncomingMessage was deprecated in Node.js v17 and removed in v24. + // Use the 'close' event on the request socket instead, guarded by a check that the response + // has not already been written (to distinguish a client abort from a normal connection close). + + if (!res.writableEnded) { + internals.event(eventContext, 'abort'); + } +}; + + internals.event = function ({ request }, event, err) { if (!request) { From 96da532a95057fc64c05dd5422816ed6f011e379 Mon Sep 17 00:00:00 2001 From: SapirBaruch Date: Tue, 19 May 2026 12:10:18 +0300 Subject: [PATCH 2/2] test: add regression test for close-event disconnect detection Adds a test verifying that request.active() returns false when the client closes the connection before a response is sent. The test uses Net.connect so it works regardless of hostname resolution, and polls active() directly without relying on the 'disconnect' event. Covers the Node.js v24+ path where IncomingMessage 'aborted' is gone. --- test/request.js | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/request.js b/test/request.js index d2f1760e8..de68ad856 100755 --- a/test/request.js +++ b/test/request.js @@ -474,6 +474,56 @@ describe('Request', () => { testComplete: false }); }); + + it('returns false after client closes connection before response is sent', { retry: true }, async (flags) => { + + // Regression test: IncomingMessage 'aborted' event was removed in Node.js v24. + // Verify that an early client disconnect still causes active() to return false. + + const handlerTeam = new Teamwork.Team(); + + const server = Hapi.server(); + flags.onCleanup = () => server.stop(); + + let client; + + server.route({ + method: 'GET', + path: '/', + options: { + handler: async (request) => { + + // Drop the connection from the client side + client.destroy(); + + // Poll until the server-side close propagates + const deadline = Date.now() + 2000; + while (request.active() && Date.now() < deadline) { + await Hoek.wait(10); + } + + handlerTeam.attend({ active: request.active() }); + return null; + } + } + }); + + await server.start(); + + await new Promise((resolve) => { + + client = Net.connect(server.info.port, () => { + + client.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'); + resolve(); + }); + + client.on('error', Hoek.ignore); + }); + + const result = await handlerTeam.work; + expect(result.active).to.be.false(); + }); }); describe('_execute()', () => {