From afd357241f9379946a73b92c6717a0b86d5970af Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:52:21 +0100 Subject: [PATCH 1/2] fix(web-incoming): remove deprecated `req.on('aborted')` listener The `aborted` event stopped firing reliably on Node.js v15.5.0+ and was later removed entirely. The listener accumulated without cleanup, causing a memory leak. The existing `res.on('close')` handler already correctly detects client disconnects and destroys the upstream proxy request. Ref: http-party/node-http-proxy#1559 --- src/middleware/web-incoming.ts | 5 -- test/middleware/web-incoming.test.ts | 78 +++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/middleware/web-incoming.ts b/src/middleware/web-incoming.ts index f02a1bc..2d85070 100644 --- a/src/middleware/web-incoming.ts +++ b/src/middleware/web-incoming.ts @@ -115,11 +115,6 @@ export const stream = defineProxyMiddleware((req, res, options, server, head, ca }); } - // Ensure we abort proxy if request is aborted - req.on("aborted", function () { - proxyReq.abort(); - }); - // Abort proxy request when client disconnects res.on("close", function () { if (!res.writableFinished) { diff --git a/test/middleware/web-incoming.test.ts b/test/middleware/web-incoming.test.ts index 2260ce3..f2ffaa0 100644 --- a/test/middleware/web-incoming.test.ts +++ b/test/middleware/web-incoming.test.ts @@ -10,7 +10,7 @@ import { stubMiddlewareOptions, stubProxyServer, } from "../_stubs.ts"; -import { listenOn } from "../_utils.ts"; +import { listenOn, proxyListen } from "../_utils.ts"; // Source: https://github.com/http-party/node-http-proxy/blob/master/test/lib-http-proxy-passes-web-incoming-test.js @@ -854,3 +854,79 @@ describe("#followRedirects", () => { await promise; }); }); + +// Regression: upstream http-party/node-http-proxy#1559 +// req.on('aborted') stopped firing on Node 15.5+ and was later removed, +// causing a memory leak from accumulated listeners that never get cleaned up. +describe("#req-aborted-memory-leak", () => { + it("should not attach deprecated 'aborted' listener on req", async () => { + const { promise, resolve } = Promise.withResolvers(); + + const source = http.createServer((_req, res) => { + res.writeHead(200); + res.end("ok"); + }); + const sourcePort = await listenOn(source); + + const proxyServer = httpProxy.createProxyServer({ + target: `http://127.0.0.1:${sourcePort}`, + }); + + // Intercept the incoming request to inspect its listeners + const server = http.createServer((req, res) => { + const abortedBefore = req.listenerCount("aborted"); + proxyServer.web(req, res).then(() => { + const abortedAfter = req.listenerCount("aborted"); + const addedAbortedListeners = abortedAfter - abortedBefore; + + expect(addedAbortedListeners).to.eql(0); + + source.close(); + server.close(); + proxyServer.close(); + resolve(); + }); + }); + const port = await listenOn(server); + + http.get(`http://127.0.0.1:${port}/test`); + await promise; + }); + + it("should abort upstream request when client disconnects via res close", async () => { + const { promise, resolve } = Promise.withResolvers(); + + let upstreamAborted = false; + const source = http.createServer((req, res) => { + res.writeHead(200, { "content-type": "text/plain" }); + res.write("start"); + req.on("close", () => { + upstreamAborted = true; + }); + }); + const sourcePort = await listenOn(source); + + const proxy = httpProxy.createProxyServer({ + target: `http://127.0.0.1:${sourcePort}`, + }); + const proxyPort = await proxyListen(proxy); + + const clientReq = http.get( + `http://127.0.0.1:${proxyPort}/stream`, + (res) => { + res.once("data", () => { + // Client received first chunk; now abort + clientReq.destroy(); + + setTimeout(() => { + expect(upstreamAborted).to.eql(true); + source.close(); + proxy.close(resolve); + }, 100); + }); + }, + ); + + await promise; + }); +}); From 2752ea6d2150ba60ff9381cde9e7a0d71bdc424b Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:53:01 +0000 Subject: [PATCH 2/2] chore: apply automated updates --- test/middleware/web-incoming.test.ts | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/test/middleware/web-incoming.test.ts b/test/middleware/web-incoming.test.ts index f2ffaa0..73ecf65 100644 --- a/test/middleware/web-incoming.test.ts +++ b/test/middleware/web-incoming.test.ts @@ -911,21 +911,18 @@ describe("#req-aborted-memory-leak", () => { }); const proxyPort = await proxyListen(proxy); - const clientReq = http.get( - `http://127.0.0.1:${proxyPort}/stream`, - (res) => { - res.once("data", () => { - // Client received first chunk; now abort - clientReq.destroy(); - - setTimeout(() => { - expect(upstreamAborted).to.eql(true); - source.close(); - proxy.close(resolve); - }, 100); - }); - }, - ); + const clientReq = http.get(`http://127.0.0.1:${proxyPort}/stream`, (res) => { + res.once("data", () => { + // Client received first chunk; now abort + clientReq.destroy(); + + setTimeout(() => { + expect(upstreamAborted).to.eql(true); + source.close(); + proxy.close(resolve); + }, 100); + }); + }); await promise; });