Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
net: add new options to net.Socket and net.Server
PR-URL: #41310
Backport-PR-URL: #42298
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
ShogunPanda authored and danielleadams committed Apr 11, 2022
1 parent 85e3624 commit b3afb20
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 13 deletions.
12 changes: 12 additions & 0 deletions doc/api/http.md
Expand Up @@ -2851,6 +2851,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 +3102,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
19 changes: 19 additions & 0 deletions doc/api/net.md
Expand Up @@ -826,6 +826,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 +1389,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 +1566,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
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
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
@@ -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
@@ -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();
}));
}));
}

0 comments on commit b3afb20

Please sign in to comment.