From 12d68665cfd3a78f2142d8d8aefd8f1d3329019e Mon Sep 17 00:00:00 2001
From: Artur K <fatal10110@gmail.com>
Date: Sun, 12 Sep 2021 16:14:22 +0300
Subject: [PATCH] http: add test, fix write after end

---
 lib/_http_server.js                           | 55 ++++++------
 .../test-http-keep-alive-max-requests.js      | 85 +++++++++++++++++++
 2 files changed, 116 insertions(+), 24 deletions(-)
 create mode 100644 test/parallel/test-http-keep-alive-max-requests.js

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..7e5aaf5d4d1954
--- /dev/null
+++ b/test/parallel/test-http-keep-alive-max-requests.js
@@ -0,0 +1,85 @@
+'use strict';
+
+const net = require('net');
+const http = require('http');
+const assert = require('assert');
+const common = require('../common');
+
+const server = http.createServer(function (req, res) {
+  res.writeHead(200, {'Content-Type': 'text/plain'});
+  res.write('Hello World!');
+  res.end();
+})
+
+function assertResponse(headers, body, expectClosed) {
+  if (expectClosed) {
+    assert.match(responses[4], /Connection: close\r\n/m);
+    assert(responses[4].search(/Keep-Alive: timeout=5, max=3\r\n/m) === -1);
+    assert.match(responses[5], /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('GET / HTTP/1.1\r\n');
+  socket.write('Connection: keep-alive\r\n');
+  socket.write('\r\n\r\n')
+}
+
+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)
+          writeRequest(anotherSocket)
+
+          let anotherBuffer = ''
+
+          anotherSocket.setEncoding('utf8');
+          anotherSocket.on('data', common.mustCall((data) => {
+            anotherBuffer += data;
+          }));
+
+          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)
+            writeRequest(socket)
+
+            let buffer = '';
+            socket.setEncoding('utf8');
+            socket.on('data', common.mustCall((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