Skip to content

Commit 0c5e44c

Browse files
pietermeessindresorhus
authored andcommitted
Fix socket connect listener memory leak (#406)
1 parent ae1a0fe commit 0c5e44c

3 files changed

Lines changed: 151 additions & 23 deletions

File tree

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ function requestAsEventEmitter(opts) {
221221
total: uploadBodySize
222222
});
223223

224-
req.connection.on('connect', () => {
224+
req.connection.once('connect', () => {
225225
const uploadEventFrequency = 150;
226226

227227
progressInterval = setInterval(() => {

test/agent.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import {Agent as HttpAgent} from 'http';
2+
import {Agent as HttpsAgent} from 'https';
3+
import test from 'ava';
4+
import pem from 'pem';
5+
import pify from 'pify';
6+
import sinon from 'sinon';
7+
import got from '..';
8+
import {createServer, createSSLServer} from './helpers/server';
9+
10+
let http;
11+
let https;
12+
13+
const pemP = pify(pem, Promise);
14+
15+
test.before('setup', async () => {
16+
const caKeys = await pemP.createCertificate({
17+
days: 1,
18+
selfSigned: true
19+
});
20+
21+
const caRootKey = caKeys.serviceKey;
22+
const caRootCert = caKeys.certificate;
23+
24+
const keys = await pemP.createCertificate({
25+
serviceCertificate: caRootCert,
26+
serviceKey: caRootKey,
27+
serial: Date.now(),
28+
days: 500,
29+
country: '',
30+
state: '',
31+
locality: '',
32+
organization: '',
33+
organizationUnit: '',
34+
commonName: 'sindresorhus.com'
35+
});
36+
37+
const key = keys.clientKey;
38+
const cert = keys.certificate;
39+
40+
https = await createSSLServer({key, cert}); // eslint-disable-line object-property-newline
41+
http = await createServer();
42+
43+
// HTTPS Handlers
44+
45+
https.on('/', (req, res) => {
46+
res.end('https');
47+
});
48+
49+
https.on('/httpsToHttp', (req, res) => {
50+
res.writeHead(302, {
51+
location: http.url
52+
});
53+
res.end();
54+
});
55+
56+
// HTTP Handlers
57+
58+
http.on('/', (req, res) => {
59+
res.end('http');
60+
});
61+
62+
http.on('/httpToHttps', (req, res) => {
63+
res.writeHead(302, {
64+
location: https.url
65+
});
66+
res.end();
67+
});
68+
69+
await http.listen(http.port);
70+
await https.listen(https.port);
71+
});
72+
73+
const createAgentSpy = Cls => {
74+
const agent = new Cls({keepAlive: true});
75+
const spy = sinon.spy(agent, 'addRequest');
76+
return {agent, spy};
77+
};
78+
79+
test('non-object agent option works with http', async t => {
80+
const {agent, spy} = createAgentSpy(HttpAgent);
81+
t.truthy((await got(`${http.url}/`, {
82+
rejectUnauthorized: false,
83+
agent
84+
})).body);
85+
t.true(spy.calledOnce);
86+
// Make sure to close all open sockets
87+
agent.destroy();
88+
});
89+
90+
test('non-object agent option works with https', async t => {
91+
const {agent, spy} = createAgentSpy(HttpsAgent);
92+
t.truthy((await got(`${https.url}/`, {
93+
rejectUnauthorized: false,
94+
agent
95+
})).body);
96+
t.true(spy.calledOnce);
97+
// Make sure to close all open sockets
98+
agent.destroy();
99+
});
100+
101+
test('redirects from http to https work with an agent object', async t => {
102+
const {agent: httpAgent, spy: httpSpy} = createAgentSpy(HttpAgent);
103+
const {agent: httpsAgent, spy: httpsSpy} = createAgentSpy(HttpsAgent);
104+
t.truthy((await got(`${http.url}/httpToHttps`, {
105+
rejectUnauthorized: false,
106+
agent: {
107+
http: httpAgent,
108+
https: httpsAgent
109+
}
110+
})).body);
111+
t.true(httpSpy.calledOnce);
112+
t.true(httpsSpy.calledOnce);
113+
// Make sure to close all open sockets
114+
httpAgent.destroy();
115+
httpsAgent.destroy();
116+
});
117+
118+
test('redirects from https to http work with an agent object', async t => {
119+
const {agent: httpAgent, spy: httpSpy} = createAgentSpy(HttpAgent);
120+
const {agent: httpsAgent, spy: httpsSpy} = createAgentSpy(HttpsAgent);
121+
t.truthy((await got(`${https.url}/httpsToHttp`, {
122+
rejectUnauthorized: false,
123+
agent: {
124+
http: httpAgent,
125+
https: httpsAgent
126+
}
127+
})).body);
128+
t.true(httpSpy.calledOnce);
129+
t.true(httpsSpy.calledOnce);
130+
// Make sure to close all open sockets
131+
httpAgent.destroy();
132+
httpsAgent.destroy();
133+
});
134+
135+
test('socket connect listener cleaned up after request', async t => {
136+
const {agent} = createAgentSpy(HttpsAgent);
137+
await got(`${https.url}`, {
138+
rejectUnauthorized: false,
139+
agent
140+
});
141+
Object.keys(agent.freeSockets).forEach(k => agent.freeSockets[k]
142+
.forEach(sock => t.is(sock.listenerCount('connect'), 0)));
143+
// Make sure to close all open sockets
144+
agent.destroy();
145+
});
146+
147+
test.after('cleanup', async () => {
148+
await http.close();
149+
await https.close();
150+
});

test/redirects.js

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import {Agent as HttpAgent} from 'http';
2-
import {Agent as HttpsAgent} from 'https';
31
import test from 'ava';
42
import pem from 'pem';
53
import pify from 'pify';
6-
import sinon from 'sinon';
74
import got from '..';
85
import {createServer, createSSLServer} from './helpers/server';
96

@@ -190,25 +187,6 @@ test('redirects from https to http works', async t => {
190187
t.truthy((await got(`${https.url}/httpsToHttp`, {rejectUnauthorized: false})).body);
191188
});
192189

193-
test('redirects from https to http works with an agent object', async t => {
194-
const httpAgent = new HttpAgent({keepAlive: true});
195-
const httpsAgent = new HttpsAgent({keepAlive: true});
196-
const httpSpy = sinon.spy(httpAgent, 'addRequest');
197-
const httpsSpy = sinon.spy(httpsAgent, 'addRequest');
198-
t.truthy((await got(`${https.url}/httpsToHttp`, {
199-
rejectUnauthorized: false,
200-
agent: {
201-
http: httpAgent,
202-
https: httpsAgent
203-
}
204-
})).body);
205-
t.true(httpSpy.calledOnce);
206-
t.true(httpsSpy.calledOnce);
207-
// Make sure to close all open sockets
208-
httpAgent.destroy();
209-
httpsAgent.destroy();
210-
});
211-
212190
test('redirects works with lowercase method', async t => {
213191
const body = (await got(`${http.url}/relative`, {method: 'head'})).body;
214192
t.is(body, '');

0 commit comments

Comments
 (0)