From 9719ec1809f330c0436c96632518f1b19626b3e3 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Thu, 30 Jul 2020 16:58:08 +0200 Subject: [PATCH 1/3] http: provide keep-alive timeout response header In http 1.1 persistent connection protocol there is a timing race where the client sends the request and then the server kills the connection (due to inactivity) before receiving the client's request. By providing a keep-alive header it is possible to provide the client a hint of when idle timeout would occur and avoid the race. Fixes: https://github.com/nodejs/node/issues/34560 --- lib/_http_outgoing.js | 7 +++++ lib/_http_server.js | 1 + test/parallel/test-http-keep-alive-timeout.js | 28 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 test/parallel/test-http-keep-alive-timeout.js diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 868c74d3518cc0..d40105034c82e7 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -28,6 +28,7 @@ const { ObjectKeys, ObjectPrototypeHasOwnProperty, ObjectSetPrototypeOf, + MathFloor, Symbol, } = primordials; @@ -123,6 +124,8 @@ function OutgoingMessage() { this._header = null; this[kOutHeaders] = null; + this._keepAliveTimeout = 0; + this._onPendingData = noopPendingOutput; } ObjectSetPrototypeOf(OutgoingMessage.prototype, Stream.prototype); @@ -424,6 +427,10 @@ function _storeHeader(firstLine, headers) { (state.contLen || this.useChunkedEncodingByDefault || this.agent); if (shouldSendKeepAlive) { header += 'Connection: keep-alive\r\n'; + if (this._keepAliveTimeout) { + const timeoutSeconds = MathFloor(this._keepAliveTimeout - 500) / 1000; + header += `Keep-Alive: timeout=${timeoutSeconds}\r\n`; + } } else { this._last = true; header += 'Connection: close\r\n'; diff --git a/lib/_http_server.js b/lib/_http_server.js index 084b67b6b3a6fe..0da43c3e3a8159 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -766,6 +766,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { } const res = new server[kServerResponse](req); + res._keepAliveTimeout = server.keepAliveTimeout; res._onPendingData = updateOutgoingData.bind(undefined, socket, state); res.shouldKeepAlive = keepAlive; diff --git a/test/parallel/test-http-keep-alive-timeout.js b/test/parallel/test-http-keep-alive-timeout.js new file mode 100644 index 00000000000000..03e497231b323d --- /dev/null +++ b/test/parallel/test-http-keep-alive-timeout.js @@ -0,0 +1,28 @@ +'use strict'; + +const common = require('../common'); +const http = require('http'); +const assert = require('assert'); + +const server = http.createServer(common.mustCall((req, res) => { + const body = 'hello world\n'; + + res.writeHead(200, { 'Content-Length': body.length }); + res.write(body); + res.end(); +})); +server.keepAliveTimeout = 12000; + +const agent = new http.Agent({ maxSockets: 1, keepAlive: true }); + +server.listen(0, common.mustCall(function() { + http.get({ + path: '/', port: this.address().port, agent: agent + }, common.mustCall((response) => { + response.resume(); + assert.strictEqual( + response.headers['keep-alive'], 'timeout=11.5'); + server.close(); + agent.destroy(); + })); +})); From 003e581088b6da260b11b37c8a9b2f80ac7cd90e Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Thu, 30 Jul 2020 17:05:41 +0200 Subject: [PATCH 2/3] fixup --- lib/_http_outgoing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index d40105034c82e7..f444be8687db34 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -428,7 +428,7 @@ function _storeHeader(firstLine, headers) { if (shouldSendKeepAlive) { header += 'Connection: keep-alive\r\n'; if (this._keepAliveTimeout) { - const timeoutSeconds = MathFloor(this._keepAliveTimeout - 500) / 1000; + const timeoutSeconds = MathFloor(this._keepAliveTimeout) / 1000; header += `Keep-Alive: timeout=${timeoutSeconds}\r\n`; } } else { From b4e8b79fcf0e5f393cf11655f7172fb1d5274a63 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Thu, 30 Jul 2020 18:08:07 +0200 Subject: [PATCH 3/3] fixup --- test/parallel/test-http-keep-alive-timeout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-http-keep-alive-timeout.js b/test/parallel/test-http-keep-alive-timeout.js index 03e497231b323d..fccb267b8e9ee2 100644 --- a/test/parallel/test-http-keep-alive-timeout.js +++ b/test/parallel/test-http-keep-alive-timeout.js @@ -21,7 +21,7 @@ server.listen(0, common.mustCall(function() { }, common.mustCall((response) => { response.resume(); assert.strictEqual( - response.headers['keep-alive'], 'timeout=11.5'); + response.headers['keep-alive'], 'timeout=12'); server.close(); agent.destroy(); }));