From f5337509f06bdc05a156bffc0e8ab59272ecc1f5 Mon Sep 17 00:00:00 2001 From: Artur K Date: Sun, 12 Sep 2021 22:45:21 +0300 Subject: [PATCH] http: Fix 503 on max reached --- lib/_http_outgoing.js | 6 +- lib/_http_server.js | 6 +- .../test-http-keep-alive-max-requests.js | 5 +- ...t-http-keep-alive-pipeline-max-requests.js | 86 +++++++++++++++++++ 4 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 test/parallel/test-http-keep-alive-pipeline-max-requests.js diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index c256e74260673d..557fa90b6107f5 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -113,6 +113,7 @@ function OutgoingMessage() { this._last = false; this.chunkedEncoding = false; this.shouldKeepAlive = true; + this.maxRequestsOnConnectionReached = false; this._defaultKeepAlive = true; this.useChunkedEncodingByDefault = true; this.sendDate = false; @@ -447,7 +448,10 @@ function _storeHeader(firstLine, headers) { } else if (!state.connection) { const shouldSendKeepAlive = this.shouldKeepAlive && (state.contLen || this.useChunkedEncodingByDefault || this.agent); - if (shouldSendKeepAlive) { + + if (shouldSendKeepAlive && this.maxRequestsOnConnectionReached) { + header += 'Connection: close' + CRLF; + } else if (shouldSendKeepAlive) { header += 'Connection: keep-alive' + CRLF; if (this._defaultKeepAlive) { diff --git a/lib/_http_server.js b/lib/_http_server.js index 58d4d99107e534..4a086deda4b18d 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -911,13 +911,13 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) { if (typeof server.maxRequestsPerSocket === 'number') { - state.requestsCount++ - res.shouldKeepAlive = server.maxRequestsPerSocket > state.requestsCount + state.requestsCount++; + res.maxRequestsOnConnectionReached = server.maxRequestsPerSocket <= state.requestsCount } if (typeof server.maxRequestsPerSocket === 'number' && (server.maxRequestsPerSocket < state.requestsCount)) { - handled = true + handled = true; res.writeHead(503); res.end(); diff --git a/test/parallel/test-http-keep-alive-max-requests.js b/test/parallel/test-http-keep-alive-max-requests.js index 9ae30480b9da36..6bacc1a68a7356 100644 --- a/test/parallel/test-http-keep-alive-max-requests.js +++ b/test/parallel/test-http-keep-alive-max-requests.js @@ -4,7 +4,6 @@ const net = require('net'); const http = require('http'); const assert = require('assert'); const common = require('../common'); -const { mustCall } = require('../common'); const bodySent = 'This is my request'; @@ -79,12 +78,12 @@ server.listen(0, common.mustCall((res) => { const socket = new net.Socket(); const anotherSocket = new net.Socket(); - socket.on('end', mustCall(() => { + socket.on('end', common.mustCall(() => { server.close(); })); socket.on('ready', common.mustCall(() => { - // Do two of 3 requests and ensure they still alive + // Do 2 of 3 allowed requests and ensure they still alive initialRequests(socket, 2, common.mustCall(() => { anotherSocket.connect({ port: server.address().port }); })) diff --git a/test/parallel/test-http-keep-alive-pipeline-max-requests.js b/test/parallel/test-http-keep-alive-pipeline-max-requests.js new file mode 100644 index 00000000000000..7f3ba7bf5f22b5 --- /dev/null +++ b/test/parallel/test-http-keep-alive-pipeline-max-requests.js @@ -0,0 +1,86 @@ +'use strict'; + +const net = require('net'); +const http = require('http'); +const assert = require('assert'); +const common = require('../common'); + +const bodySent = 'This is my request'; + +function assertResponse(headers, body, expectClosed) { + if (expectClosed) { + assert.match(headers, /Connection: close\r\n/m); + assert(headers.search(/Keep-Alive: timeout=5, max=3\r\n/m) === -1); + assert.match(body, /Hello World!/m); + } else { + assert.match(headers, /Connection: keep-alive\r\n/m); + assert.match(headers, /Keep-Alive: timeout=5, max=3\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(function (req, res) { + let body = '' + req.on('data', (data) => { + body += data + }); + + req.on('end', () => { + if (req.method === 'POST') { + assert(bodySent === body) + } + + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.write('Hello World!'); + res.end(); + }) +}) + +server.maxRequestsPerSocket = 3; + +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], true); + + assert.match(responseParts[6], /HTTP\/1.1 503 Service Unavailable/m) + assert.match(responseParts[6], /Connection: close\r\n/m); + assert(responseParts[6].search(/Keep-Alive: timeout=5, max=3\r\n/m) === -1); + assert(responseParts[7].search(/Hello World!/m) === -1); + + socket.end(); + } + }) + + socket.connect({ port: server.address().port }); +})); \ No newline at end of file