From 0ab2852470416954d5ce6c82617f1657ea0a3c60 Mon Sep 17 00:00:00 2001
From: Artur K <fatal10110@gmail.com>
Date: Sat, 11 Sep 2021 14:55:09 +0300
Subject: [PATCH 1/9] http: limit requests per connection

---
 lib/_http_outgoing.js |  1 +
 lib/_http_server.js   | 13 +++++++++++++
 2 files changed, 14 insertions(+)

diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js
index 25fe8fb1ede73f..32d52ccce303dc 100644
--- a/lib/_http_outgoing.js
+++ b/lib/_http_outgoing.js
@@ -134,6 +134,7 @@ function OutgoingMessage() {
   this._header = null;
   this[kOutHeaders] = null;
 
+  this._maxRequestsPerSocket = null;
   this._keepAliveTimeout = 0;
 
   this._onPendingData = nop;
diff --git a/lib/_http_server.js b/lib/_http_server.js
index bafd615a0fdc60..e66d8e6df73648 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -394,6 +394,7 @@ function Server(options, requestListener) {
   this.timeout = 0;
   this.keepAliveTimeout = 5000;
   this.maxHeadersCount = null;
+  this.maxRequestsPerSocket = null;
   this.headersTimeout = 60 * 1000; // 60 seconds
   this.requestTimeout = 0;
 }
@@ -485,6 +486,7 @@ function connectionListenerInternal(server, socket) {
     // need to pause TCP socket/HTTP parser, and wait until the data will be
     // sent to the client.
     outgoingData: 0,
+    requestsCount: 0,
     keepAliveTimeoutSet: false
   };
   state.onData = socketOnData.bind(undefined,
@@ -875,6 +877,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
 
   const res = new server[kServerResponse](req);
   res._keepAliveTimeout = server.keepAliveTimeout;
+  res._maxRequestsPerSocket = server.maxRequestsPerSocket;
   res._onPendingData = updateOutgoingData.bind(undefined,
                                                socket, state);
 
@@ -903,6 +906,16 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
          resOnFinish.bind(undefined,
                           req, res, socket, state, server));
 
+  if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1
+    && typeof server.maxRequestsPerSocket === 'number'
+    && server.maxRequestsPerSocket > ++state.requestsCount) {
+      res.shouldKeepAlive = false;
+      res.writeHead(503, {
+        'Connection': 'close'
+      });
+      res.end();
+  }
+
   if (req.headers.expect !== undefined &&
       (req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) {
     if (RegExpPrototypeTest(continueExpression, req.headers.expect)) {

From dc611ee3a6a23f3779fe388a8582322d7df1047d Mon Sep 17 00:00:00 2001
From: Artur K <fatal10110@gmail.com>
Date: Sat, 11 Sep 2021 16:44:56 +0300
Subject: [PATCH 2/9] http: fix condition

---
 lib/_http_server.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/_http_server.js b/lib/_http_server.js
index e66d8e6df73648..0c2e8b0dd5edc7 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -908,7 +908,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
 
   if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1
     && typeof server.maxRequestsPerSocket === 'number'
-    && server.maxRequestsPerSocket > ++state.requestsCount) {
+    && server.maxRequestsPerSocket < ++state.requestsCount) {
       res.shouldKeepAlive = false;
       res.writeHead(503, {
         'Connection': 'close'

From 8f55f74667be144e8e2b63db60231f895c804abe Mon Sep 17 00:00:00 2001
From: Artur K <fatal10110@gmail.com>
Date: Sat, 11 Sep 2021 17:07:01 +0300
Subject: [PATCH 3/9] http: close on last request

---
 lib/_http_server.js | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/lib/_http_server.js b/lib/_http_server.js
index 0c2e8b0dd5edc7..355119b8412a86 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -906,14 +906,15 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
          resOnFinish.bind(undefined,
                           req, res, socket, state, server));
 
-  if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1
-    && typeof server.maxRequestsPerSocket === 'number'
-    && server.maxRequestsPerSocket < ++state.requestsCount) {
-      res.shouldKeepAlive = false;
-      res.writeHead(503, {
-        'Connection': 'close'
-      });
+  if (typeof server.maxRequestsPerSocket === 'number'
+    && (req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) {
+
+    if (server.maxRequestsPerSocket < ++state.requestsCount) {
+      res.writeHead(503);
       res.end();
+    }
+
+    res.shouldKeepAlive = server.maxRequestsPerSocket > state.requestsCount
   }
 
   if (req.headers.expect !== undefined &&

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

---
 lib/_http_server.js                           |  55 +++++----
 .../test-http-keep-alive-max-requests.js      | 114 ++++++++++++++++++
 2 files changed, 145 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 355119b8412a86..1dd62942245425 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -906,39 +906,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

From 719eba93c23ca8d0fc6540d15e8840d7e3c398e4 Mon Sep 17 00:00:00 2001
From: Artur K <fatal10110@gmail.com>
Date: Sun, 12 Sep 2021 22:01:05 +0300
Subject: [PATCH 5/9] http: refactor test

---
 .../test-http-keep-alive-max-requests.js      | 117 +++++++++---------
 1 file changed, 58 insertions(+), 59 deletions(-)

diff --git a/test/parallel/test-http-keep-alive-max-requests.js b/test/parallel/test-http-keep-alive-max-requests.js
index a06161b94b61b8..9ae30480b9da36 100644
--- a/test/parallel/test-http-keep-alive-max-requests.js
+++ b/test/parallel/test-http-keep-alive-max-requests.js
@@ -4,6 +4,7 @@ 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';
 
@@ -50,65 +51,63 @@ const server = http.createServer(function (req, res) {
   })
 })
 
+function initialRequests(socket, numberOfRequests, cb) {
+  let buffer = '';
+
+  writeRequest(socket)
+
+  socket.on('data', (data) => {
+    buffer += data;
+
+    if (buffer.endsWith('\r\n\r\n')) {
+      if (--numberOfRequests === 0) {
+        socket.removeAllListeners('data');
+        cb();
+      } else {
+        const [headers, body] = buffer.trim().split('\r\n\r\n');
+        assertResponse(headers, body)
+        buffer = '';
+        writeRequest(socket, true)
+      }
+    }
+  });
+}
+
+
 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();
-            }));
-          }));
-        }));
-    })
-  );
+  const socket = new net.Socket();
+  const anotherSocket = new net.Socket();
+
+  socket.on('end', mustCall(() => {
+    server.close();
+  }));
+
+  socket.on('ready', common.mustCall(() => {
+    // Do two of 3 requests and ensure they still alive
+    initialRequests(socket, 2, common.mustCall(() => {
+        anotherSocket.connect({ port: server.address().port });
+    }))
+  }));
+
+  anotherSocket.on('ready', common.mustCall(() => {
+    // Do another 2 requests with another socket, enusre that this will not affect the first socket
+    initialRequests(anotherSocket, 2, common.mustCall(() => {
+      let buffer = '';
+
+      // Send the rest of the calls to the first socket and see connection is closed
+      socket.on('data', common.mustCall((data) => {
+        buffer += data;
+
+        if (buffer.endsWith('\r\n\r\n')) {
+          const [headers, body] = buffer.trim().split('\r\n\r\n');
+          assertResponse(headers, body, true);
+        }
+      }));
+
+      writeRequest(socket, true);
+    }));
+  }));
+
+  socket.connect({ port: server.address().port });
 }));
\ No newline at end of file

From b1d70f652facd4896849203bf50a6d07432e83b1 Mon Sep 17 00:00:00 2001
From: Artur K <fatal10110@gmail.com>
Date: Sun, 12 Sep 2021 22:45:21 +0300
Subject: [PATCH 6/9] http: fix 503 on max reached

---
 lib/_http_outgoing.js                         |  1 +
 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, 92 insertions(+), 6 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 32d52ccce303dc..ecf46143466d37 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;
diff --git a/lib/_http_server.js b/lib/_http_server.js
index 1dd62942245425..839c8bd2d93128 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -910,13 +910,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

From 729fd5ab45f55fa20566c3a04ea41c02ec3d147d Mon Sep 17 00:00:00 2001
From: Artur K <fatal10110@gmail.com>
Date: Mon, 13 Sep 2021 00:26:30 +0300
Subject: [PATCH 7/9] http: fix lint

---
 lib/_http_server.js                           | 21 +++++-----
 .../test-http-keep-alive-max-requests.js      | 42 ++++++++++---------
 ...t-http-keep-alive-pipeline-max-requests.js | 34 +++++++--------
 3 files changed, 51 insertions(+), 46 deletions(-)

diff --git a/lib/_http_server.js b/lib/_http_server.js
index 839c8bd2d93128..758bb8c9267e1e 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -911,21 +911,22 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
   if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) {
     if (typeof server.maxRequestsPerSocket === 'number') {
       state.requestsCount++;
-      res.maxRequestsOnConnectionReached = server.maxRequestsPerSocket <= state.requestsCount
+      res.maxRequestsOnConnectionReached = (
+        server.maxRequestsPerSocket <= state.requestsCount);
     }
 
-    if (typeof server.maxRequestsPerSocket === 'number' 
-      && (server.maxRequestsPerSocket < state.requestsCount)) {
-        handled = true;
+    if (typeof server.maxRequestsPerSocket === 'number' &&
+      (server.maxRequestsPerSocket < state.requestsCount)) {
+      handled = true;
 
-        res.writeHead(503);
-        res.end();
+      res.writeHead(503);
+      res.end();
     } else if (req.headers.expect !== undefined) {
-      handled = true
-  
+      handled = true;
+
       if (RegExpPrototypeTest(continueExpression, req.headers.expect)) {
         res._expect_continue = true;
-  
+
         if (server.listenerCount('checkContinue') > 0) {
           server.emit('checkContinue', req, res);
         } else {
@@ -941,7 +942,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
     }
   }
 
-  if(!handled) {
+  if (!handled) {
     req.on('end', clearRequestTimeout);
     server.emit('request', req, res);
   }
diff --git a/test/parallel/test-http-keep-alive-max-requests.js b/test/parallel/test-http-keep-alive-max-requests.js
index 6bacc1a68a7356..d1fbc6a7fd0842 100644
--- a/test/parallel/test-http-keep-alive-max-requests.js
+++ b/test/parallel/test-http-keep-alive-max-requests.js
@@ -1,16 +1,16 @@
 'use strict';
 
+const common = require('../common');
 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.strictEqual(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);
@@ -26,34 +26,34 @@ function writeRequest(socket, withBody) {
     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')
+    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')
+    socket.write('\r\n\r\n');
   }
 }
 
-const server = http.createServer(function (req, res) {
-  let body = ''
+const server = http.createServer((req, res) => {
+  let body = '';
   req.on('data', (data) => {
-    body += data
+    body += data;
   });
 
   req.on('end', () => {
     if (req.method === 'POST') {
-      assert(bodySent === body)
+      assert.strictEqual(bodySent, body);
     }
-    res.writeHead(200, {'Content-Type': 'text/plain'});
+    res.writeHead(200, { 'Content-Type': 'text/plain' });
     res.write('Hello World!');
     res.end();
-  })
-})
+  });
+});
 
 function initialRequests(socket, numberOfRequests, cb) {
   let buffer = '';
 
-  writeRequest(socket)
+  writeRequest(socket);
 
   socket.on('data', (data) => {
     buffer += data;
@@ -64,9 +64,9 @@ function initialRequests(socket, numberOfRequests, cb) {
         cb();
       } else {
         const [headers, body] = buffer.trim().split('\r\n\r\n');
-        assertResponse(headers, body)
+        assertResponse(headers, body);
         buffer = '';
-        writeRequest(socket, true)
+        writeRequest(socket, true);
       }
     }
   });
@@ -85,22 +85,26 @@ server.listen(0, common.mustCall((res) => {
   socket.on('ready', common.mustCall(() => {
     // Do 2 of 3 allowed requests and ensure they still alive
     initialRequests(socket, 2, common.mustCall(() => {
-        anotherSocket.connect({ port: server.address().port });
-    }))
+      anotherSocket.connect({ port: server.address().port });
+    }));
   }));
 
   anotherSocket.on('ready', common.mustCall(() => {
-    // Do another 2 requests with another socket, enusre that this will not affect the first socket
+    // Do another 2 requests with another socket
+    // enusre that this will not affect the first socket
     initialRequests(anotherSocket, 2, common.mustCall(() => {
       let buffer = '';
 
-      // Send the rest of the calls to the first socket and see connection is closed
+      // Send the rest of the calls to the first socket
+      // and see connection is closed
       socket.on('data', common.mustCall((data) => {
         buffer += data;
 
         if (buffer.endsWith('\r\n\r\n')) {
           const [headers, body] = buffer.trim().split('\r\n\r\n');
           assertResponse(headers, body, true);
+          anotherSocket.end();
+          socket.end();
         }
       }));
 
@@ -109,4 +113,4 @@ server.listen(0, common.mustCall((res) => {
   }));
 
   socket.connect({ port: server.address().port });
-}));
\ No newline at end of file
+}));
diff --git a/test/parallel/test-http-keep-alive-pipeline-max-requests.js b/test/parallel/test-http-keep-alive-pipeline-max-requests.js
index 7f3ba7bf5f22b5..0de00b7a04910b 100644
--- a/test/parallel/test-http-keep-alive-pipeline-max-requests.js
+++ b/test/parallel/test-http-keep-alive-pipeline-max-requests.js
@@ -1,16 +1,16 @@
 'use strict';
 
+const common = require('../common');
 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.strictEqual(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);
@@ -25,25 +25,25 @@ function writeRequest(socket) {
   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')
+  socket.write('\r\n\r\n');
 }
 
-const server = http.createServer(function (req, res) {
-  let body = ''
+const server = http.createServer((req, res) => {
+  let body = '';
   req.on('data', (data) => {
-    body += data
+    body += data;
   });
 
   req.on('end', () => {
     if (req.method === 'POST') {
-      assert(bodySent === body)
+      assert.strictEqual(bodySent, body);
     }
 
-    res.writeHead(200, {'Content-Type': 'text/plain'});
+    res.writeHead(200, { 'Content-Type': 'text/plain' });
     res.write('Hello World!');
     res.end();
-  })
-})
+  });
+});
 
 server.maxRequestsPerSocket = 3;
 
@@ -61,7 +61,7 @@ server.listen(0, common.mustCall((res) => {
     writeRequest(socket);
   }));
 
-  let buffer = ''
+  let buffer = '';
 
   socket.on('data', (data) => {
     buffer += data;
@@ -73,14 +73,14 @@ server.listen(0, common.mustCall((res) => {
       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], /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);
-      
+      assert.strictEqual(responseParts[6].search(/Keep-Alive: timeout=5, max=3\r\n/m), -1);
+      assert.strictEqual(responseParts[7].search(/Hello World!/m), -1);
+
       socket.end();
     }
-  })
+  });
 
   socket.connect({ port: server.address().port });
-}));
\ No newline at end of file
+}));

From 063a83cc20c4be55633215a7b7c0d66ce7fb70cf Mon Sep 17 00:00:00 2001
From: Artur K <fatal10110@gmail.com>
Date: Mon, 13 Sep 2021 00:42:59 +0300
Subject: [PATCH 8/9] http: max requests per socket docs

---
 doc/api/http.md | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/doc/api/http.md b/doc/api/http.md
index f7484baac2eb1a..24303dfa1a7041 100644
--- a/doc/api/http.md
+++ b/doc/api/http.md
@@ -1352,6 +1352,22 @@ By default, the Server does not timeout sockets. However, if a callback
 is assigned to the Server's `'timeout'` event, timeouts must be handled
 explicitly.
 
+### `server.maxRequestsPerSocket`
+<!-- YAML
+added: REPLACEME
+-->
+
+* {number} Requests per socket. **Default:** null (no limit)
+
+The maximum number of requests socket can handle
+before closing keep alive connection.
+
+A value of `null` will disable the limit.
+
+When limit is reach it will set `Connection` header value to `closed`,
+but will not actually close the connection, subsequent requests sent
+after the limit is reached will get `503 Service Unavailable` as a response.
+
 ### `server.timeout`
 <!-- YAML
 added: v0.9.12

From c5ae8142cf8a45df7c061f9369eb68f73a8fa4ab Mon Sep 17 00:00:00 2001
From: Artur K <fatal10110@gmail.com>
Date: Tue, 14 Sep 2021 14:14:07 +0300
Subject: [PATCH 9/9] http: remove "max" hint

---
 lib/_http_outgoing.js                                       | 5 +++--
 lib/_http_server.js                                         | 1 -
 test/parallel/test-http-keep-alive-max-requests.js          | 4 ++--
 test/parallel/test-http-keep-alive-pipeline-max-requests.js | 6 +++---
 4 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js
index ecf46143466d37..27e290a2b916cd 100644
--- a/lib/_http_outgoing.js
+++ b/lib/_http_outgoing.js
@@ -135,7 +135,6 @@ function OutgoingMessage() {
   this._header = null;
   this[kOutHeaders] = null;
 
-  this._maxRequestsPerSocket = null;
   this._keepAliveTimeout = 0;
 
   this._onPendingData = nop;
@@ -448,7 +447,9 @@ 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\r\n';
+    } else if (shouldSendKeepAlive) {
       header += 'Connection: keep-alive\r\n';
       if (this._keepAliveTimeout && this._defaultKeepAlive) {
         const timeoutSeconds = MathFloor(this._keepAliveTimeout / 1000);
diff --git a/lib/_http_server.js b/lib/_http_server.js
index 758bb8c9267e1e..2221b132f4d29c 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -877,7 +877,6 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
 
   const res = new server[kServerResponse](req);
   res._keepAliveTimeout = server.keepAliveTimeout;
-  res._maxRequestsPerSocket = server.maxRequestsPerSocket;
   res._onPendingData = updateOutgoingData.bind(undefined,
                                                socket, state);
 
diff --git a/test/parallel/test-http-keep-alive-max-requests.js b/test/parallel/test-http-keep-alive-max-requests.js
index d1fbc6a7fd0842..657b59ae6d9369 100644
--- a/test/parallel/test-http-keep-alive-max-requests.js
+++ b/test/parallel/test-http-keep-alive-max-requests.js
@@ -10,11 +10,11 @@ const bodySent = 'This is my request';
 function assertResponse(headers, body, expectClosed) {
   if (expectClosed) {
     assert.match(headers, /Connection: close\r\n/m);
-    assert.strictEqual(headers.search(/Keep-Alive: timeout=5, max=3\r\n/m), -1);
+    assert.strictEqual(headers.search(/Keep-Alive: timeout=5\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(headers, /Keep-Alive: timeout=5\r\n/m);
     assert.match(body, /Hello World!/m);
   }
 }
diff --git a/test/parallel/test-http-keep-alive-pipeline-max-requests.js b/test/parallel/test-http-keep-alive-pipeline-max-requests.js
index 0de00b7a04910b..9c5d46a57ce197 100644
--- a/test/parallel/test-http-keep-alive-pipeline-max-requests.js
+++ b/test/parallel/test-http-keep-alive-pipeline-max-requests.js
@@ -10,11 +10,11 @@ const bodySent = 'This is my request';
 function assertResponse(headers, body, expectClosed) {
   if (expectClosed) {
     assert.match(headers, /Connection: close\r\n/m);
-    assert.strictEqual(headers.search(/Keep-Alive: timeout=5, max=3\r\n/m), -1);
+    assert.strictEqual(headers.search(/Keep-Alive: timeout=5\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(headers, /Keep-Alive: timeout=5\r\n/m);
     assert.match(body, /Hello World!/m);
   }
 }
@@ -75,7 +75,7 @@ server.listen(0, common.mustCall((res) => {
 
       assert.match(responseParts[6], /HTTP\/1\.1 503 Service Unavailable/m);
       assert.match(responseParts[6], /Connection: close\r\n/m);
-      assert.strictEqual(responseParts[6].search(/Keep-Alive: timeout=5, max=3\r\n/m), -1);
+      assert.strictEqual(responseParts[6].search(/Keep-Alive: timeout=5\r\n/m), -1);
       assert.strictEqual(responseParts[7].search(/Hello World!/m), -1);
 
       socket.end();