From 10815c4eff5361a18bafb718287c947e7b5f6e1e Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Thu, 30 Jul 2020 16:58:08 +0200 Subject: [PATCH] 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 PR-URL: https://github.com/nodejs/node/pull/34561 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina Reviewed-By: Luigi Pinca Reviewed-By: Zeyu Yang Reviewed-By: Trivikram Kamat Reviewed-By: Pranshu Srivastava --- 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 442369581d3779..8de389e1f09dd4 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -28,6 +28,7 @@ const { ObjectKeys, ObjectPrototypeHasOwnProperty, ObjectSetPrototypeOf, + MathFloor, Symbol, } = primordials; @@ -117,6 +118,8 @@ function OutgoingMessage() { this._header = null; this[kOutHeaders] = null; + this._keepAliveTimeout = 0; + this._onPendingData = noopPendingOutput; } ObjectSetPrototypeOf(OutgoingMessage.prototype, Stream.prototype); @@ -402,6 +405,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) / 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 c707ca3f9949b1..4d1f8e4fbd7077 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -740,6 +740,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..fccb267b8e9ee2 --- /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=12'); + server.close(); + agent.destroy(); + })); +}));