Skip to content

Commit 6d9bd7d

Browse files
committed
http: fix http client leaky with double response
1 parent c6316f9 commit 6d9bd7d

File tree

3 files changed

+58
-3
lines changed

3 files changed

+58
-3
lines changed

lib/_http_client.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const {
4747
HTTPParser,
4848
isLenient,
4949
prepareError,
50+
kSkipPendingData,
5051
} = require('_http_common');
5152
const {
5253
kUniqueHeaders,
@@ -692,7 +693,14 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
692693
// We already have a response object, this means the server
693694
// sent a double response.
694695
socket.destroy();
695-
return 0; // No special treatment.
696+
if (socket.parser) {
697+
// https://github.com/nodejs/node/issues/60025
698+
// Now, parser.incoming is pointed to the new IncomingMessage,
699+
// we need to rewrite it to the first one and skip all the pending IncomingMessage
700+
socket.parser.incoming = req.res;
701+
socket.parser.incoming[kSkipPendingData] = true;
702+
}
703+
return 0;
696704
}
697705
req.res = res;
698706

lib/_http_common.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const {
4141
} = incoming;
4242

4343
const kIncomingMessage = Symbol('IncomingMessage');
44+
const kSkipPendingData = Symbol('SkipPendingData');
4445
const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0;
4546
const kOnHeaders = HTTPParser.kOnHeaders | 0;
4647
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
@@ -126,7 +127,7 @@ function parserOnBody(b) {
126127
const stream = this.incoming;
127128

128129
// If the stream has already been removed, then drop it.
129-
if (stream === null)
130+
if (stream === null || stream[kSkipPendingData])
130131
return;
131132

132133
// Pretend this was the result of a stream._read call.
@@ -141,7 +142,7 @@ function parserOnMessageComplete() {
141142
const parser = this;
142143
const stream = parser.incoming;
143144

144-
if (stream !== null) {
145+
if (stream !== null && !stream[kSkipPendingData]) {
145146
stream.complete = true;
146147
// Emit any trailing headers.
147148
const headers = parser._headers;
@@ -310,4 +311,5 @@ module.exports = {
310311
HTTPParser,
311312
isLenient,
312313
prepareError,
314+
kSkipPendingData,
313315
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use strict';
2+
// Flags: --expose-gc
3+
const common = require('../common');
4+
const http = require('http');
5+
const assert = require('assert');
6+
const { onGC } = require('../common/gc');
7+
8+
function createServer() {
9+
const server = http.createServer(common.mustCall((req, res) => {
10+
res.setHeader('Content-Type', 'application/json');
11+
res.end(JSON.stringify({ hello: 'world' }));
12+
req.socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
13+
}));
14+
15+
return new Promise((resolve) => {
16+
server.listen(0, common.mustCall(() => {
17+
resolve(server);
18+
}));
19+
});
20+
}
21+
22+
async function main() {
23+
const server = await createServer();
24+
const req = http.get({
25+
host: '127.0.0.1',
26+
port: server.address().port,
27+
}, common.mustCall((res) => {
28+
const chunks = [];
29+
res.on('data', (c) => chunks.push(c));
30+
res.on('end', common.mustCall(() => {
31+
const body = Buffer.concat(chunks).toString('utf8');
32+
const data = JSON.parse(body);
33+
assert.strictEqual(data.hello, 'world');
34+
}));
35+
}));
36+
const timer = setInterval(global.gc, 300);
37+
onGC(req, {
38+
ongc: common.mustCall(() => {
39+
clearInterval(timer);
40+
server.close();
41+
})
42+
});
43+
}
44+
45+
main();

0 commit comments

Comments
 (0)