From e265c6d89eb41df970a118628bedd9427e39f5fd Mon Sep 17 00:00:00 2001 From: akshatsrivastava11 Date: Wed, 1 Apr 2026 23:18:54 +0530 Subject: [PATCH 1/4] http: add req.signal to IncomingMessage --- lib/_http_incoming.js | 26 ++++++++++++++ test/parallel/test-http-request-signal.js | 44 +++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 test/parallel/test-http-request-signal.js diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js index 04b13358ca717f..ca09957ac97209 100644 --- a/lib/_http_incoming.js +++ b/lib/_http_incoming.js @@ -29,12 +29,15 @@ const { const { Readable, finished } = require('stream'); +const { AbortController } = require('internal/abort_controller'); + const kHeaders = Symbol('kHeaders'); const kHeadersDistinct = Symbol('kHeadersDistinct'); const kHeadersCount = Symbol('kHeadersCount'); const kTrailers = Symbol('kTrailers'); const kTrailersDistinct = Symbol('kTrailersDistinct'); const kTrailersCount = Symbol('kTrailersCount'); +const kAbortController = Symbol('kAbortController'); function readStart(socket) { if (socket && !socket._paused && socket.readable) @@ -90,6 +93,7 @@ function IncomingMessage(socket) { // Flag for when we decide that this message cannot possibly be // read by the user, so there's no point continuing to handle it. this._dumped = false; + this[kAbortController] = undefined; } ObjectSetPrototypeOf(IncomingMessage.prototype, Readable.prototype); ObjectSetPrototypeOf(IncomingMessage, Readable); @@ -184,6 +188,28 @@ ObjectDefineProperty(IncomingMessage.prototype, 'trailersDistinct', { }, }); +ObjectDefineProperty(IncomingMessage.prototype, 'signal', { + __proto__: null, + configurable: true, + get: function() { + if (this[kAbortController] === undefined) { + const ac = new AbortController(); + this[kAbortController] = ac; + if (this.destroyed) { + ac.abort(); + } else { + this.once('close', function() { + ac.abort(); + }); + this.once('abort', function() { + ac.abort(); + }); + } + } + return this[kAbortController].signal; + }, +}); + IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) { if (callback) this.on('timeout', callback); diff --git a/test/parallel/test-http-request-signal.js b/test/parallel/test-http-request-signal.js new file mode 100644 index 00000000000000..0ac3f7664ff9f2 --- /dev/null +++ b/test/parallel/test-http-request-signal.js @@ -0,0 +1,44 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +// Test 1: req.signal is an AbortSignal and aborts on 'close' +{ + const server = http.createServer(common.mustCall((req, res) => { + assert.ok(req.signal instanceof AbortSignal); + assert.strictEqual(req.signal.aborted, false); + req.signal.onabort = common.mustCall(() => { + assert.strictEqual(req.signal.aborted, true); + }); + res.destroy(); + })); + server.listen(0, common.mustCall(() => { + http.get({ port: server.address().port }, () => {}).on('error', () => { + server.close(); + }); + })); +} + +// Test 2: req.signal is aborted if accessed after destroy +{ + const req = new http.IncomingMessage(null); + req.destroy(); + assert.strictEqual(req.signal.aborted, true); +} + +// Test 3: Multiple accesses return the same signal +{ + const req = new http.IncomingMessage(null); + assert.strictEqual(req.signal, req.signal); +} + +// Test 4: req.signal aborts when 'abort' event is emitted on req +{ + const req = new http.IncomingMessage(null); + const signal = req.signal; + assert.strictEqual(signal.aborted, false); + req.emit('abort'); + assert.strictEqual(signal.aborted, true); +} From 18f6103bd30b15663799b0bf333815d4de8b2417 Mon Sep 17 00:00:00 2001 From: akshatsrivastava11 Date: Thu, 2 Apr 2026 09:27:17 +0530 Subject: [PATCH 2/4] test: add test for res.signal on client side http.request() response --- test/parallel/test-http-request-signal.js | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/parallel/test-http-request-signal.js b/test/parallel/test-http-request-signal.js index 0ac3f7664ff9f2..6fd2abea0c3b55 100644 --- a/test/parallel/test-http-request-signal.js +++ b/test/parallel/test-http-request-signal.js @@ -42,3 +42,29 @@ const http = require('http'); req.emit('abort'); assert.strictEqual(signal.aborted, true); } + +// Test 5: res.signal on a client-side http.request() response (IncomingMessage). +{ + const server = http.createServer(common.mustCall((req, res) => { + res.writeHead(200); + res.write('partial'); + })); + + server.listen(0, common.mustCall(() => { + const clientReq = http.request( + { port: server.address().port }, + common.mustCall((res) => { + assert.ok(res.signal instanceof AbortSignal); + assert.strictEqual(res.signal.aborted, false); + + res.signal.onabort = common.mustCall(() => { + assert.strictEqual(res.signal.aborted, true); + server.close(); + }); + clientReq.destroy(); + }), + ); + clientReq.on('error', () => {}); + clientReq.end(); + })); +} From b9a1333b3d3249ed93e1a269ee1b83119102474d Mon Sep 17 00:00:00 2001 From: akshatsrivastava11 Date: Thu, 2 Apr 2026 15:02:46 +0530 Subject: [PATCH 3/4] http: initialize kAbortController to null instead of undefined --- lib/_http_incoming.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js index ca09957ac97209..a1399d7ab3d7c6 100644 --- a/lib/_http_incoming.js +++ b/lib/_http_incoming.js @@ -93,7 +93,7 @@ function IncomingMessage(socket) { // Flag for when we decide that this message cannot possibly be // read by the user, so there's no point continuing to handle it. this._dumped = false; - this[kAbortController] = undefined; + this[kAbortController] = null; } ObjectSetPrototypeOf(IncomingMessage.prototype, Readable.prototype); ObjectSetPrototypeOf(IncomingMessage, Readable); @@ -192,7 +192,7 @@ ObjectDefineProperty(IncomingMessage.prototype, 'signal', { __proto__: null, configurable: true, get: function() { - if (this[kAbortController] === undefined) { + if (this[kAbortController] === null) { const ac = new AbortController(); this[kAbortController] = ac; if (this.destroyed) { From bdf63e12023f647456d8b8035ce7a5aa69869132 Mon Sep 17 00:00:00 2001 From: akshatsrivastava11 Date: Thu, 2 Apr 2026 17:40:22 +0530 Subject: [PATCH 4/4] http: remove abort listener and add test for server-side req.signal.aborted on client disconnect --- lib/_http_incoming.js | 3 --- test/parallel/test-http-request-signal.js | 33 ++++++++++++++++------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js index a1399d7ab3d7c6..18aed3b8df037d 100644 --- a/lib/_http_incoming.js +++ b/lib/_http_incoming.js @@ -201,9 +201,6 @@ ObjectDefineProperty(IncomingMessage.prototype, 'signal', { this.once('close', function() { ac.abort(); }); - this.once('abort', function() { - ac.abort(); - }); } } return this[kAbortController].signal; diff --git a/test/parallel/test-http-request-signal.js b/test/parallel/test-http-request-signal.js index 6fd2abea0c3b55..b7c9fdc79e8365 100644 --- a/test/parallel/test-http-request-signal.js +++ b/test/parallel/test-http-request-signal.js @@ -34,16 +34,8 @@ const http = require('http'); assert.strictEqual(req.signal, req.signal); } -// Test 4: req.signal aborts when 'abort' event is emitted on req -{ - const req = new http.IncomingMessage(null); - const signal = req.signal; - assert.strictEqual(signal.aborted, false); - req.emit('abort'); - assert.strictEqual(signal.aborted, true); -} -// Test 5: res.signal on a client-side http.request() response (IncomingMessage). +// Test 4: res.signal on a client-side http.request() response (IncomingMessage). { const server = http.createServer(common.mustCall((req, res) => { res.writeHead(200); @@ -68,3 +60,26 @@ const http = require('http'); clientReq.end(); })); } + +// Test 5: Client cancels a pending request. +{ + const server = http.createServer(common.mustCall((req, res) => { + req.signal.onabort = common.mustCall(() => { + assert.strictEqual(req.signal.aborted, true); + server.close(); + }); + res.flushHeaders(); + })); + + server.listen(0, common.mustCall(() => { + const clientReq = http.request( + { port: server.address().port }, + common.mustCall((res) => { + res.on('error', () => {}); + clientReq.destroy(); + }), + ); + clientReq.on('error', () => {}); + clientReq.end(); + })); +}