diff --git a/doc/api/http.md b/doc/api/http.md index ec4392745e1aca..e4bf379a97ed28 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -1357,12 +1357,12 @@ explicitly. added: v16.10.0 --> -* {number} Requests per socket. **Default:** null (no limit) +* {number} Requests per socket. **Default:** 0 (no limit) The maximum number of requests socket can handle before closing keep alive connection. -A value of `null` will disable the limit. +A value of `0` will disable the limit. When the limit is reached it will set the `Connection` header value to `close`, but will not actually close the connection, subsequent requests sent diff --git a/lib/_http_server.js b/lib/_http_server.js index 2221b132f4d29c..045e0b2f719717 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -394,7 +394,7 @@ function Server(options, requestListener) { this.timeout = 0; this.keepAliveTimeout = 5000; this.maxHeadersCount = null; - this.maxRequestsPerSocket = null; + this.maxRequestsPerSocket = 0; this.headersTimeout = 60 * 1000; // 60 seconds this.requestTimeout = 0; } @@ -908,13 +908,18 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { let handled = false; if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) { - if (typeof server.maxRequestsPerSocket === 'number') { + const isRequestsLimitSet = ( + typeof server.maxRequestsPerSocket === 'number' && + server.maxRequestsPerSocket > 0 + ); + + if (isRequestsLimitSet) { state.requestsCount++; res.maxRequestsOnConnectionReached = ( server.maxRequestsPerSocket <= state.requestsCount); } - if (typeof server.maxRequestsPerSocket === 'number' && + if (isRequestsLimitSet && (server.maxRequestsPerSocket < state.requestsCount)) { handled = true; diff --git a/test/parallel/test-http-server-keep-alive-defaults.js b/test/parallel/test-http-server-keep-alive-defaults.js new file mode 100644 index 00000000000000..6f3abc30b573a2 --- /dev/null +++ b/test/parallel/test-http-server-keep-alive-defaults.js @@ -0,0 +1,76 @@ +'use strict'; + +const common = require('../common'); +const net = require('net'); +const http = require('http'); +const assert = require('assert'); + +const bodySent = 'This is my request'; + +function assertResponse(headers, body, expectClosed) { + assert.match(headers, /Connection: keep-alive\r\n/m); + assert.match(headers, /Keep-Alive: timeout=5\r\n/m); + assert.match(body, /Hello World!/m); +} + +function writeRequest(socket) { + socket.write('POST / HTTP/1.1\r\n'); + socket.write('Connection: keep-alive\r\n'); + socket.write('Content-Type: text/plain\r\n'); + socket.write(`Content-Length: ${bodySent.length}\r\n\r\n`); + socket.write(`${bodySent}\r\n`); + socket.write('\r\n\r\n'); +} + +const server = http.createServer((req, res) => { + let body = ''; + req.on('data', (data) => { + body += data; + }); + + req.on('end', () => { + if (req.method === 'POST') { + assert.strictEqual(bodySent, body); + } + + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('Hello World!'); + res.end(); + }); +}); + +server.listen(0, common.mustCall((res) => { + assert.strictEqual(server.maxRequestsPerSocket, 0); + + const socket = new net.Socket(); + + socket.on('end', common.mustCall(() => { + server.close(); + })); + + socket.on('ready', common.mustCall(() => { + writeRequest(socket); + writeRequest(socket); + writeRequest(socket); + writeRequest(socket); + })); + + let buffer = ''; + + socket.on('data', (data) => { + buffer += data; + + const responseParts = buffer.trim().split('\r\n\r\n'); + + if (responseParts.length === 8) { + assertResponse(responseParts[0], responseParts[1]); + assertResponse(responseParts[2], responseParts[3]); + assertResponse(responseParts[4], responseParts[5]); + assertResponse(responseParts[6], responseParts[7]); + + socket.end(); + } + }); + + socket.connect({ port: server.address().port }); +})); diff --git a/test/parallel/test-http-server-keep-alive-max-requests-null.js b/test/parallel/test-http-server-keep-alive-max-requests-null.js new file mode 100644 index 00000000000000..fee882be856290 --- /dev/null +++ b/test/parallel/test-http-server-keep-alive-max-requests-null.js @@ -0,0 +1,75 @@ +'use strict'; + +const common = require('../common'); +const net = require('net'); +const http = require('http'); +const assert = require('assert'); + +const bodySent = 'This is my request'; + +function assertResponse(headers, body, expectClosed) { + assert.match(headers, /Connection: keep-alive\r\n/m); + assert.match(headers, /Keep-Alive: timeout=5\r\n/m); + assert.match(body, /Hello World!/m); +} + +function writeRequest(socket) { + socket.write('POST / HTTP/1.1\r\n'); + socket.write('Connection: keep-alive\r\n'); + socket.write('Content-Type: text/plain\r\n'); + socket.write(`Content-Length: ${bodySent.length}\r\n\r\n`); + socket.write(`${bodySent}\r\n`); + socket.write('\r\n\r\n'); +} + +const server = http.createServer((req, res) => { + let body = ''; + req.on('data', (data) => { + body += data; + }); + + req.on('end', () => { + if (req.method === 'POST') { + assert.strictEqual(bodySent, body); + } + + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('Hello World!'); + res.end(); + }); +}); + +server.maxRequestsPerSocket = null; +server.listen(0, common.mustCall((res) => { + const socket = new net.Socket(); + + socket.on('end', common.mustCall(() => { + server.close(); + })); + + socket.on('ready', common.mustCall(() => { + writeRequest(socket); + writeRequest(socket); + writeRequest(socket); + writeRequest(socket); + })); + + let buffer = ''; + + socket.on('data', (data) => { + buffer += data; + + const responseParts = buffer.trim().split('\r\n\r\n'); + + if (responseParts.length === 8) { + assertResponse(responseParts[0], responseParts[1]); + assertResponse(responseParts[2], responseParts[3]); + assertResponse(responseParts[4], responseParts[5]); + assertResponse(responseParts[6], responseParts[7]); + + socket.end(); + } + }); + + socket.connect({ port: server.address().port }); +}));