diff --git a/lib/_http_server.js b/lib/_http_server.js index 2c06e564fc2f21..58d4d99107e534 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -907,39 +907,46 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { resOnFinish.bind(undefined, req, res, socket, state, server)); - if (typeof server.maxRequestsPerSocket === 'number' - && (req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) { + let handled = false; - if (server.maxRequestsPerSocket < ++state.requestsCount) { - res.writeHead(503); - res.end(); + if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) { + if (typeof server.maxRequestsPerSocket === 'number') { + state.requestsCount++ + res.shouldKeepAlive = server.maxRequestsPerSocket > state.requestsCount } - res.shouldKeepAlive = server.maxRequestsPerSocket > state.requestsCount - } - - if (req.headers.expect !== undefined && - (req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) { - if (RegExpPrototypeTest(continueExpression, req.headers.expect)) { - res._expect_continue = true; - - if (server.listenerCount('checkContinue') > 0) { - server.emit('checkContinue', req, res); + if (typeof server.maxRequestsPerSocket === 'number' + && (server.maxRequestsPerSocket < state.requestsCount)) { + handled = true + + res.writeHead(503); + res.end(); + } else if (req.headers.expect !== undefined) { + handled = true + + if (RegExpPrototypeTest(continueExpression, req.headers.expect)) { + res._expect_continue = true; + + if (server.listenerCount('checkContinue') > 0) { + server.emit('checkContinue', req, res); + } else { + res.writeContinue(); + server.emit('request', req, res); + } + } else if (server.listenerCount('checkExpectation') > 0) { + server.emit('checkExpectation', req, res); } else { - res.writeContinue(); - server.emit('request', req, res); + res.writeHead(417); + res.end(); } - } else if (server.listenerCount('checkExpectation') > 0) { - server.emit('checkExpectation', req, res); - } else { - res.writeHead(417); - res.end(); } - } else { - req.on('end', clearRequestTimeout); + } + if(!handled) { + req.on('end', clearRequestTimeout); server.emit('request', req, res); } + return 0; // No special treatment. } diff --git a/test/parallel/test-http-keep-alive-max-requests.js b/test/parallel/test-http-keep-alive-max-requests.js new file mode 100644 index 00000000000000..a06161b94b61b8 --- /dev/null +++ b/test/parallel/test-http-keep-alive-max-requests.js @@ -0,0 +1,114 @@ +'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, withBody) { + if (withBody) { + 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') + } else { + socket.write('GET / HTTP/1.1\r\n'); + socket.write('Connection: keep-alive\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 = net.createConnection( + { port: server.address().port }, + common.mustCall(() => { + writeRequest(socket) + writeRequest(socket) + + const anotherSocket = net.createConnection( + { port: server.address().port }, + common.mustCall(() => { + writeRequest(anotherSocket) + + let anotherBuffer = '' + let lastWritten = false; + anotherSocket.setEncoding('utf8'); + anotherSocket.on('data', (data) => { + anotherBuffer += data; + + if (anotherBuffer.endsWith('\r\n\r\n')) { + if (lastWritten) { + anotherSocket.end() + } else { + writeRequest(anotherSocket); + lastWritten = true; + } + } + }); + + anotherSocket.on('end', common.mustCall(() => { + const anoterResponses = anotherBuffer.trim().split('\r\n\r\n'); + + assertResponse(anoterResponses[0], anoterResponses[1], false) + assertResponse(anoterResponses[2], anoterResponses[3], false) + + // Add two additional requests to two previous on the first socket + writeRequest(socket, true) + writeRequest(socket, true) + + let buffer = ''; + socket.setEncoding('utf8'); + socket.on('data', (data) => { + buffer += data; + }); + + socket.on('end', common.mustCall(() => { + const responses = buffer.trim().split('\r\n\r\n'); + // We sent more requests than allowed per socket, + // but we get only the allowed number of responses & headers + assert(responses.length === server.maxRequestsPerSocket * 2); + + assertResponse(responses[0], responses[1], false) + assertResponse(responses[2], responses[3], false) + assertResponse(responses[4], responses[5], true) + + server.close(); + })); + })); + })); + }) + ); +})); \ No newline at end of file