Skip to content

Commit 12da258

Browse files
imyllerMyles Borins
authored andcommitted
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 last one 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. PR-URL: #8647Fixes: #6687 Reviewed-By: Fedor Indutny <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Jackson Tian <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent e97723b commit 12da258

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

‎lib/_http_agent.js‎

Lines changed: 8 additions & 0 deletions
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+
consthostHeader=req.getHeader('host');
129+
if(hostHeader){
130+
options.servername=hostHeader.replace(/:.*$/,'');
131+
}
132+
}
133+
126134
varname=this.getName(options);
127135
if(!this.sockets[name]){
128136
this.sockets[name]=[];
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto){
5+
common.skip('missing crypto');
6+
return;
7+
}
8+
9+
constfs=require('fs');
10+
consthttps=require('https');
11+
constassert=require('assert');
12+
13+
constoptions={
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+
constserver=https.Server(options,common.mustCall((req,res)=>{
20+
res.writeHead(200);
21+
res.end('https\n');
22+
}));
23+
24+
constagent=newhttps.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+
res.resume();
38+
server.close();
39+
40+
// Only one entry should exist in agent.sockets pool
41+
// If there are more entries in agent.sockets,
42+
// removeSocket will not delete them resulting in a resource leak
43+
assert.strictEqual(Object.keys(agent.sockets).length,1);
44+
45+
res.req.on('close',common.mustCall(()=>{
46+
// To verify that no leaks occur, check that no entries
47+
// exist in agent.sockets pool after both request and socket
48+
// has been closed.
49+
assert.strictEqual(Object.keys(agent.sockets).length,0);
50+
}));
51+
}));
52+
}));

0 commit comments

Comments
(0)