net: add new options to net.Socket and net.Server

PR-URL: https://github.com/nodejs/node/pull/41310
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Paolo Insogna 2022-02-22 11:58:30 +01:00 committed by GitHub
parent c071bd581a
commit 45b5ca810a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 202 additions and 13 deletions

View File

@ -2865,6 +2865,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}
@ -3108,6 +3118,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.

View File

@ -854,6 +854,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:
@ -1415,8 +1423,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.
@ -1582,6 +1600,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

View File

@ -378,7 +378,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);

View File

@ -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);
@ -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
@ -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;
@ -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);
@ -504,31 +518,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;
};
@ -1141,6 +1162,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');
@ -1204,6 +1233,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;
@ -1215,6 +1253,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);
@ -1567,6 +1608,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;

View File

@ -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();
}));
}));
}

View File

@ -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();
}));
}));
}