Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/errors.md5
-rw-r--r--doc/api/http.md17
-rw-r--r--doc/api/https.md10
-rw-r--r--lib/_http_common.js8
-rw-r--r--lib/_http_server.js85
-rw-r--r--lib/https.js1
-rw-r--r--lib/internal/errors.js1
-rw-r--r--src/node_http_parser.cc28
-rw-r--r--test/parallel/test-http-server-request-timeout-delayed-body.js62
-rw-r--r--test/parallel/test-http-server-request-timeout-delayed-headers.js48
-rw-r--r--test/parallel/test-http-server-request-timeout-interrupted-body.js63
-rw-r--r--test/parallel/test-http-server-request-timeout-interrupted-headers.js48
-rw-r--r--test/parallel/test-http-server-request-timeout-keepalive.js94
-rw-r--r--test/parallel/test-http-server-request-timeout-upgrade.js55
14 files changed, 517 insertions, 8 deletions
diff --git a/doc/api/errors.md b/doc/api/errors.md
index db56c571e9c..98ca5c17a8f 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -940,6 +940,11 @@ allowed size for a `Buffer`.
An invalid symlink type was passed to the [`fs.symlink()`][] or
[`fs.symlinkSync()`][] methods.
+<a id="ERR_HTTP_REQUEST_TIMEOUT"></a>
+### `ERR_HTTP_REQUEST_TIMEOUT`
+
+The client has not sent the entire request within the allowed time.
+
<a id="ERR_HTTP_HEADERS_SENT"></a>
### `ERR_HTTP_HEADERS_SENT`
diff --git a/doc/api/http.md b/doc/api/http.md
index e5871ef5a8c..8945ba3fda5 100644
--- a/doc/api/http.md
+++ b/doc/api/http.md
@@ -1259,6 +1259,23 @@ added: v0.7.0
Limits maximum incoming headers count. If set to 0, no limit will be applied.
+### `server.requestTimeout`
+<!-- YAML
+added: REPLACEME
+-->
+
+* {number} **Default:** `0`
+
+Sets the timeout value in milliseconds for receiving the entire request from
+the client.
+
+If the timeout expires, the server responds with status 408 without
+forwarding the request to the request listener and then closes the connection.
+
+It must be set to a non-zero value (e.g. 120 seconds) to proctect against
+potential Denial-of-Service attacks in case the server is deployed without a
+reverse proxy in front.
+
### `server.setTimeout([msecs][, callback])`
<!-- YAML
added: v0.9.12
diff --git a/doc/api/https.md b/doc/api/https.md
index f94bf7f4e9e..8dbe4d2f224 100644
--- a/doc/api/https.md
+++ b/doc/api/https.md
@@ -113,6 +113,15 @@ This method is identical to [`server.listen()`][] from [`net.Server`][].
See [`http.Server#maxHeadersCount`][].
+### `server.requestTimeout`
+<!-- YAML
+added: REPLACEME
+-->
+
+* {number} **Default:** `0`
+
+See [`http.Server#requestTimeout`][].
+
### `server.setTimeout([msecs][, callback])`
<!-- YAML
added: v0.11.2
@@ -451,6 +460,7 @@ headers: max-age=0; pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; p
[`http.Server#headersTimeout`]: http.html#http_server_headerstimeout
[`http.Server#keepAliveTimeout`]: http.html#http_server_keepalivetimeout
[`http.Server#maxHeadersCount`]: http.html#http_server_maxheaderscount
+[`http.Server#requestTimeout`]: http.html#http_server_requesttimeout
[`http.Server#setTimeout()`]: http.html#http_server_settimeout_msecs_callback
[`http.Server#timeout`]: http.html#http_server_timeout
[`http.Server`]: http.html#http_class_http_server
diff --git a/lib/_http_common.js b/lib/_http_common.js
index a667557f9d3..e97670f0aca 100644
--- a/lib/_http_common.js
+++ b/lib/_http_common.js
@@ -44,6 +44,7 @@ let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
});
const kIncomingMessage = Symbol('IncomingMessage');
+const kRequestTimeout = Symbol('RequestTimeout');
const kOnHeaders = HTTPParser.kOnHeaders | 0;
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
const kOnBody = HTTPParser.kOnBody | 0;
@@ -99,6 +100,12 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
incoming.url = url;
incoming.upgrade = upgrade;
+ if (socket) {
+ debug('requestTimeout timer moved to req');
+ incoming[kRequestTimeout] = incoming.socket[kRequestTimeout];
+ incoming.socket[kRequestTimeout] = undefined;
+ }
+
let n = headers.length;
// If parser.maxHeaderPairs <= 0 assume that there's no limit.
@@ -264,6 +271,7 @@ module.exports = {
methods,
parsers,
kIncomingMessage,
+ kRequestTimeout,
HTTPParser,
isLenient,
prepareError,
diff --git a/lib/_http_server.js b/lib/_http_server.js
index 84f2696b307..21eb68d463e 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -40,6 +40,7 @@ const {
continueExpression,
chunkExpression,
kIncomingMessage,
+ kRequestTimeout,
HTTPParser,
isLenient,
_checkInvalidHeaderChar: checkInvalidHeaderChar,
@@ -61,6 +62,7 @@ const {
codes
} = require('internal/errors');
const {
+ ERR_HTTP_REQUEST_TIMEOUT,
ERR_HTTP_HEADERS_SENT,
ERR_HTTP_INVALID_STATUS_CODE,
ERR_HTTP_SOCKET_ENCODING,
@@ -77,6 +79,7 @@ const {
DTRACE_HTTP_SERVER_RESPONSE
} = require('internal/dtrace');
const { observerCounts, constants } = internalBinding('performance');
+const { setTimeout, clearTimeout } = require('timers');
const { NODE_PERFORMANCE_ENTRY_TYPE_HTTP } = constants;
const kServerResponse = Symbol('ServerResponse');
@@ -148,6 +151,7 @@ const STATUS_CODES = {
511: 'Network Authentication Required' // RFC 6585 6
};
+const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0;
const kOnExecute = HTTPParser.kOnExecute | 0;
const kOnTimeout = HTTPParser.kOnTimeout | 0;
@@ -369,6 +373,7 @@ function Server(options, requestListener) {
this.keepAliveTimeout = 5000;
this.maxHeadersCount = null;
this.headersTimeout = 60 * 1000; // 60 seconds
+ this.requestTimeout = 0; // 120 seconds
}
ObjectSetPrototypeOf(Server.prototype, net.Server.prototype);
ObjectSetPrototypeOf(Server, net.Server);
@@ -491,6 +496,16 @@ function connectionListenerInternal(server, socket) {
parser[kOnTimeout] =
onParserTimeout.bind(undefined, server, socket);
+ // When receiving new requests on the same socket (pipelining or keep alive)
+ // make sure the requestTimeout is active.
+ parser[kOnMessageBegin] =
+ setRequestTimeout.bind(undefined, server, socket);
+
+ // This protects from DOS attack where an attacker establish the connection
+ // without sending any data on applications where server.timeout is left to
+ // the default value of zero.
+ setRequestTimeout(server, socket);
+
socket._paused = false;
}
@@ -586,6 +601,11 @@ function socketOnData(server, socket, parser, state, d) {
onParserExecuteCommon(server, socket, parser, state, ret, d);
}
+function onRequestTimeout(socket) {
+ socket[kRequestTimeout] = undefined;
+ socketOnError.call(socket, new ERR_HTTP_REQUEST_TIMEOUT());
+}
+
function onParserExecute(server, socket, parser, state, ret) {
// When underlying `net.Socket` instance is consumed - no
// `data` events are emitted, and thus `socket.setTimeout` fires the
@@ -608,6 +628,10 @@ const badRequestResponse = Buffer.from(
`HTTP/1.1 400 ${STATUS_CODES[400]}${CRLF}` +
`Connection: close${CRLF}${CRLF}`, 'ascii'
);
+const requestTimeoutResponse = Buffer.from(
+ `HTTP/1.1 408 ${STATUS_CODES[408]}${CRLF}` +
+ `Connection: close${CRLF}${CRLF}`, 'ascii'
+);
const requestHeaderFieldsTooLargeResponse = Buffer.from(
`HTTP/1.1 431 ${STATUS_CODES[431]}${CRLF}` +
`Connection: close${CRLF}${CRLF}`, 'ascii'
@@ -619,8 +643,20 @@ function socketOnError(e) {
if (!this.server.emit('clientError', e, this)) {
if (this.writable && this.bytesWritten === 0) {
- const response = e.code === 'HPE_HEADER_OVERFLOW' ?
- requestHeaderFieldsTooLargeResponse : badRequestResponse;
+ let response;
+
+ switch (e.code) {
+ case 'HPE_HEADER_OVERFLOW':
+ response = requestHeaderFieldsTooLargeResponse;
+ break;
+ case 'ERR_HTTP_REQUEST_TIMEOUT':
+ response = requestTimeoutResponse;
+ break;
+ default:
+ response = badRequestResponse;
+ break;
+ }
+
this.write(response);
}
this.destroy(e);
@@ -660,11 +696,20 @@ function onParserExecuteCommon(server, socket, parser, state, ret, d) {
const bodyHead = d.slice(ret, d.length);
socket.readableFlowing = null;
+
+ // Clear the requestTimeout after upgrading the connection.
+ clearRequestTimeout(req);
+
server.emit(eventName, req, socket, bodyHead);
} else {
// Got CONNECT method, but have no handler.
socket.destroy();
}
+ } else {
+ // When receiving new requests on the same socket (pipelining or keep alive)
+ // make sure the requestTimeout is active.
+ parser[kOnMessageBegin] =
+ setRequestTimeout.bind(undefined, server, socket);
}
if (socket._paused && socket.parser) {
@@ -692,6 +737,32 @@ function clearIncoming(req) {
}
}
+function setRequestTimeout(server, socket) {
+ // Set the request timeout handler.
+ if (
+ !socket[kRequestTimeout] &&
+ server.requestTimeout && server.requestTimeout > 0
+ ) {
+ debug('requestTimeout timer set');
+ socket[kRequestTimeout] =
+ setTimeout(onRequestTimeout, server.requestTimeout, socket).unref();
+ }
+}
+
+function clearRequestTimeout(req) {
+ if (!req) {
+ req = this;
+ }
+
+ if (!req[kRequestTimeout]) {
+ return;
+ }
+
+ debug('requestTimeout timer cleared');
+ clearTimeout(req[kRequestTimeout]);
+ req[kRequestTimeout] = undefined;
+}
+
function resOnFinish(req, res, socket, state, server) {
// Usually the first incoming element should be our request. it may
// be that in the case abortIncoming() was called that the incoming
@@ -706,6 +777,14 @@ function resOnFinish(req, res, socket, state, server) {
if (!req._consuming && !req._readableState.resumeScheduled)
req._dump();
+ // Make sure the requestTimeout is cleared before finishing.
+ // This might occur if the application has sent a response
+ // without consuming the request body, which would have alredy
+ // cleared the timer.
+ // clearRequestTimeout can be executed even if the timer is not active,
+ // so this is safe.
+ clearRequestTimeout(req);
+
res.detachSocket(socket);
clearIncoming(req);
process.nextTick(emitCloseNT, res);
@@ -802,6 +881,8 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
res.end();
}
} else {
+ req.on('end', clearRequestTimeout);
+
server.emit('request', req, res);
}
return 0; // No special treatment.
diff --git a/lib/https.js b/lib/https.js
index 17a89c2f0bc..4b21977fe48 100644
--- a/lib/https.js
+++ b/lib/https.js
@@ -80,6 +80,7 @@ function Server(opts, requestListener) {
this.keepAliveTimeout = 5000;
this.maxHeadersCount = null;
this.headersTimeout = 60 * 1000; // 60 seconds
+ this.requestTimeout = 120 * 1000; // 120 seconds
}
ObjectSetPrototypeOf(Server.prototype, tls.Server.prototype);
ObjectSetPrototypeOf(Server, tls.Server);
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 787298288b6..ff4cea13b61 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -940,6 +940,7 @@ E('ERR_HTTP_HEADERS_SENT',
E('ERR_HTTP_INVALID_HEADER_VALUE',
'Invalid value "%s" for header "%s"', TypeError);
E('ERR_HTTP_INVALID_STATUS_CODE', 'Invalid status code: %s', RangeError);
+E('ERR_HTTP_REQUEST_TIMEOUT', 'Request timeout', Error);
E('ERR_HTTP_SOCKET_ENCODING',
'Changing the socket encoding is not allowed per RFC7230 Section 3.', Error);
E('ERR_HTTP_TRAILER_INVALID',
diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc
index b409d007307..eada685f2d6 100644
--- a/src/node_http_parser.cc
+++ b/src/node_http_parser.cc
@@ -69,12 +69,13 @@ using v8::Uint32;
using v8::Undefined;
using v8::Value;
-const uint32_t kOnHeaders = 0;
-const uint32_t kOnHeadersComplete = 1;
-const uint32_t kOnBody = 2;
-const uint32_t kOnMessageComplete = 3;
-const uint32_t kOnExecute = 4;
-const uint32_t kOnTimeout = 5;
+const uint32_t kOnMessageBegin = 0;
+const uint32_t kOnHeaders = 1;
+const uint32_t kOnHeadersComplete = 2;
+const uint32_t kOnBody = 3;
+const uint32_t kOnMessageComplete = 4;
+const uint32_t kOnExecute = 5;
+const uint32_t kOnTimeout = 6;
// Any more fields than this will be flushed into JS
const size_t kMaxHeaderFieldsCount = 32;
@@ -204,6 +205,19 @@ class Parser : public AsyncWrap, public StreamListener {
url_.Reset();
status_message_.Reset();
header_parsing_start_time_ = uv_hrtime();
+
+ Local<Value> cb = object()->Get(env()->context(), kOnMessageBegin)
+ .ToLocalChecked();
+ if (cb->IsFunction()) {
+ InternalCallbackScope callback_scope(
+ this, InternalCallbackScope::kSkipTaskQueues);
+
+ MaybeLocal<Value> r = cb.As<Function>()->Call(
+ env()->context(), object(), 0, nullptr);
+
+ if (r.IsEmpty()) callback_scope.MarkAsFailed();
+ }
+
return 0;
}
@@ -939,6 +953,8 @@ void InitializeHttpParser(Local<Object> target,
Integer::New(env->isolate(), HTTP_REQUEST));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "RESPONSE"),
Integer::New(env->isolate(), HTTP_RESPONSE));
+ t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnMessageBegin"),
+ Integer::NewFromUnsigned(env->isolate(), kOnMessageBegin));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnHeaders"),
Integer::NewFromUnsigned(env->isolate(), kOnHeaders));
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnHeadersComplete"),
diff --git a/test/parallel/test-http-server-request-timeout-delayed-body.js b/test/parallel/test-http-server-request-timeout-delayed-body.js
new file mode 100644
index 00000000000..c6f26def86b
--- /dev/null
+++ b/test/parallel/test-http-server-request-timeout-delayed-body.js
@@ -0,0 +1,62 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const { createServer } = require('http');
+const { connect } = require('net');
+
+// This test validates that the server returns 408
+// after server.requestTimeout if the client
+// pauses before start sending the body.
+
+const server = createServer(common.mustCall((req, res) => {
+ let body = '';
+ req.setEncoding('utf-8');
+
+ req.on('data', (chunk) => {
+ body += chunk;
+ });
+
+ req.on('end', () => {
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
+ res.write(body);
+ res.end();
+ });
+}));
+
+// 0 seconds is the default
+assert.strictEqual(server.requestTimeout, 0);
+const requestTimeout = common.platformTimeout(1000);
+server.requestTimeout = requestTimeout;
+assert.strictEqual(server.requestTimeout, requestTimeout);
+
+server.listen(0, common.mustCall(() => {
+ const client = connect(server.address().port);
+ let response = '';
+
+ client.on('data', common.mustCall((chunk) => {
+ response += chunk.toString('utf-8');
+ }));
+
+ client.resume();
+ client.write('POST / HTTP/1.1\r\n');
+ client.write('Content-Length: 20\r\n');
+ client.write('Connection: close\r\n');
+ client.write('\r\n');
+
+ setTimeout(() => {
+ client.write('12345678901234567890\r\n\r\n');
+ }, common.platformTimeout(2000)).unref();
+
+ const errOrEnd = common.mustCall(function(err) {
+ console.log(err);
+ assert.strictEqual(
+ response,
+ 'HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n'
+ );
+ server.close();
+ });
+
+ client.on('end', errOrEnd);
+ client.on('error', errOrEnd);
+}));
diff --git a/test/parallel/test-http-server-request-timeout-delayed-headers.js b/test/parallel/test-http-server-request-timeout-delayed-headers.js
new file mode 100644
index 00000000000..b2c282fd2fa
--- /dev/null
+++ b/test/parallel/test-http-server-request-timeout-delayed-headers.js
@@ -0,0 +1,48 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const { createServer } = require('http');
+const { connect } = require('net');
+
+// This test validates that the server returns 408
+// after server.requestTimeout if the client
+// pauses before start sending the request.
+
+const server = createServer(common.mustNotCall());
+
+// 0 seconds is the default
+assert.strictEqual(server.requestTimeout, 0);
+const requestTimeout = common.platformTimeout(1000);
+server.requestTimeout = requestTimeout;
+assert.strictEqual(server.requestTimeout, requestTimeout);
+
+server.listen(0, common.mustCall(() => {
+ const client = connect(server.address().port);
+ let response = '';
+
+ client.on('data', common.mustCall((chunk) => {
+ response += chunk.toString('utf-8');
+ }));
+
+ const errOrEnd = common.mustCall(function(err) {
+ console.log(err);
+ assert.strictEqual(
+ response,
+ 'HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n'
+ );
+ server.close();
+ });
+
+ client.on('end', errOrEnd);
+ client.on('error', errOrEnd);
+
+ client.resume();
+
+ setTimeout(() => {
+ client.write('POST / HTTP/1.1\r\n');
+ client.write('Content-Length: 20\r\n');
+ client.write('Connection: close\r\n\r\n');
+ client.write('12345678901234567890\r\n\r\n');
+ }, common.platformTimeout(2000)).unref();
+}));
diff --git a/test/parallel/test-http-server-request-timeout-interrupted-body.js b/test/parallel/test-http-server-request-timeout-interrupted-body.js
new file mode 100644
index 00000000000..237e133fef7
--- /dev/null
+++ b/test/parallel/test-http-server-request-timeout-interrupted-body.js
@@ -0,0 +1,63 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const { createServer } = require('http');
+const { connect } = require('net');
+
+// This test validates that the server returns 408
+// after server.requestTimeout if the client
+// pauses sending in the middle of the body.
+
+const server = createServer(common.mustCall((req, res) => {
+ let body = '';
+ req.setEncoding('utf-8');
+
+ req.on('data', (chunk) => {
+ body += chunk;
+ });
+
+ req.on('end', () => {
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
+ res.write(body);
+ res.end();
+ });
+}));
+
+// 0 seconds is the default
+assert.strictEqual(server.requestTimeout, 0);
+const requestTimeout = common.platformTimeout(1000);
+server.requestTimeout = requestTimeout;
+assert.strictEqual(server.requestTimeout, requestTimeout);
+
+server.listen(0, common.mustCall(() => {
+ const client = connect(server.address().port);
+ let response = '';
+
+ client.on('data', common.mustCall((chunk) => {
+ response += chunk.toString('utf-8');
+ }));
+
+ const errOrEnd = common.mustCall(function(err) {
+ console.log(err);
+ assert.strictEqual(
+ response,
+ 'HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n'
+ );
+ server.close();
+ });
+
+ client.on('error', errOrEnd);
+ client.on('end', errOrEnd);
+
+ client.resume();
+ client.write('POST / HTTP/1.1\r\n');
+ client.write('Content-Length: 20\r\n');
+ client.write('Connection: close\r\n');
+ client.write('\r\n');
+ client.write('1234567890');
+
+ setTimeout(() => {
+ client.write('1234567890\r\n\r\n');
+ }, common.platformTimeout(2000)).unref();
+}));
diff --git a/test/parallel/test-http-server-request-timeout-interrupted-headers.js b/test/parallel/test-http-server-request-timeout-interrupted-headers.js
new file mode 100644
index 00000000000..0b8a2c315ab
--- /dev/null
+++ b/test/parallel/test-http-server-request-timeout-interrupted-headers.js
@@ -0,0 +1,48 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const { createServer } = require('http');
+const { connect } = require('net');
+
+// This test validates that the server returns 408
+// after server.requestTimeout if the client
+// pauses sending in the middle of a header.
+
+const server = createServer(common.mustNotCall());
+
+// 120 seconds is the default
+assert.strictEqual(server.requestTimeout, 0);
+const requestTimeout = common.platformTimeout(1000);
+server.requestTimeout = requestTimeout;
+assert.strictEqual(server.requestTimeout, requestTimeout);
+
+server.listen(0, common.mustCall(() => {
+ const client = connect(server.address().port);
+ let response = '';
+
+ client.on('data', common.mustCall((chunk) => {
+ response += chunk.toString('utf-8');
+ }));
+
+ const errOrEnd = common.mustCall(function(err) {
+ console.log(err);
+ assert.strictEqual(
+ response,
+ 'HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n'
+ );
+ server.close();
+ });
+
+ client.on('end', errOrEnd);
+ client.on('error', errOrEnd);
+
+ client.resume();
+ client.write('GET / HTTP/1.1\r\n');
+ client.write('Connection: close\r\n');
+ client.write('X-CRASH: ');
+
+ setTimeout(() => {
+ client.write('1234567890\r\n\r\n');
+ }, common.platformTimeout(2000)).unref();
+}));
diff --git a/test/parallel/test-http-server-request-timeout-keepalive.js b/test/parallel/test-http-server-request-timeout-keepalive.js
new file mode 100644
index 00000000000..77fde867e9b
--- /dev/null
+++ b/test/parallel/test-http-server-request-timeout-keepalive.js
@@ -0,0 +1,94 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const { createServer } = require('http');
+const { connect } = require('net');
+
+// This test validates that the server returns 408
+// after server.requestTimeout if the client
+// does not complete a request, and that keep alive
+// works properly
+
+function performRequestWithDelay(client, firstDelay, secondDelay) {
+ client.resume();
+ client.write('GET / HTTP/1.1\r\n');
+
+ firstDelay = common.platformTimeout(firstDelay);
+ secondDelay = common.platformTimeout(secondDelay);
+
+ console.log('performRequestWithDelay', firstDelay, secondDelay);
+
+ setTimeout(() => {
+ client.write('Connection: ');
+ }, firstDelay).unref();
+
+ // Complete the request
+ setTimeout(() => {
+ client.write('keep-alive\r\n\r\n');
+ }, firstDelay + secondDelay).unref();
+}
+
+const server = createServer(common.mustCallAtLeast((req, res) => {
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
+ res.end();
+}));
+
+// 0 seconds is the default
+assert.strictEqual(server.requestTimeout, 0);
+const requestTimeout = common.platformTimeout(1000);
+server.requestTimeout = requestTimeout;
+assert.strictEqual(server.requestTimeout, requestTimeout);
+
+// Make sure keepAliveTimeout is big enough for the requestTimeout.
+server.keepAliveTimeout = 0;
+
+server.listen(0, common.mustCall(() => {
+ const client = connect(server.address().port);
+ let second = false;
+ let response = '';
+
+ client.on('data', common.mustCallAtLeast((chunk) => {
+ response += chunk.toString('utf-8');
+
+ // First response has ended
+ if (!second && response.endsWith('\r\n\r\n')) {
+ assert.strictEqual(
+ response.split('\r\n')[0],
+ 'HTTP/1.1 200 OK'
+ );
+
+ const defer = common.platformTimeout(server.requestTimeout * 1.5);
+
+ console.log('defer by', defer);
+
+ // Wait some time to make sure requestTimeout
+ // does not interfere with keep alive
+ setTimeout(() => {
+ response = '';
+ second = true;
+
+ // Perform a second request expected to finish after requestTimeout
+ performRequestWithDelay(client, 1000, 3000);
+ }, defer).unref();
+ }
+ }, 1));
+
+ const errOrEnd = common.mustCall(function(err) {
+ console.log(err);
+ assert.strictEqual(second, true);
+ assert.strictEqual(
+ response,
+ // Empty because of https://github.com/nodejs/node/commit/e8d7fedf7cad6e612e4f2e0456e359af57608ac7
+ // 'HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n'
+ ''
+ );
+ server.close();
+ });
+
+ client.on('error', errOrEnd);
+ client.on('end', errOrEnd);
+
+ // Perform a second request expected to finish before requestTimeout
+ performRequestWithDelay(client, 50, 500);
+}));
diff --git a/test/parallel/test-http-server-request-timeout-upgrade.js b/test/parallel/test-http-server-request-timeout-upgrade.js
new file mode 100644
index 00000000000..dd7269621e2
--- /dev/null
+++ b/test/parallel/test-http-server-request-timeout-upgrade.js
@@ -0,0 +1,55 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const { createServer } = require('http');
+const { connect } = require('net');
+
+// This test validates that the requestTimeoout
+// is disabled after the connection is upgraded.
+
+const server = createServer(common.mustNotCall());
+
+// 0 seconds is the default
+assert.strictEqual(server.requestTimeout, 0);
+const requestTimeout = common.platformTimeout(1000);
+server.requestTimeout = requestTimeout;
+assert.strictEqual(server.requestTimeout, requestTimeout);
+
+server.on('upgrade', common.mustCall((req, socket, head) => {
+ socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n');
+ socket.write('Upgrade: WebSocket\r\n');
+ socket.write('Connection: Upgrade\r\n\r\n');
+ socket.pipe(socket);
+}));
+
+server.listen(0, common.mustCall(() => {
+ const client = connect(server.address().port);
+ let response = '';
+
+ client.on('data', common.mustCallAtLeast((chunk) => {
+ response += chunk.toString('utf-8');
+ }, 1));
+
+ client.on('end', common.mustCall(() => {
+ assert.strictEqual(
+ response,
+ 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' +
+ 'Upgrade: WebSocket\r\n' +
+ 'Connection: Upgrade\r\n\r\n' +
+ '12345678901234567890'
+ );
+
+ server.close();
+ }));
+
+ client.resume();
+ client.write('GET / HTTP/1.1\r\n');
+ client.write('Upgrade: WebSocket\r\n');
+ client.write('Connection: Upgrade\r\n\r\n');
+
+ setTimeout(() => {
+ client.write('12345678901234567890');
+ client.end();
+ }, common.platformTimeout(2000)).unref();
+}));