Skip to content

Commit 43ac439

Browse files
committed
https: fix memory leak with https.request()
If calling `https.request()` with `options.headers.host` defined and `options.servername` undefined, `https.Agent.createSocket` mutates connection `options` after `https.Agent.addRequest` has created empty socket pool array with mismatching connection name. This results in two socket pool arrays being created and only the former gets eventually deleted by `removeSocket` - causing a memory leak. This commit fixes the leak by making sure that `addRequest` does the same modifications to `options` object as the `createSocket`. `createSocket` is intentionally left unmodified to prevent userland regressions. Test case included. Fixes: #6687
1 parent db1087c commit 43ac439

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

lib/_http_agent.js

+8
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ Agent.prototype.addRequest = function(req, options) {
123123
options = util._extend({}, options);
124124
options = util._extend(options, this.options);
125125

126+
if (!options.servername) {
127+
options.servername = options.host;
128+
const hostHeader = req.getHeader('host');
129+
if (hostHeader) {
130+
options.servername = hostHeader.replace(/:.*$/, '');
131+
}
132+
}
133+
126134
var name = this.getName(options);
127135
if (!this.sockets[name]) {
128136
this.sockets[name] = [];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto) {
5+
common.skip('missing crypto');
6+
return;
7+
}
8+
9+
const fs = require('fs');
10+
const https = require('https');
11+
const assert = require('assert');
12+
13+
const options = {
14+
key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'),
15+
cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem'),
16+
ca: fs.readFileSync(common.fixturesDir + '/keys/ca1-cert.pem')
17+
};
18+
19+
const server = https.Server(options, common.mustCall((req, res) => {
20+
res.writeHead(200);
21+
res.end('https\n');
22+
}));
23+
24+
const agent = new https.Agent({
25+
keepAlive: false
26+
});
27+
28+
server.listen(0, common.mustCall(() => {
29+
https.get({
30+
host: server.address().host,
31+
port: server.address().port,
32+
headers: {host: 'agent1'},
33+
rejectUnauthorized: true,
34+
ca: options.ca,
35+
agent: agent
36+
}, common.mustCall((res) => {
37+
38+
res.resume();
39+
server.close();
40+
41+
// Only one entry should exist in agent.sockets pool
42+
// If there are more entries in agent.sockets,
43+
// removeSocket will not delete them resulting in a resource leak
44+
45+
assert.strictEqual(Object.keys(agent.sockets).length, 1);
46+
47+
res.req.on('close', common.mustCall(() => {
48+
49+
// To verify that no leaks occur, check that no entries
50+
// exist in agent.sockets pool after both request and socket
51+
// has been closed.
52+
53+
assert.strictEqual(Object.keys(agent.sockets).length, 0);
54+
55+
}));
56+
57+
}));
58+
}));
59+

0 commit comments

Comments
 (0)