Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a63a4bc

Browse filesBrowse files
fatal10110BethGriggs
authored andcommitted
http: limit requests per connection
Fixes: #40071 PR-URL: #40082 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com>
1 parent 590ace4 commit a63a4bc
Copy full SHA for a63a4bc

File tree

Expand file treeCollapse file tree

5 files changed

+258
-16
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+258
-16
lines changed
Open diff view settings
Collapse file

‎doc/api/http.md‎

Copy file name to clipboardExpand all lines: doc/api/http.md
+16Lines changed: 16 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,22 @@ By default, the Server does not timeout sockets. However, if a callback
13481348
is assigned to the Server's `'timeout'` event, timeouts must be handled
13491349
explicitly.
13501350

1351+
### `server.maxRequestsPerSocket`
1352+
<!-- YAML
1353+
added: REPLACEME
1354+
-->
1355+
1356+
* {number} Requests per socket. **Default:** null (no limit)
1357+
1358+
The maximum number of requests socket can handle
1359+
before closing keep alive connection.
1360+
1361+
A value of `null` will disable the limit.
1362+
1363+
When limit is reach it will set `Connection` header value to `closed`,
1364+
but will not actually close the connection, subsequent requests sent
1365+
after the limit is reached will get `503 Service Unavailable` as a response.
1366+
13511367
### `server.timeout`
13521368
<!-- YAML
13531369
added: v0.9.12
Collapse file

‎lib/_http_outgoing.js‎

Copy file name to clipboardExpand all lines: lib/_http_outgoing.js
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ function OutgoingMessage() {
113113
this._last = false;
114114
this.chunkedEncoding = false;
115115
this.shouldKeepAlive = true;
116+
this.maxRequestsOnConnectionReached = false;
116117
this._defaultKeepAlive = true;
117118
this.useChunkedEncodingByDefault = true;
118119
this.sendDate = false;
@@ -446,7 +447,9 @@ function _storeHeader(firstLine, headers) {
446447
} else if (!state.connection) {
447448
const shouldSendKeepAlive = this.shouldKeepAlive &&
448449
(state.contLen || this.useChunkedEncodingByDefault || this.agent);
449-
if (shouldSendKeepAlive) {
450+
if (shouldSendKeepAlive && this.maxRequestsOnConnectionReached) {
451+
header += 'Connection: close\r\n';
452+
} else if (shouldSendKeepAlive) {
450453
header += 'Connection: keep-alive\r\n';
451454
if (this._keepAliveTimeout && this._defaultKeepAlive) {
452455
const timeoutSeconds = MathFloor(this._keepAliveTimeout / 1000);
Collapse file

‎lib/_http_server.js‎

Copy file name to clipboardExpand all lines: lib/_http_server.js
+36-15Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ function Server(options, requestListener) {
396396
this.timeout = 0;
397397
this.keepAliveTimeout = 5000;
398398
this.maxHeadersCount = null;
399+
this.maxRequestsPerSocket = null;
399400
this.headersTimeout = 60 * 1000; // 60 seconds
400401
this.requestTimeout = 0;
401402
}
@@ -487,6 +488,7 @@ function connectionListenerInternal(server, socket) {
487488
// need to pause TCP socket/HTTP parser, and wait until the data will be
488489
// sent to the client.
489490
outgoingData: 0,
491+
requestsCount: 0,
490492
keepAliveTimeoutSet: false
491493
};
492494
state.onData = socketOnData.bind(undefined,
@@ -903,28 +905,47 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
903905
resOnFinish.bind(undefined,
904906
req, res, socket, state, server));
905907

906-
if (req.headers.expect !== undefined &&
907-
(req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) {
908-
if (RegExpPrototypeTest(continueExpression, req.headers.expect)) {
909-
res._expect_continue = true;
908+
let handled = false;
910909

911-
if (server.listenerCount('checkContinue') > 0) {
912-
server.emit('checkContinue', req, res);
910+
if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) {
911+
if (typeof server.maxRequestsPerSocket === 'number') {
912+
state.requestsCount++;
913+
res.maxRequestsOnConnectionReached = (
914+
server.maxRequestsPerSocket <= state.requestsCount);
915+
}
916+
917+
if (typeof server.maxRequestsPerSocket === 'number' &&
918+
(server.maxRequestsPerSocket < state.requestsCount)) {
919+
handled = true;
920+
921+
res.writeHead(503);
922+
res.end();
923+
} else if (req.headers.expect !== undefined) {
924+
handled = true;
925+
926+
if (RegExpPrototypeTest(continueExpression, req.headers.expect)) {
927+
res._expect_continue = true;
928+
929+
if (server.listenerCount('checkContinue') > 0) {
930+
server.emit('checkContinue', req, res);
931+
} else {
932+
res.writeContinue();
933+
server.emit('request', req, res);
934+
}
935+
} else if (server.listenerCount('checkExpectation') > 0) {
936+
server.emit('checkExpectation', req, res);
913937
} else {
914-
res.writeContinue();
915-
server.emit('request', req, res);
938+
res.writeHead(417);
939+
res.end();
916940
}
917-
} else if (server.listenerCount('checkExpectation') > 0) {
918-
server.emit('checkExpectation', req, res);
919-
} else {
920-
res.writeHead(417);
921-
res.end();
922941
}
923-
} else {
924-
req.on('end', clearRequestTimeout);
942+
}
925943

944+
if (!handled) {
945+
req.on('end', clearRequestTimeout);
926946
server.emit('request', req, res);
927947
}
948+
928949
return 0; // No special treatment.
929950
}
930951

Collapse file
+116Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const net = require('net');
5+
const http = require('http');
6+
const assert = require('assert');
7+
8+
const bodySent = 'This is my request';
9+
10+
function assertResponse(headers, body, expectClosed) {
11+
if (expectClosed) {
12+
assert.match(headers, /Connection: close\r\n/m);
13+
assert.strictEqual(headers.search(/Keep-Alive: timeout=5\r\n/m), -1);
14+
assert.match(body, /Hello World!/m);
15+
} else {
16+
assert.match(headers, /Connection: keep-alive\r\n/m);
17+
assert.match(headers, /Keep-Alive: timeout=5\r\n/m);
18+
assert.match(body, /Hello World!/m);
19+
}
20+
}
21+
22+
function writeRequest(socket, withBody) {
23+
if (withBody) {
24+
socket.write('POST / HTTP/1.1\r\n');
25+
socket.write('Connection: keep-alive\r\n');
26+
socket.write('Content-Type: text/plain\r\n');
27+
socket.write(`Content-Length: ${bodySent.length}\r\n\r\n`);
28+
socket.write(`${bodySent}\r\n`);
29+
socket.write('\r\n\r\n');
30+
} else {
31+
socket.write('GET / HTTP/1.1\r\n');
32+
socket.write('Connection: keep-alive\r\n');
33+
socket.write('\r\n\r\n');
34+
}
35+
}
36+
37+
const server = http.createServer((req, res) => {
38+
let body = '';
39+
req.on('data', (data) => {
40+
body += data;
41+
});
42+
43+
req.on('end', () => {
44+
if (req.method === 'POST') {
45+
assert.strictEqual(bodySent, body);
46+
}
47+
res.writeHead(200, { 'Content-Type': 'text/plain' });
48+
res.write('Hello World!');
49+
res.end();
50+
});
51+
});
52+
53+
function initialRequests(socket, numberOfRequests, cb) {
54+
let buffer = '';
55+
56+
writeRequest(socket);
57+
58+
socket.on('data', (data) => {
59+
buffer += data;
60+
61+
if (buffer.endsWith('\r\n\r\n')) {
62+
if (--numberOfRequests === 0) {
63+
socket.removeAllListeners('data');
64+
cb();
65+
} else {
66+
const [headers, body] = buffer.trim().split('\r\n\r\n');
67+
assertResponse(headers, body);
68+
buffer = '';
69+
writeRequest(socket, true);
70+
}
71+
}
72+
});
73+
}
74+
75+
76+
server.maxRequestsPerSocket = 3;
77+
server.listen(0, common.mustCall((res) => {
78+
const socket = new net.Socket();
79+
const anotherSocket = new net.Socket();
80+
81+
socket.on('end', common.mustCall(() => {
82+
server.close();
83+
}));
84+
85+
socket.on('ready', common.mustCall(() => {
86+
// Do 2 of 3 allowed requests and ensure they still alive
87+
initialRequests(socket, 2, common.mustCall(() => {
88+
anotherSocket.connect({ port: server.address().port });
89+
}));
90+
}));
91+
92+
anotherSocket.on('ready', common.mustCall(() => {
93+
// Do another 2 requests with another socket
94+
// enusre that this will not affect the first socket
95+
initialRequests(anotherSocket, 2, common.mustCall(() => {
96+
let buffer = '';
97+
98+
// Send the rest of the calls to the first socket
99+
// and see connection is closed
100+
socket.on('data', common.mustCall((data) => {
101+
buffer += data;
102+
103+
if (buffer.endsWith('\r\n\r\n')) {
104+
const [headers, body] = buffer.trim().split('\r\n\r\n');
105+
assertResponse(headers, body, true);
106+
anotherSocket.end();
107+
socket.end();
108+
}
109+
}));
110+
111+
writeRequest(socket, true);
112+
}));
113+
}));
114+
115+
socket.connect({ port: server.address().port });
116+
}));
Collapse file
+86Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const net = require('net');
5+
const http = require('http');
6+
const assert = require('assert');
7+
8+
const bodySent = 'This is my request';
9+
10+
function assertResponse(headers, body, expectClosed) {
11+
if (expectClosed) {
12+
assert.match(headers, /Connection: close\r\n/m);
13+
assert.strictEqual(headers.search(/Keep-Alive: timeout=5\r\n/m), -1);
14+
assert.match(body, /Hello World!/m);
15+
} else {
16+
assert.match(headers, /Connection: keep-alive\r\n/m);
17+
assert.match(headers, /Keep-Alive: timeout=5\r\n/m);
18+
assert.match(body, /Hello World!/m);
19+
}
20+
}
21+
22+
function writeRequest(socket) {
23+
socket.write('POST / HTTP/1.1\r\n');
24+
socket.write('Connection: keep-alive\r\n');
25+
socket.write('Content-Type: text/plain\r\n');
26+
socket.write(`Content-Length: ${bodySent.length}\r\n\r\n`);
27+
socket.write(`${bodySent}\r\n`);
28+
socket.write('\r\n\r\n');
29+
}
30+
31+
const server = http.createServer((req, res) => {
32+
let body = '';
33+
req.on('data', (data) => {
34+
body += data;
35+
});
36+
37+
req.on('end', () => {
38+
if (req.method === 'POST') {
39+
assert.strictEqual(bodySent, body);
40+
}
41+
42+
res.writeHead(200, { 'Content-Type': 'text/plain' });
43+
res.write('Hello World!');
44+
res.end();
45+
});
46+
});
47+
48+
server.maxRequestsPerSocket = 3;
49+
50+
server.listen(0, common.mustCall((res) => {
51+
const socket = new net.Socket();
52+
53+
socket.on('end', common.mustCall(() => {
54+
server.close();
55+
}));
56+
57+
socket.on('ready', common.mustCall(() => {
58+
writeRequest(socket);
59+
writeRequest(socket);
60+
writeRequest(socket);
61+
writeRequest(socket);
62+
}));
63+
64+
let buffer = '';
65+
66+
socket.on('data', (data) => {
67+
buffer += data;
68+
69+
const responseParts = buffer.trim().split('\r\n\r\n');
70+
71+
if (responseParts.length === 8) {
72+
assertResponse(responseParts[0], responseParts[1]);
73+
assertResponse(responseParts[2], responseParts[3]);
74+
assertResponse(responseParts[4], responseParts[5], true);
75+
76+
assert.match(responseParts[6], /HTTP\/1\.1 503 Service Unavailable/m);
77+
assert.match(responseParts[6], /Connection: close\r\n/m);
78+
assert.strictEqual(responseParts[6].search(/Keep-Alive: timeout=5\r\n/m), -1);
79+
assert.strictEqual(responseParts[7].search(/Hello World!/m), -1);
80+
81+
socket.end();
82+
}
83+
});
84+
85+
socket.connect({ port: server.address().port });
86+
}));

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.