From 169eabb11ab4895702f01fed5901bc792417e021 Mon Sep 17 00:00:00 2001 From: theanarkh Date: Sun, 28 Sep 2025 23:54:12 +0800 Subject: [PATCH] http: fix http client leaky with double response --- lib/_http_client.js | 9 +++- lib/_http_common.js | 4 +- ...-http-client-leaky-with-double-response.js | 45 +++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-http-client-leaky-with-double-response.js diff --git a/lib/_http_client.js b/lib/_http_client.js index 63a7befc8ebbb3..43b71cd8bcc208 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -692,7 +692,14 @@ function parserOnIncomingClient(res, shouldKeepAlive) { // We already have a response object, this means the server // sent a double response. socket.destroy(); - return 0; // No special treatment. + if (socket.parser) { + // https://github.com/nodejs/node/issues/60025 + // Now, parser.incoming is pointed to the new IncomingMessage, + // we need to rewrite it to the first one and skip all the pending IncomingMessage + socket.parser.incoming = req.res; + socket.parser.incoming.skipPendingData = true; + } + return 0; } req.res = res; diff --git a/lib/_http_common.js b/lib/_http_common.js index d64c95afab985c..fdf038ca5237ca 100644 --- a/lib/_http_common.js +++ b/lib/_http_common.js @@ -126,7 +126,7 @@ function parserOnBody(b) { const stream = this.incoming; // If the stream has already been removed, then drop it. - if (stream === null) + if (stream === null || stream.skipPendingData) return; // Pretend this was the result of a stream._read call. @@ -141,7 +141,7 @@ function parserOnMessageComplete() { const parser = this; const stream = parser.incoming; - if (stream !== null) { + if (stream !== null && !stream.skipPendingData) { stream.complete = true; // Emit any trailing headers. const headers = parser._headers; diff --git a/test/parallel/test-http-client-leaky-with-double-response.js b/test/parallel/test-http-client-leaky-with-double-response.js new file mode 100644 index 00000000000000..7b7ecb1210da9e --- /dev/null +++ b/test/parallel/test-http-client-leaky-with-double-response.js @@ -0,0 +1,45 @@ +'use strict'; +// Flags: --expose-gc +const common = require('../common'); +const http = require('http'); +const assert = require('assert'); +const { onGC } = require('../common/gc'); + +function createServer() { + const server = http.createServer(common.mustCall((req, res) => { + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ hello: 'world' })); + req.socket.write('HTTP/1.1 400 Bad Request\r\n\r\n'); + })); + + return new Promise((resolve) => { + server.listen(0, common.mustCall(() => { + resolve(server); + })); + }); +} + +async function main() { + const server = await createServer(); + const req = http.get({ + host: '127.0.0.1', + port: server.address().port, + }, common.mustCall((res) => { + const chunks = []; + res.on('data', (c) => chunks.push(c)); + res.on('end', common.mustCall(() => { + const body = Buffer.concat(chunks).toString('utf8'); + const data = JSON.parse(body); + assert.strictEqual(data.hello, 'world'); + })); + })); + const timer = setInterval(global.gc, 500); + onGC(req, { + ongc: common.mustCall(() => { + clearInterval(timer); + server.close(); + }) + }); +} + +main();