| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a8532d4 commit eb43bc0
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -941,6 +941,26 @@ added: v0.7.0 | |||
| 941 | 941 | ||
| 942 | 942 | Limits maximum incoming headers count. If set to 0, no limit will be applied. | |
| 943 | 943 | ||
| 944 | + ### server.headersTimeout | ||
| 945 | + <!-- YAML | ||
| 946 | + added: REPLACEME | ||
| 947 | + --> | ||
| 948 | + | ||
| 949 | + * {number} **Default:** `40000` | ||
| 950 | + | ||
| 951 | + Limit the amount of time the parser will wait to receive the complete HTTP | ||
| 952 | + headers. | ||
| 953 | + | ||
| 954 | + In case of inactivity, the rules defined in [server.timeout][] apply. However, | ||
| 955 | + that inactivity based timeout would still allow the connection to be kept open | ||
| 956 | + if the headers are being sent very slowly (by default, up to a byte per 2 | ||
| 957 | + minutes). In order to prevent this, whenever header data arrives an additional | ||
| 958 | + check is made that more than `server.headersTimeout` milliseconds has not | ||
| 959 | + passed since the connection was established. If the check fails, a `'timeout'` | ||
| 960 | + event is emitted on the server object, and (by default) the socket is destroyed. | ||
| 961 | + See [server.timeout][] for more information on how timeout behaviour can be | ||
| 962 | + customised. | ||
| 963 | + | ||
| 944 | 964 | ### server.setTimeout([msecs][, callback]) | |
| 945 | 965 | <!-- YAML | |
| 946 | 966 | added: v0.9.12 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -43,6 +43,12 @@ This method is identical to [`server.listen()`][] from [`net.Server`][]. | |||
| 43 | 43 | ||
| 44 | 44 | See [`http.Server#maxHeadersCount`][]. | |
| 45 | 45 | ||
| 46 | + ### server.headersTimeout | ||
| 47 | + | ||
| 48 | + - {number} **Default:** `40000` | ||
| 49 | + | ||
| 50 | + See [`http.Server#headersTimeout`][]. | ||
| 51 | + | ||
| 46 | 52 | ### server.setTimeout([msecs][, callback]) | |
| 47 | 53 | <!-- YAML | |
| 48 | 54 | added: v0.11.2 | |
@@ -360,6 +366,7 @@ headers: max-age=0; pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; p | |||
| 360 | 366 | [`http.Agent`]: http.html#http_class_http_agent | |
| 361 | 367 | [`http.Server#keepAliveTimeout`]: http.html#http_server_keepalivetimeout | |
| 362 | 368 | [`http.Server#maxHeadersCount`]: http.html#http_server_maxheaderscount | |
| 369 | + [`http.Server#headersTimeout`]: http.html#http_server_headerstimeout | ||
| 363 | 370 | [`http.Server#setTimeout()`]: http.html#http_server_settimeout_msecs_callback | |
| 364 | 371 | [`http.Server#timeout`]: http.html#http_server_timeout | |
| 365 | 372 | [`http.Server`]: http.html#http_class_http_server | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -37,7 +37,7 @@ const { | |||
| 37 | 37 | _checkInvalidHeaderChar: checkInvalidHeaderChar | |
| 38 | 38 | } = require('_http_common'); | |
| 39 | 39 | const { OutgoingMessage } = require('_http_outgoing'); | |
| 40 | - const { outHeadersKey, ondrain } = require('internal/http'); | ||
| 40 | + const { outHeadersKey, ondrain, nowDate } = require('internal/http'); | ||
| 41 | 41 | const { | |
| 42 | 42 | defaultTriggerAsyncIdScope, | |
| 43 | 43 | getOrSetAsyncId | |
@@ -303,6 +303,7 @@ function Server(options, requestListener) { | |||
| 303 | 303 | this.keepAliveTimeout = 5000; | |
| 304 | 304 | this._pendingResponseData = 0; | |
| 305 | 305 | this.maxHeadersCount = null; | |
| 306 | + this.headersTimeout = 40 * 1000; // 40 seconds | ||
| 306 | 307 | } | |
| 307 | 308 | util.inherits(Server, net.Server); | |
| 308 | 309 | ||
@@ -341,6 +342,9 @@ function connectionListenerInternal(server, socket) { | |||
| 341 | 342 | var parser = parsers.alloc(); | |
| 342 | 343 | parser.reinitialize(HTTPParser.REQUEST); | |
| 343 | 344 | parser.socket = socket; | |
| 345 | + | ||
| 346 | + // We are starting to wait for our headers. | ||
| 347 | + parser.parsingHeadersStart = nowDate(); | ||
| 344 | 348 | socket.parser = parser; | |
| 345 | 349 | ||
| 346 | 350 | // Propagate headers limit from server instance to parser | |
@@ -478,7 +482,20 @@ function socketOnData(server, socket, parser, state, d) { | |||
| 478 | 482 | ||
| 479 | 483 | function onParserExecute(server, socket, parser, state, ret) { | |
| 480 | 484 | socket._unrefTimer(); | |
| 485 | + const start = parser.parsingHeadersStart; | ||
| 481 | 486 | debug('SERVER socketOnParserExecute %d', ret); | |
| 487 | + | ||
| 488 | + // If we have not parsed the headers, destroy the socket | ||
| 489 | + // after server.headersTimeout to protect from DoS attacks. | ||
| 490 | + // start === 0 means that we have parsed headers. | ||
| 491 | + if (start !== 0 && nowDate() - start > server.headersTimeout) { | ||
| 492 | + const serverTimeout = server.emit('timeout', socket); | ||
| 493 | + | ||
| 494 | + if (!serverTimeout) | ||
| 495 | + socket.destroy(); | ||
| 496 | + return; | ||
| 497 | + } | ||
| 498 | + | ||
| 482 | 499 | onParserExecuteCommon(server, socket, parser, state, ret, undefined); | |
| 483 | 500 | } | |
| 484 | 501 | ||
@@ -589,6 +606,9 @@ function resOnFinish(req, res, socket, state, server) { | |||
| 589 | 606 | function parserOnIncoming(server, socket, state, req, keepAlive) { | |
| 590 | 607 | resetSocketTimeout(server, socket, state); | |
| 591 | 608 | ||
| 609 | + // Set to zero to communicate that we have finished parsing. | ||
| 610 | + socket.parser.parsingHeadersStart = 0; | ||
| 611 | + | ||
| 592 | 612 | if (req.upgrade) { | |
| 593 | 613 | req.upgrade = req.method === 'CONNECT' || | |
| 594 | 614 | server.listenerCount('upgrade') > 0; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -75,6 +75,7 @@ function Server(opts, requestListener) { | |||
| 75 | 75 | this.timeout = 2 * 60 * 1000; | |
| 76 | 76 | this.keepAliveTimeout = 5000; | |
| 77 | 77 | this.maxHeadersCount = null; | |
| 78 | + this.headersTimeout = 40 * 1000; // 40 seconds | ||
| 78 | 79 | } | |
| 79 | 80 | inherits(Server, tls.Server); | |
| 80 | 81 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,19 +2,29 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { setUnrefTimeout } = require('internal/timers'); | |
| 4 | 4 | ||
| 5 | - var dateCache; | ||
| 5 | + var nowCache; | ||
| 6 | + var utcCache; | ||
| 7 | + | ||
| 8 | + function nowDate() { | ||
| 9 | + if (!nowCache) cache(); | ||
| 10 | + return nowCache; | ||
| 11 | + } | ||
| 12 | + | ||
| 6 | 13 | function utcDate() { | |
| 7 | - if (!dateCache) { | ||
| 8 | - const d = new Date(); | ||
| 9 | - dateCache = d.toUTCString(); | ||
| 14 | + if (!utcCache) cache(); | ||
| 15 | + return utcCache; | ||
| 16 | + } | ||
| 10 | 17 | ||
| 11 | - setUnrefTimeout(resetCache, 1000 - d.getMilliseconds()); | ||
| 12 | - } | ||
| 13 | - return dateCache; | ||
| 18 | + function cache() { | ||
| 19 | + const d = new Date(); | ||
| 20 | + nowCache = d.valueOf(); | ||
| 21 | + utcCache = d.toUTCString(); | ||
| 22 | + setUnrefTimeout(resetCache, 1000 - d.getMilliseconds()); | ||
| 14 | 23 | } | |
| 15 | 24 | ||
| 16 | 25 | function resetCache() { | |
| 17 | - dateCache = undefined; | ||
| 26 | + nowCache = undefined; | ||
| 27 | + utcCache = undefined; | ||
| 18 | 28 | } | |
| 19 | 29 | ||
| 20 | 30 | function ondrain() { | |
@@ -24,5 +34,6 @@ function ondrain() { | |||
| 24 | 34 | module.exports = { | |
| 25 | 35 | outHeadersKey: Symbol('outHeadersKey'), | |
| 26 | 36 | ondrain, | |
| 37 | + nowDate, | ||
| 27 | 38 | utcDate | |
| 28 | 39 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,10 +52,10 @@ process.on('exit', function() { | |||
| 52 | 52 | triggerAsyncId: 'tcp:2' }, | |
| 53 | 53 | { type: 'Timeout', | |
| 54 | 54 | id: 'timeout:2', | |
| 55 | - triggerAsyncId: 'httpparser:4' }, | ||
| 55 | + triggerAsyncId: 'tcp:2' }, | ||
| 56 | 56 | { type: 'TIMERWRAP', | |
| 57 | 57 | id: 'timer:2', | |
| 58 | - triggerAsyncId: 'httpparser:4' }, | ||
| 58 | + triggerAsyncId: 'tcp:2' }, | ||
| 59 | 59 | { type: 'SHUTDOWNWRAP', | |
| 60 | 60 | id: 'shutdown:1', | |
| 61 | 61 | triggerAsyncId: 'tcp:2' } ] | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,50 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + const assert = require('assert'); | ||
| 5 | + const { createServer } = require('http'); | ||
| 6 | + const { connect } = require('net'); | ||
| 7 | + const { finished } = require('stream'); | ||
| 8 | + | ||
| 9 | + // This test validates that the 'timeout' event fires | ||
| 10 | + // after server.headersTimeout. | ||
| 11 | + | ||
| 12 | + const headers = | ||
| 13 | + 'GET / HTTP/1.1\r\n' + | ||
| 14 | + 'Host: localhost\r\n' + | ||
| 15 | + 'Agent: node\r\n'; | ||
| 16 | + | ||
| 17 | + const server = createServer(common.mustNotCall()); | ||
| 18 | + let sendCharEvery = 1000; | ||
| 19 | + | ||
| 20 | + // 40 seconds is the default | ||
| 21 | + assert.strictEqual(server.headersTimeout, 40 * 1000); | ||
| 22 | + | ||
| 23 | + // Pass a REAL env variable to shortening up the default | ||
| 24 | + // value which is 40s otherwise this is useful for manual | ||
| 25 | + // testing | ||
| 26 | + if (!process.env.REAL) { | ||
| 27 | + sendCharEvery = common.platformTimeout(10); | ||
| 28 | + server.headersTimeout = 2 * sendCharEvery; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + server.once('timeout', common.mustCall((socket) => { | ||
| 32 | + socket.destroy(); | ||
| 33 | + })); | ||
| 34 | + | ||
| 35 | + server.listen(0, common.mustCall(() => { | ||
| 36 | + const client = connect(server.address().port); | ||
| 37 | + client.write(headers); | ||
| 38 | + client.write('X-CRASH: '); | ||
| 39 | + | ||
| 40 | + const interval = setInterval(() => { | ||
| 41 | + client.write('a'); | ||
| 42 | + }, sendCharEvery); | ||
| 43 | + | ||
| 44 | + client.resume(); | ||
| 45 | + | ||
| 46 | + finished(client, common.mustCall((err) => { | ||
| 47 | + clearInterval(interval); | ||
| 48 | + server.close(); | ||
| 49 | + })); | ||
| 50 | + })); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,63 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + const { readKey } = require('../common/fixtures'); | ||
| 5 | + | ||
| 6 | + if (!common.hasCrypto) | ||
| 7 | + common.skip('missing crypto'); | ||
| 8 | + | ||
| 9 | + const assert = require('assert'); | ||
| 10 | + const { createServer } = require('https'); | ||
| 11 | + const { connect } = require('tls'); | ||
| 12 | + const { finished } = require('stream'); | ||
| 13 | + | ||
| 14 | + // This test validates that the 'timeout' event fires | ||
| 15 | + // after server.headersTimeout. | ||
| 16 | + | ||
| 17 | + const headers = | ||
| 18 | + 'GET / HTTP/1.1\r\n' + | ||
| 19 | + 'Host: localhost\r\n' + | ||
| 20 | + 'Agent: node\r\n'; | ||
| 21 | + | ||
| 22 | + const server = createServer({ | ||
| 23 | + key: readKey('agent1-key.pem'), | ||
| 24 | + cert: readKey('agent1-cert.pem'), | ||
| 25 | + ca: readKey('ca1-cert.pem'), | ||
| 26 | + }, common.mustNotCall()); | ||
| 27 | + | ||
| 28 | + let sendCharEvery = 1000; | ||
| 29 | + | ||
| 30 | + // 40 seconds is the default | ||
| 31 | + assert.strictEqual(server.headersTimeout, 40 * 1000); | ||
| 32 | + | ||
| 33 | + // pass a REAL env variable to shortening up the default | ||
| 34 | + // value which is 40s otherwise | ||
| 35 | + // this is useful for manual testing | ||
| 36 | + if (!process.env.REAL) { | ||
| 37 | + sendCharEvery = common.platformTimeout(10); | ||
| 38 | + server.headersTimeout = 2 * sendCharEvery; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + server.once('timeout', common.mustCall((socket) => { | ||
| 42 | + socket.destroy(); | ||
| 43 | + })); | ||
| 44 | + | ||
| 45 | + server.listen(0, common.mustCall(() => { | ||
| 46 | + const client = connect({ | ||
| 47 | + port: server.address().port, | ||
| 48 | + rejectUnauthorized: false | ||
| 49 | + }); | ||
| 50 | + client.write(headers); | ||
| 51 | + client.write('X-CRASH: '); | ||
| 52 | + | ||
| 53 | + const interval = setInterval(() => { | ||
| 54 | + client.write('a'); | ||
| 55 | + }, sendCharEvery); | ||
| 56 | + | ||
| 57 | + client.resume(); | ||
| 58 | + | ||
| 59 | + finished(client, common.mustCall((err) => { | ||
| 60 | + clearInterval(interval); | ||
| 61 | + server.close(); | ||
| 62 | + })); | ||
| 63 | + })); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments