Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions doc/api/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -1942,18 +1942,39 @@ added: v8.0.0

The number of milliseconds of inactivity a server needs to wait for additional
incoming data, after it has finished writing the last response, before a socket
will be destroyed. If the server receives new data before the keep-alive
timeout has fired, it will reset the regular inactivity timeout, i.e.,
[`server.timeout`][].
will be destroyed.

This timeout value is combined with the
\[`server.keepAliveTimeoutBuffer`]\[] option to determine the actual socket
timeout, calculated as:
socketTimeout = keepAliveTimeout + keepAliveTimeoutBuffer
If the server receives new data before the keep-alive timeout has fired, it
will reset the regular inactivity timeout, i.e., [`server.timeout`][].

A value of `0` will disable the keep-alive timeout behavior on incoming
connections.
A value of `0` makes the http server behave similarly to Node.js versions prior
A value of `0` makes the HTTP server behave similarly to Node.js versions prior
to 8.0.0, which did not have a keep-alive timeout.

The socket timeout logic is set up on connection, so changing this value only
affects new connections to the server, not any existing connections.

### `server.keepAliveTimeoutBuffer`

<!-- YAML
added: REPLACEME
-->

* Type: {number} Timeout in milliseconds. **Default:** `1000` (1 second).

An additional buffer time added to the
[`server.keepAliveTimeout`][] to extend the internal socket timeout.

This buffer helps reduce connection reset (`ECONNRESET`) errors by increasing
the socket timeout slightly beyond the advertised keep-alive timeout.

This option applies only to new incoming connections.

### `server[Symbol.asyncDispose]()`

<!-- YAML
Expand Down
23 changes: 19 additions & 4 deletions lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const {
ArrayIsArray,
Error,
MathMin,
NumberIsFinite,
ObjectKeys,
ObjectSetPrototypeOf,
ReflectApply,
Expand Down Expand Up @@ -184,8 +185,6 @@ const kConnections = Symbol('http.server.connections');
const kConnectionsCheckingInterval = Symbol('http.server.connectionsCheckingInterval');

const HTTP_SERVER_TRACE_EVENT_NAME = 'http.server.request';
// TODO(jazelly): make this configurable
const HTTP_SERVER_KEEP_ALIVE_TIMEOUT_BUFFER = 1000;

class HTTPServerAsyncResource {
constructor(type, socket) {
Expand Down Expand Up @@ -484,6 +483,14 @@ function storeHTTPOptions(options) {
this.keepAliveTimeout = 5_000; // 5 seconds;
}

const keepAliveTimeoutBuffer = options.keepAliveTimeoutBuffer;
if (keepAliveTimeoutBuffer !== undefined) {
validateInteger(keepAliveTimeoutBuffer, 'keepAliveTimeoutBuffer', 0);
this.keepAliveTimeoutBuffer = keepAliveTimeoutBuffer;
} else {
this.keepAliveTimeoutBuffer = 1000;
}

const connectionsCheckingInterval = options.connectionsCheckingInterval;
if (connectionsCheckingInterval !== undefined) {
validateInteger(connectionsCheckingInterval, 'connectionsCheckingInterval', 0);
Expand Down Expand Up @@ -546,6 +553,13 @@ function Server(options, requestListener) {
}

storeHTTPOptions.call(this, options);

// Optional buffer added to the keep-alive timeout when setting socket timeouts.
// Helps reduce ECONNRESET errors from clients by extending the internal timeout.
// Default is 1000ms if not specified.
const buf = options.keepAliveTimeoutBuffer;
this.keepAliveTimeoutBuffer =
(typeof buf === 'number' && NumberIsFinite(buf) && buf >= 0) ? buf : 1000;
net.Server.call(
this,
{ allowHalfOpen: true, noDelay: options.noDelay ?? true,
Expand Down Expand Up @@ -1012,9 +1026,10 @@ function resOnFinish(req, res, socket, state, server) {
}
} else if (state.outgoing.length === 0) {
if (server.keepAliveTimeout && typeof socket.setTimeout === 'function') {
// Increase the internal timeout wrt the advertised value to reduce
// Extend the internal timeout by the configured buffer to reduce
// the likelihood of ECONNRESET errors.
socket.setTimeout(server.keepAliveTimeout + HTTP_SERVER_KEEP_ALIVE_TIMEOUT_BUFFER);
// This allows fine-tuning beyond the advertised keepAliveTimeout.
socket.setTimeout(server.keepAliveTimeout + server.keepAliveTimeoutBuffer);
state.keepAliveTimeoutSet = true;
}
} else {
Expand Down
39 changes: 39 additions & 0 deletions test/parallel/test-http-keep-alive-timeout-buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';

const common = require('../common');
const http = require('http');
const assert = require('assert');

const server = http.createServer(common.mustCall((req, res) => {
const body = 'buffer test\n';

res.writeHead(200, { 'Content-Length': body.length });
res.write(body);
res.end();
}));

server.keepAliveTimeout = 100;

if (server.keepAliveTimeoutBuffer === undefined) {
server.keepAliveTimeoutBuffer = 1000;
}
assert.strictEqual(server.keepAliveTimeoutBuffer, 1000);

server.listen(0, () => {
http.get({
port: server.address().port,
path: '/',
}, (res) => {
res.resume();
server.close();
});
});

{
const customBuffer = 3000;
const server = http.createServer(() => {});
server.keepAliveTimeout = 200;
server.keepAliveTimeoutBuffer = customBuffer;
assert.strictEqual(server.keepAliveTimeoutBuffer, customBuffer);
server.close();
}
Loading