Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v16.x backport] net: add new options to net.Socket and net.Server #42298

Closed
wants to merge 2 commits into from
Closed
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
16 changes: 16 additions & 0 deletions doc/api/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -2820,6 +2820,10 @@ Found'`.
<!-- YAML
added: v0.1.13
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/41310
description: The `noDelay`, `keepAlive` and `keepAliveInitialDelay`
options are supported now.
- version:
- v13.8.0
- v12.15.0
Expand Down Expand Up @@ -2851,6 +2855,16 @@ changes:
[`--max-http-header-size`][] for requests received by this server, i.e.
the maximum length of request headers in bytes.
**Default:** 16384 (16 KB).
* `noDelay` {boolean} If set to `true`, it disables the use of Nagle's
algorithm immediately after a new incoming connection is received.
**Default:** `false`.
* `keepAlive` {boolean} If set to `true`, it enables keep-alive functionality
on the socket immediately after a new incoming connection is received,
similarly on what is done in \[`socket.setKeepAlive([enable][, initialDelay])`]\[`socket.setKeepAlive(enable, initialDelay)`].
**Default:** `false`.
* `keepAliveInitialDelay` {number} If set to a positive number, it sets the
initial delay before the first keepalive probe is sent on an idle socket.
**Default:** `0`.

* `requestListener` {Function}

Expand Down Expand Up @@ -3092,6 +3106,8 @@ changes:
* `callback` {Function}
* Returns: {http.ClientRequest}

`options` in [`socket.connect()`][] are also supported.

Node.js maintains several connections per server to make HTTP requests.
This function allows one to transparently issue requests.

Expand Down
23 changes: 23 additions & 0 deletions doc/api/net.md
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,10 @@ behavior.
<!-- YAML
added: v0.1.90
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/41310
description: The `noDelay`, `keepAlive` and `keepAliveInitialDelay`
options are supported now.
- version: v12.10.0
pr-url: https://github.com/nodejs/node/pull/25436
description: Added `onread` option.
Expand Down Expand Up @@ -826,6 +830,14 @@ For TCP connections, available `options` are:
`0` indicates that both IPv4 and IPv6 addresses are allowed. **Default:** `0`.
* `hints` {number} Optional [`dns.lookup()` hints][].
* `lookup` {Function} Custom lookup function. **Default:** [`dns.lookup()`][].
* `noDelay` {boolean} If set to `true`, it disables the use of Nagle's algorithm immediately
after the socket is established. **Default:** `false`.
* `keepAlive` {boolean} If set to `true`, it enables keep-alive functionality on the socket
immediately after the connection is established, similarly on what is done in
[`socket.setKeepAlive([enable][, initialDelay])`][`socket.setKeepAlive(enable, initialDelay)`].
**Default:** `false`.
* `keepAliveInitialDelay` {number} If set to a positive number, it sets the initial delay before
the first keepalive probe is sent on an idle socket.**Default:** `0`.

For [IPC][] connections, available `options` are:

Expand Down Expand Up @@ -1381,8 +1393,18 @@ added: v0.5.0
**Default:** `false`.
* `pauseOnConnect` {boolean} Indicates whether the socket should be
paused on incoming connections. **Default:** `false`.
* `noDelay` {boolean} If set to `true`, it disables the use of Nagle's algorithm immediately
after a new incoming connection is received. **Default:** `false`.
* `keepAlive` {boolean} If set to `true`, it enables keep-alive functionality on the socket
immediately after a new incoming connection is received, similarly on what is done in
[`socket.setKeepAlive([enable][, initialDelay])`][`socket.setKeepAlive(enable, initialDelay)`].
**Default:** `false`.
* `keepAliveInitialDelay` {number} If set to a positive number, it sets the initial delay before
the first keepalive probe is sent on an idle socket.**Default:** `0`.

* `connectionListener` {Function} Automatically set as a listener for the
[`'connection'`][] event.

* Returns: {net.Server}

Creates a new TCP or [IPC][] server.
Expand Down Expand Up @@ -1548,6 +1570,7 @@ net.isIPv6('fhqwhgads'); // returns false
[`socket.pause()`]: #socketpause
[`socket.resume()`]: #socketresume
[`socket.setEncoding()`]: #socketsetencodingencoding
[`socket.setKeepAlive(enable, initialDelay)`]: #socketsetkeepaliveenable-initialdelay
[`socket.setTimeout()`]: #socketsettimeouttimeout-callback
[`socket.setTimeout(timeout)`]: #socketsettimeouttimeout-callback
[`writable.destroy()`]: stream.md#writabledestroyerror
Expand Down
6 changes: 5 additions & 1 deletion lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,11 @@ function Server(options, requestListener) {
}

storeHTTPOptions.call(this, options);
net.Server.call(this, { allowHalfOpen: true });
net.Server.call(
this,
{ allowHalfOpen: true, noDelay: options.noDelay,
keepAlive: options.keepAlive,
keepAliveInitialDelay: options.keepAliveInitialDelay });

if (requestListener) {
this.on('request', requestListener);
Expand Down
73 changes: 61 additions & 12 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ function initSocketHandle(self) {
const kBytesRead = Symbol('kBytesRead');
const kBytesWritten = Symbol('kBytesWritten');
const kSetNoDelay = Symbol('kSetNoDelay');
const kSetKeepAlive = Symbol('kSetKeepAlive');
const kSetKeepAliveInitialDelay = Symbol('kSetKeepAliveInitialDelay');

function Socket(options) {
if (!(this instanceof Socket)) return new Socket(options);
Expand All @@ -297,6 +299,15 @@ function Socket(options) {
'is not supported'
);
}
if (typeof options?.keepAliveInitialDelay !== 'undefined') {
validateNumber(
options?.keepAliveInitialDelay, 'options.keepAliveInitialDelay'
);

if (options.keepAliveInitialDelay < 0) {
options.keepAliveInitialDelay = 0;
}
}

this.connecting = false;
// Problem with this is that users can supply their own handle, that may not
Expand All @@ -307,7 +318,6 @@ function Socket(options) {
this[kHandle] = null;
this._parent = null;
this._host = null;
this[kSetNoDelay] = false;
this[kLastWriteQueueSize] = 0;
this[kTimeout] = null;
this[kBuffer] = null;
Expand Down Expand Up @@ -381,6 +391,10 @@ function Socket(options) {
this[kBufferCb] = onread.callback;
}

this[kSetNoDelay] = Boolean(options.noDelay);
this[kSetKeepAlive] = Boolean(options.keepAlive);
this[kSetKeepAliveInitialDelay] = ~~(options.keepAliveInitialDelay / 1000);

// Shut down the socket when we're finished with it.
this.on('end', onReadableStreamEnd);

Expand Down Expand Up @@ -503,31 +517,38 @@ Socket.prototype._onTimeout = function() {


Socket.prototype.setNoDelay = function(enable) {
// Backwards compatibility: assume true when `enable` is omitted
enable = Boolean(enable === undefined ? true : enable);

if (!this._handle) {
this.once('connect',
enable ? this.setNoDelay : () => this.setNoDelay(enable));
this[kSetNoDelay] = enable;
return this;
}

// Backwards compatibility: assume true when `enable` is omitted
const newValue = enable === undefined ? true : !!enable;
if (this._handle.setNoDelay && newValue !== this[kSetNoDelay]) {
this[kSetNoDelay] = newValue;
this._handle.setNoDelay(newValue);
if (this._handle.setNoDelay && enable !== this[kSetNoDelay]) {
this[kSetNoDelay] = enable;
this._handle.setNoDelay(enable);
}

return this;
};


Socket.prototype.setKeepAlive = function(setting, msecs) {
Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs) {
enable = Boolean(enable);
const initialDelay = ~~(initialDelayMsecs / 1000);

if (!this._handle) {
this.once('connect', () => this.setKeepAlive(setting, msecs));
this[kSetKeepAlive] = enable;
this[kSetKeepAliveInitialDelay] = initialDelay;
return this;
}

if (this._handle.setKeepAlive)
this._handle.setKeepAlive(setting, ~~(msecs / 1000));
if (this._handle.setKeepAlive && enable !== this[kSetKeepAlive]) {
this[kSetKeepAlive] = enable;
this[kSetKeepAliveInitialDelay] = initialDelay;
this._handle.setKeepAlive(enable, initialDelay);
}

return this;
};
Expand Down Expand Up @@ -1140,6 +1161,14 @@ function afterConnect(status, handle, req, readable, writable) {
}
self._unrefTimer();

if (self[kSetNoDelay] && self._handle.setNoDelay) {
self._handle.setNoDelay(true);
}

if (self[kSetKeepAlive] && self._handle.setKeepAlive) {
self._handle.setKeepAlive(true, self[kSetKeepAliveInitialDelay]);
}

self.emit('connect');
self.emit('ready');

Expand Down Expand Up @@ -1203,6 +1232,15 @@ function Server(options, connectionListener) {
} else {
throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
}
if (typeof options.keepAliveInitialDelay !== 'undefined') {
validateNumber(
options.keepAliveInitialDelay, 'options.keepAliveInitialDelay'
);

if (options.keepAliveInitialDelay < 0) {
options.keepAliveInitialDelay = 0;
}
}

this._connections = 0;

Expand All @@ -1214,6 +1252,9 @@ function Server(options, connectionListener) {

this.allowHalfOpen = options.allowHalfOpen || false;
this.pauseOnConnect = !!options.pauseOnConnect;
this.noDelay = Boolean(options.noDelay);
this.keepAlive = Boolean(options.keepAlive);
this.keepAliveInitialDelay = ~~(options.keepAliveInitialDelay / 1000);
}
ObjectSetPrototypeOf(Server.prototype, EventEmitter.prototype);
ObjectSetPrototypeOf(Server, EventEmitter);
Expand Down Expand Up @@ -1565,6 +1606,14 @@ function onconnection(err, clientHandle) {
writable: true
});

if (self.noDelay && handle.setNoDelay) {
handle.setNoDelay(true);
}

if (self.keepAlive && self.setKeepAlive) {
handle.setKeepAlive(true, handle.keepAliveInitialDelay);
}

self._connections++;
socket.server = self;
socket._server = self;
Expand Down
56 changes: 56 additions & 0 deletions test/parallel/test-net-connect-keepalive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict';

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

const truthyValues = [true, 1, 'true', {}, []];
const delays = [[123, 0], [456123, 456], [-123000, 0], [undefined, 0]];
const falseyValues = [false, 0, ''];

const genSetKeepAlive = (desiredEnable, desiredDelay) => (enable, delay) => {
assert.strictEqual(enable, desiredEnable);
assert.strictEqual(delay, desiredDelay);
};

for (const value of truthyValues) {
for (const delay of delays) {
const server = net.createServer();

server.listen(0, common.mustCall(function() {
const port = server.address().port;

const client = net.connect(
{ port, keepAlive: value, keepAliveInitialDelay: delay[0] },
common.mustCall(() => client.end())
);

client._handle.setKeepAlive = common.mustCall(
genSetKeepAlive(true, delay[1])
);

client.on('end', common.mustCall(function() {
server.close();
}));
}));
}
}

for (const value of falseyValues) {
const server = net.createServer();

server.listen(0, common.mustCall(function() {
const port = server.address().port;

const client = net.connect(
{ port, keepAlive: value },
common.mustCall(() => client.end())
);

client._handle.setKeepAlive = common.mustNotCall();

client.on('end', common.mustCall(function() {
server.close();
}));
}));
}
49 changes: 49 additions & 0 deletions test/parallel/test-net-connect-nodelay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

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

const truthyValues = [true, 1, 'true', {}, []];
const falseyValues = [false, 0, ''];
const genSetNoDelay = (desiredArg) => (enable) => {
assert.strictEqual(enable, desiredArg);
};

for (const value of truthyValues) {
const server = net.createServer();

server.listen(0, common.mustCall(function() {
const port = server.address().port;

const client = net.connect(
{ port, noDelay: value },
common.mustCall(() => client.end())
);

client._handle.setNoDelay = common.mustCall(genSetNoDelay(true));

client.on('end', common.mustCall(function() {
server.close();
}));
}));
}

for (const value of falseyValues) {
const server = net.createServer();

server.listen(0, common.mustCall(function() {
const port = server.address().port;

const client = net.connect(
{ port, noDelay: value },
common.mustCall(() => client.end())
);

client._handle.setNoDelay = common.mustNotCall();

client.on('end', common.mustCall(function() {
server.close();
}));
}));
}