Skip to content

Commit 4267b92

Browse files
committed
http: use Keep-Alive by default in global agents
PR-URL: #43522 Fixes: #37184 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 8e19dab commit 4267b92

27 files changed

+173
-35
lines changed

doc/api/http.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -1449,11 +1449,20 @@ type other than {net.Socket}.
14491449

14501450
<!-- YAML
14511451
added: v0.1.90
1452+
changes:
1453+
- version:
1454+
- REPLACEME
1455+
pr-url: https://github.com/nodejs/node/pull/43522
1456+
description: The method closes idle connections before returning.
1457+
14521458
-->
14531459

14541460
* `callback` {Function}
14551461

1456-
Stops the server from accepting new connections. See [`net.Server.close()`][].
1462+
Stops the server from accepting new connections and closes all connections
1463+
connected to this server which are not sending a request or waiting for
1464+
a response.
1465+
See [`net.Server.close()`][].
14571466

14581467
### `server.closeAllConnections()`
14591468

@@ -3214,6 +3223,11 @@ server.listen(8000);
32143223

32153224
<!-- YAML
32163225
added: v0.5.9
3226+
changes:
3227+
- version:
3228+
- REPLACEME
3229+
pr-url: https://github.com/nodejs/node/pull/43522
3230+
description: The agent now uses HTTP Keep-Alive by default.
32173231
-->
32183232

32193233
* {http.Agent}

doc/api/https.md

+5
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,11 @@ https.get('https://encrypted.google.com/', (res) => {
309309

310310
<!-- YAML
311311
added: v0.5.9
312+
changes:
313+
- version:
314+
- REPLACEME
315+
pr-url: https://github.com/nodejs/node/pull/43522
316+
description: The agent now uses HTTP Keep-Alive by default.
312317
-->
313318

314319
Global instance of [`https.Agent`][] for all HTTPS client requests.

lib/_http_agent.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ const {
3131
ArrayPrototypeSplice,
3232
FunctionPrototypeCall,
3333
NumberIsNaN,
34+
NumberParseInt,
3435
ObjectCreate,
3536
ObjectKeys,
3637
ObjectSetPrototypeOf,
3738
ObjectValues,
39+
RegExpPrototypeExec,
3840
StringPrototypeIndexOf,
3941
StringPrototypeSplit,
4042
StringPrototypeStartsWith,
@@ -492,7 +494,24 @@ Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
492494
socket.setKeepAlive(true, this.keepAliveMsecs);
493495
socket.unref();
494496

495-
const agentTimeout = this.options.timeout || 0;
497+
let agentTimeout = this.options.timeout || 0;
498+
499+
if (socket._httpMessage?.res) {
500+
const keepAliveHint = socket._httpMessage.res.headers['keep-alive'];
501+
502+
if (keepAliveHint) {
503+
const hint = RegExpPrototypeExec(/^timeout=(\d+)/, keepAliveHint)?.[1];
504+
505+
if (hint) {
506+
const serverHintTimeout = NumberParseInt(hint) * 1000;
507+
508+
if (serverHintTimeout < agentTimeout) {
509+
agentTimeout = serverHintTimeout;
510+
}
511+
}
512+
}
513+
}
514+
496515
if (socket.timeout !== agentTimeout) {
497516
socket.setTimeout(agentTimeout);
498517
}
@@ -542,5 +561,5 @@ function asyncResetHandle(socket) {
542561

543562
module.exports = {
544563
Agent,
545-
globalAgent: new Agent()
564+
globalAgent: new Agent({ keepAlive: true, scheduling: 'lifo', timeout: 5000 })
546565
};

lib/_http_server.js

+1
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ ObjectSetPrototypeOf(Server.prototype, net.Server.prototype);
478478
ObjectSetPrototypeOf(Server, net.Server);
479479

480480
Server.prototype.close = function() {
481+
this.closeIdleConnections();
481482
clearInterval(this[kConnectionsCheckingInterval]);
482483
ReflectApply(net.Server.prototype.close, this, arguments);
483484
};

lib/https.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ Agent.prototype._evictSession = function _evictSession(key) {
331331
delete this._sessionCache.map[key];
332332
};
333333

334-
const globalAgent = new Agent();
334+
const globalAgent = new Agent({ keepAlive: true, scheduling: 'lifo', timeout: 5000 });
335335

336336
/**
337337
* Makes a request to a secure web server.

test/async-hooks/test-graph.http.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const hooks = initHooks();
1212
hooks.enable();
1313

1414
const server = http.createServer(common.mustCall((req, res) => {
15+
res.writeHead(200, { 'Connection': 'close' });
1516
res.end();
1617
server.close(common.mustCall());
1718
}));
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const http = require('http');
6+
7+
const server = http.createServer(function(req, res) {
8+
res.writeHead(200);
9+
res.end();
10+
});
11+
12+
server.listen(0, common.mustCall(() => {
13+
const req = http.get({ port: server.address().port }, (res) => {
14+
assert.strictEqual(res.statusCode, 200);
15+
16+
res.resume();
17+
server.close();
18+
});
19+
20+
req.end();
21+
}));
22+
23+
// This timer should never go off as the server will close the socket
24+
setTimeout(common.mustNotCall(), 1000).unref();

test/parallel/test-http-client-agent.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const Countdown = require('../common/countdown');
2727

2828
let name;
2929
const max = 3;
30+
const agent = new http.Agent();
3031

3132
const server = http.Server(common.mustCall((req, res) => {
3233
if (req.url === '/0') {
@@ -40,27 +41,28 @@ const server = http.Server(common.mustCall((req, res) => {
4041
}
4142
}, max));
4243
server.listen(0, common.mustCall(() => {
43-
name = http.globalAgent.getName({ port: server.address().port });
44+
name = agent.getName({ port: server.address().port });
4445
for (let i = 0; i < max; ++i)
4546
request(i);
4647
}));
4748

4849
const countdown = new Countdown(max, () => {
49-
assert(!(name in http.globalAgent.sockets));
50-
assert(!(name in http.globalAgent.requests));
50+
assert(!(name in agent.sockets));
51+
assert(!(name in agent.requests));
5152
server.close();
5253
});
5354

5455
function request(i) {
5556
const req = http.get({
5657
port: server.address().port,
57-
path: `/${i}`
58+
path: `/${i}`,
59+
agent
5860
}, function(res) {
5961
const socket = req.socket;
6062
socket.on('close', common.mustCall(() => {
6163
countdown.dec();
6264
if (countdown.remaining > 0) {
63-
assert.strictEqual(http.globalAgent.sockets[name].includes(socket),
65+
assert.strictEqual(agent.sockets[name].includes(socket),
6466
false);
6567
}
6668
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const http = require('http');
6+
7+
const server = http.createServer(function(req, res) {
8+
res.writeHead(200);
9+
res.end();
10+
});
11+
12+
server.listen(0, common.mustCall(() => {
13+
const req = http.get({ port: server.address().port }, (res) => {
14+
assert.strictEqual(res.statusCode, 200);
15+
res.resume();
16+
server.close();
17+
});
18+
19+
req.end();
20+
}));
21+
22+
// This timer should never go off as the server will close the socket
23+
setTimeout(common.mustNotCall(), common.platformTimeout(10000)).unref();

test/parallel/test-http-client-headers-array.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ function execute(options) {
1010
const expectHeaders = {
1111
'x-foo': 'boom',
1212
'cookie': 'a=1; b=2; c=3',
13-
'connection': 'close'
13+
'connection': 'keep-alive'
1414
};
1515

1616
// no Host header when you set headers an array
@@ -28,6 +28,7 @@ function execute(options) {
2828

2929
assert.deepStrictEqual(req.headers, expectHeaders);
3030

31+
res.writeHead(200, { 'Connection': 'close' });
3132
res.end();
3233
}).listen(0, function() {
3334
options = Object.assign(options, {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const http = require('http');
6+
7+
const server = http.createServer(
8+
{ keepAliveTimeout: common.platformTimeout(60000) },
9+
function(req, res) {
10+
req.resume();
11+
res.writeHead(200, { 'Connection': 'keep-alive', 'Keep-Alive': 'timeout=1' });
12+
res.end('FOO');
13+
}
14+
);
15+
16+
server.listen(0, common.mustCall(() => {
17+
http.get({ port: server.address().port }, (res) => {
18+
assert.strictEqual(res.statusCode, 200);
19+
20+
res.resume();
21+
server.close();
22+
});
23+
}));
24+
25+
26+
// This timer should never go off as the agent will parse the hint and terminate earlier
27+
setTimeout(common.mustNotCall(), common.platformTimeout(3000)).unref();

test/parallel/test-http-client-spurious-aborted.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const N = 2;
1010
let abortRequest = true;
1111

1212
const server = http.Server(common.mustCall((req, res) => {
13-
const headers = { 'Content-Type': 'text/plain' };
13+
const headers = { 'Content-Type': 'text/plain', 'Connection': 'close' };
1414
headers['Content-Length'] = 50;
1515
const socket = res.socket;
1616
res.writeHead(200, headers);

test/parallel/test-http-client-timeout-on-connect.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ const server = http.createServer((req, res) => {
1313

1414
server.listen(0, common.localhostIPv4, common.mustCall(() => {
1515
const port = server.address().port;
16-
const req = http.get(`http://${common.localhostIPv4}:${port}`);
16+
const req = http.get(
17+
`http://${common.localhostIPv4}:${port}`,
18+
{ agent: new http.Agent() }
19+
);
1720

1821
req.setTimeout(1);
1922
req.on('socket', common.mustCall((socket) => {

test/parallel/test-http-content-length.js

+11-7
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ const http = require('http');
55
const Countdown = require('../common/countdown');
66

77
const expectedHeadersMultipleWrites = {
8-
'connection': 'close',
8+
'connection': 'keep-alive',
99
'transfer-encoding': 'chunked',
1010
};
1111

1212
const expectedHeadersEndWithData = {
13-
'connection': 'close',
14-
'content-length': String('hello world'.length)
13+
'connection': 'keep-alive',
14+
'content-length': String('hello world'.length),
1515
};
1616

1717
const expectedHeadersEndNoData = {
18-
'connection': 'close',
18+
'connection': 'keep-alive',
1919
'content-length': '0',
2020
};
2121

@@ -24,6 +24,7 @@ const countdown = new Countdown(3, () => server.close());
2424

2525
const server = http.createServer(function(req, res) {
2626
res.removeHeader('Date');
27+
res.setHeader('Keep-Alive', 'timeout=1');
2728

2829
switch (req.url.substr(1)) {
2930
case 'multiple-writes':
@@ -59,7 +60,8 @@ server.listen(0, function() {
5960
req.write('hello ');
6061
req.end('world');
6162
req.on('response', function(res) {
62-
assert.deepStrictEqual(res.headers, expectedHeadersMultipleWrites);
63+
assert.deepStrictEqual(res.headers, { ...expectedHeadersMultipleWrites, 'keep-alive': 'timeout=1' });
64+
res.resume();
6365
});
6466

6567
req = http.request({
@@ -71,7 +73,8 @@ server.listen(0, function() {
7173
req.removeHeader('Host');
7274
req.end('hello world');
7375
req.on('response', function(res) {
74-
assert.deepStrictEqual(res.headers, expectedHeadersEndWithData);
76+
assert.deepStrictEqual(res.headers, { ...expectedHeadersEndWithData, 'keep-alive': 'timeout=1' });
77+
res.resume();
7578
});
7679

7780
req = http.request({
@@ -83,7 +86,8 @@ server.listen(0, function() {
8386
req.removeHeader('Host');
8487
req.end();
8588
req.on('response', function(res) {
86-
assert.deepStrictEqual(res.headers, expectedHeadersEndNoData);
89+
assert.deepStrictEqual(res.headers, { ...expectedHeadersEndNoData, 'keep-alive': 'timeout=1' });
90+
res.resume();
8791
});
8892

8993
});

test/parallel/test-http-default-encoding.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ const server = http.Server((req, res) => {
3232
req.on('data', (chunk) => {
3333
result += chunk;
3434
}).on('end', () => {
35-
server.close();
3635
res.writeHead(200);
3736
res.end('hello world\n');
37+
server.close();
3838
});
3939

4040
});

test/parallel/test-http-max-headers-count.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const server = http.createServer(function(req, res) {
4848
expected = maxAndExpected[requests][1];
4949
server.maxHeadersCount = max;
5050
}
51-
res.writeHead(200, headers);
51+
res.writeHead(200, { ...headers, 'Connection': 'close' });
5252
res.end();
5353
});
5454
server.maxHeadersCount = max;

test/parallel/test-http-outgoing-message-capture-rejection.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ events.captureRejections = true;
1616

1717
res.socket.on('error', common.mustCall((err) => {
1818
assert.strictEqual(err, _err);
19+
server.close();
1920
}));
2021

2122
// Write until there is space in the buffer
23+
res.writeHead(200, { 'Connection': 'close' });
2224
while (res.write('hello'));
2325
}));
2426

@@ -37,7 +39,6 @@ events.captureRejections = true;
3739
code: 'ECONNRESET'
3840
}));
3941
res.resume();
40-
server.close();
4142
}));
4243
}));
4344
}

0 commit comments

Comments
 (0)