| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1348,6 +1348,22 @@ By default, the Server does not timeout sockets. However, if a callback | |||
| 1348 | 1348 | is assigned to the Server's `'timeout'` event, timeouts must be handled | |
| 1349 | 1349 | explicitly. | |
| 1350 | 1350 | ||
| 1351 | + ### `server.maxRequestsPerSocket` | ||
| 1352 | + <!-- YAML | ||
| 1353 | + added: REPLACEME | ||
| 1354 | + --> | ||
| 1355 | + | ||
| 1356 | + * {number} Requests per socket. **Default:** null (no limit) | ||
| 1357 | + | ||
| 1358 | + The maximum number of requests socket can handle | ||
| 1359 | + before closing keep alive connection. | ||
| 1360 | + | ||
| 1361 | + A value of `null` will disable the limit. | ||
| 1362 | + | ||
| 1363 | + When limit is reach it will set `Connection` header value to `closed`, | ||
| 1364 | + but will not actually close the connection, subsequent requests sent | ||
| 1365 | + after the limit is reached will get `503 Service Unavailable` as a response. | ||
| 1366 | + | ||
| 1351 | 1367 | ### `server.timeout` | |
| 1352 | 1368 | <!-- YAML | |
| 1353 | 1369 | added: v0.9.12 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -113,6 +113,7 @@ function OutgoingMessage() { | |||
| 113 | 113 | this._last = false; | |
| 114 | 114 | this.chunkedEncoding = false; | |
| 115 | 115 | this.shouldKeepAlive = true; | |
| 116 | + this.maxRequestsOnConnectionReached = false; | ||
| 116 | 117 | this._defaultKeepAlive = true; | |
| 117 | 118 | this.useChunkedEncodingByDefault = true; | |
| 118 | 119 | this.sendDate = false; | |
@@ -446,7 +447,9 @@ function _storeHeader(firstLine, headers) { | |||
| 446 | 447 | } else if (!state.connection) { | |
| 447 | 448 | const shouldSendKeepAlive = this.shouldKeepAlive && | |
| 448 | 449 | (state.contLen || this.useChunkedEncodingByDefault || this.agent); | |
| 449 | - if (shouldSendKeepAlive) { | ||
| 450 | + if (shouldSendKeepAlive && this.maxRequestsOnConnectionReached) { | ||
| 451 | + header += 'Connection: close\r\n'; | ||
| 452 | + } else if (shouldSendKeepAlive) { | ||
| 450 | 453 | header += 'Connection: keep-alive\r\n'; | |
| 451 | 454 | if (this._keepAliveTimeout && this._defaultKeepAlive) { | |
| 452 | 455 | const timeoutSeconds = MathFloor(this._keepAliveTimeout / 1000); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -396,6 +396,7 @@ function Server(options, requestListener) { | |||
| 396 | 396 | this.timeout = 0; | |
| 397 | 397 | this.keepAliveTimeout = 5000; | |
| 398 | 398 | this.maxHeadersCount = null; | |
| 399 | + this.maxRequestsPerSocket = null; | ||
| 399 | 400 | this.headersTimeout = 60 * 1000; // 60 seconds | |
| 400 | 401 | this.requestTimeout = 0; | |
| 401 | 402 | } | |
@@ -487,6 +488,7 @@ function connectionListenerInternal(server, socket) { | |||
| 487 | 488 | // need to pause TCP socket/HTTP parser, and wait until the data will be | |
| 488 | 489 | // sent to the client. | |
| 489 | 490 | outgoingData: 0, | |
| 491 | + requestsCount: 0, | ||
| 490 | 492 | keepAliveTimeoutSet: false | |
| 491 | 493 | }; | |
| 492 | 494 | state.onData = socketOnData.bind(undefined, | |
@@ -903,28 +905,47 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { | |||
| 903 | 905 | resOnFinish.bind(undefined, | |
| 904 | 906 | req, res, socket, state, server)); | |
| 905 | 907 | ||
| 906 | - if (req.headers.expect !== undefined && | ||
| 907 | - (req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) { | ||
| 908 | - if (RegExpPrototypeTest(continueExpression, req.headers.expect)) { | ||
| 909 | - res._expect_continue = true; | ||
| 908 | + let handled = false; | ||
| 910 | 909 | ||
| 911 | - if (server.listenerCount('checkContinue') > 0) { | ||
| 912 | - server.emit('checkContinue', req, res); | ||
| 910 | + if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) { | ||
| 911 | + if (typeof server.maxRequestsPerSocket === 'number') { | ||
| 912 | + state.requestsCount++; | ||
| 913 | + res.maxRequestsOnConnectionReached = ( | ||
| 914 | + server.maxRequestsPerSocket <= state.requestsCount); | ||
| 915 | + } | ||
| 916 | + | ||
| 917 | + if (typeof server.maxRequestsPerSocket === 'number' && | ||
| 918 | + (server.maxRequestsPerSocket < state.requestsCount)) { | ||
| 919 | + handled = true; | ||
| 920 | + | ||
| 921 | + res.writeHead(503); | ||
| 922 | + res.end(); | ||
| 923 | + } else if (req.headers.expect !== undefined) { | ||
| 924 | + handled = true; | ||
| 925 | + | ||
| 926 | + if (RegExpPrototypeTest(continueExpression, req.headers.expect)) { | ||
| 927 | + res._expect_continue = true; | ||
| 928 | + | ||
| 929 | + if (server.listenerCount('checkContinue') > 0) { | ||
| 930 | + server.emit('checkContinue', req, res); | ||
| 931 | + } else { | ||
| 932 | + res.writeContinue(); | ||
| 933 | + server.emit('request', req, res); | ||
| 934 | + } | ||
| 935 | + } else if (server.listenerCount('checkExpectation') > 0) { | ||
| 936 | + server.emit('checkExpectation', req, res); | ||
| 913 | 937 | } else { | |
| 914 | - res.writeContinue(); | ||
| 915 | - server.emit('request', req, res); | ||
| 938 | + res.writeHead(417); | ||
| 939 | + res.end(); | ||
| 916 | 940 | } | |
| 917 | - } else if (server.listenerCount('checkExpectation') > 0) { | ||
| 918 | - server.emit('checkExpectation', req, res); | ||
| 919 | - } else { | ||
| 920 | - res.writeHead(417); | ||
| 921 | - res.end(); | ||
| 922 | 941 | } | |
| 923 | - } else { | ||
| 924 | - req.on('end', clearRequestTimeout); | ||
| 942 | + } | ||
| 925 | 943 | ||
| 944 | + if (!handled) { | ||
| 945 | + req.on('end', clearRequestTimeout); | ||
| 926 | 946 | server.emit('request', req, res); | |
| 927 | 947 | } | |
| 948 | + | ||
| 928 | 949 | return 0; // No special treatment. | |
| 929 | 950 | } | |
| 930 | 951 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,116 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + const net = require('net'); | ||
| 5 | + const http = require('http'); | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + | ||
| 8 | + const bodySent = 'This is my request'; | ||
| 9 | + | ||
| 10 | + function assertResponse(headers, body, expectClosed) { | ||
| 11 | + if (expectClosed) { | ||
| 12 | + assert.match(headers, /Connection: close\r\n/m); | ||
| 13 | + assert.strictEqual(headers.search(/Keep-Alive: timeout=5\r\n/m), -1); | ||
| 14 | + assert.match(body, /Hello World!/m); | ||
| 15 | + } else { | ||
| 16 | + assert.match(headers, /Connection: keep-alive\r\n/m); | ||
| 17 | + assert.match(headers, /Keep-Alive: timeout=5\r\n/m); | ||
| 18 | + assert.match(body, /Hello World!/m); | ||
| 19 | + } | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + function writeRequest(socket, withBody) { | ||
| 23 | + if (withBody) { | ||
| 24 | + socket.write('POST / HTTP/1.1\r\n'); | ||
| 25 | + socket.write('Connection: keep-alive\r\n'); | ||
| 26 | + socket.write('Content-Type: text/plain\r\n'); | ||
| 27 | + socket.write(`Content-Length: ${bodySent.length}\r\n\r\n`); | ||
| 28 | + socket.write(`${bodySent}\r\n`); | ||
| 29 | + socket.write('\r\n\r\n'); | ||
| 30 | + } else { | ||
| 31 | + socket.write('GET / HTTP/1.1\r\n'); | ||
| 32 | + socket.write('Connection: keep-alive\r\n'); | ||
| 33 | + socket.write('\r\n\r\n'); | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + const server = http.createServer((req, res) => { | ||
| 38 | + let body = ''; | ||
| 39 | + req.on('data', (data) => { | ||
| 40 | + body += data; | ||
| 41 | + }); | ||
| 42 | + | ||
| 43 | + req.on('end', () => { | ||
| 44 | + if (req.method === 'POST') { | ||
| 45 | + assert.strictEqual(bodySent, body); | ||
| 46 | + } | ||
| 47 | + res.writeHead(200, { 'Content-Type': 'text/plain' }); | ||
| 48 | + res.write('Hello World!'); | ||
| 49 | + res.end(); | ||
| 50 | + }); | ||
| 51 | + }); | ||
| 52 | + | ||
| 53 | + function initialRequests(socket, numberOfRequests, cb) { | ||
| 54 | + let buffer = ''; | ||
| 55 | + | ||
| 56 | + writeRequest(socket); | ||
| 57 | + | ||
| 58 | + socket.on('data', (data) => { | ||
| 59 | + buffer += data; | ||
| 60 | + | ||
| 61 | + if (buffer.endsWith('\r\n\r\n')) { | ||
| 62 | + if (--numberOfRequests === 0) { | ||
| 63 | + socket.removeAllListeners('data'); | ||
| 64 | + cb(); | ||
| 65 | + } else { | ||
| 66 | + const [headers, body] = buffer.trim().split('\r\n\r\n'); | ||
| 67 | + assertResponse(headers, body); | ||
| 68 | + buffer = ''; | ||
| 69 | + writeRequest(socket, true); | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + }); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + | ||
| 76 | + server.maxRequestsPerSocket = 3; | ||
| 77 | + server.listen(0, common.mustCall((res) => { | ||
| 78 | + const socket = new net.Socket(); | ||
| 79 | + const anotherSocket = new net.Socket(); | ||
| 80 | + | ||
| 81 | + socket.on('end', common.mustCall(() => { | ||
| 82 | + server.close(); | ||
| 83 | + })); | ||
| 84 | + | ||
| 85 | + socket.on('ready', common.mustCall(() => { | ||
| 86 | + // Do 2 of 3 allowed requests and ensure they still alive | ||
| 87 | + initialRequests(socket, 2, common.mustCall(() => { | ||
| 88 | + anotherSocket.connect({ port: server.address().port }); | ||
| 89 | + })); | ||
| 90 | + })); | ||
| 91 | + | ||
| 92 | + anotherSocket.on('ready', common.mustCall(() => { | ||
| 93 | + // Do another 2 requests with another socket | ||
| 94 | + // enusre that this will not affect the first socket | ||
| 95 | + initialRequests(anotherSocket, 2, common.mustCall(() => { | ||
| 96 | + let buffer = ''; | ||
| 97 | + | ||
| 98 | + // Send the rest of the calls to the first socket | ||
| 99 | + // and see connection is closed | ||
| 100 | + socket.on('data', common.mustCall((data) => { | ||
| 101 | + buffer += data; | ||
| 102 | + | ||
| 103 | + if (buffer.endsWith('\r\n\r\n')) { | ||
| 104 | + const [headers, body] = buffer.trim().split('\r\n\r\n'); | ||
| 105 | + assertResponse(headers, body, true); | ||
| 106 | + anotherSocket.end(); | ||
| 107 | + socket.end(); | ||
| 108 | + } | ||
| 109 | + })); | ||
| 110 | + | ||
| 111 | + writeRequest(socket, true); | ||
| 112 | + })); | ||
| 113 | + })); | ||
| 114 | + | ||
| 115 | + socket.connect({ port: server.address().port }); | ||
| 116 | + })); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,86 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + const net = require('net'); | ||
| 5 | + const http = require('http'); | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + | ||
| 8 | + const bodySent = 'This is my request'; | ||
| 9 | + | ||
| 10 | + function assertResponse(headers, body, expectClosed) { | ||
| 11 | + if (expectClosed) { | ||
| 12 | + assert.match(headers, /Connection: close\r\n/m); | ||
| 13 | + assert.strictEqual(headers.search(/Keep-Alive: timeout=5\r\n/m), -1); | ||
| 14 | + assert.match(body, /Hello World!/m); | ||
| 15 | + } else { | ||
| 16 | + assert.match(headers, /Connection: keep-alive\r\n/m); | ||
| 17 | + assert.match(headers, /Keep-Alive: timeout=5\r\n/m); | ||
| 18 | + assert.match(body, /Hello World!/m); | ||
| 19 | + } | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + function writeRequest(socket) { | ||
| 23 | + socket.write('POST / HTTP/1.1\r\n'); | ||
| 24 | + socket.write('Connection: keep-alive\r\n'); | ||
| 25 | + socket.write('Content-Type: text/plain\r\n'); | ||
| 26 | + socket.write(`Content-Length: ${bodySent.length}\r\n\r\n`); | ||
| 27 | + socket.write(`${bodySent}\r\n`); | ||
| 28 | + socket.write('\r\n\r\n'); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + const server = http.createServer((req, res) => { | ||
| 32 | + let body = ''; | ||
| 33 | + req.on('data', (data) => { | ||
| 34 | + body += data; | ||
| 35 | + }); | ||
| 36 | + | ||
| 37 | + req.on('end', () => { | ||
| 38 | + if (req.method === 'POST') { | ||
| 39 | + assert.strictEqual(bodySent, body); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + res.writeHead(200, { 'Content-Type': 'text/plain' }); | ||
| 43 | + res.write('Hello World!'); | ||
| 44 | + res.end(); | ||
| 45 | + }); | ||
| 46 | + }); | ||
| 47 | + | ||
| 48 | + server.maxRequestsPerSocket = 3; | ||
| 49 | + | ||
| 50 | + server.listen(0, common.mustCall((res) => { | ||
| 51 | + const socket = new net.Socket(); | ||
| 52 | + | ||
| 53 | + socket.on('end', common.mustCall(() => { | ||
| 54 | + server.close(); | ||
| 55 | + })); | ||
| 56 | + | ||
| 57 | + socket.on('ready', common.mustCall(() => { | ||
| 58 | + writeRequest(socket); | ||
| 59 | + writeRequest(socket); | ||
| 60 | + writeRequest(socket); | ||
| 61 | + writeRequest(socket); | ||
| 62 | + })); | ||
| 63 | + | ||
| 64 | + let buffer = ''; | ||
| 65 | + | ||
| 66 | + socket.on('data', (data) => { | ||
| 67 | + buffer += data; | ||
| 68 | + | ||
| 69 | + const responseParts = buffer.trim().split('\r\n\r\n'); | ||
| 70 | + | ||
| 71 | + if (responseParts.length === 8) { | ||
| 72 | + assertResponse(responseParts[0], responseParts[1]); | ||
| 73 | + assertResponse(responseParts[2], responseParts[3]); | ||
| 74 | + assertResponse(responseParts[4], responseParts[5], true); | ||
| 75 | + | ||
| 76 | + assert.match(responseParts[6], /HTTP\/1\.1 503 Service Unavailable/m); | ||
| 77 | + assert.match(responseParts[6], /Connection: close\r\n/m); | ||
| 78 | + assert.strictEqual(responseParts[6].search(/Keep-Alive: timeout=5\r\n/m), -1); | ||
| 79 | + assert.strictEqual(responseParts[7].search(/Hello World!/m), -1); | ||
| 80 | + | ||
| 81 | + socket.end(); | ||
| 82 | + } | ||
| 83 | + }); | ||
| 84 | + | ||
| 85 | + socket.connect({ port: server.address().port }); | ||
| 86 | + })); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments