diff --git a/CHANGES.md b/CHANGES.md index d3ab2a4..365e397 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ - feat(protocol): added unix socket supports. - feat(protocol): accepts socket factory function to produce sockets. - feat(connection): added ended/finished properties and finished event. +- feat(connection): added timeout property and timeout event. - feat(encoder): accepts string as data to send. - feat(connector): added `IConnection.writable` property. diff --git a/src/examples/tcp/server.ts b/src/examples/tcp/server.ts index 2900682..f29e8cd 100644 --- a/src/examples/tcp/server.ts +++ b/src/examples/tcp/server.ts @@ -19,6 +19,7 @@ import * as LwDFX from '../../lib'; const server = LwDFX.createServer({ alpWhitelist: ['b1', 'b2'], maxFrameSize: 1024, + timeout: 500, }); const tcpGateway = LwDFX.Tcp.createGateway(server, { diff --git a/src/examples/tls/server.ts b/src/examples/tls/server.ts index 691dcdc..7b57c4c 100644 --- a/src/examples/tls/server.ts +++ b/src/examples/tls/server.ts @@ -20,6 +20,7 @@ import * as FS from 'node:fs'; const server = LwDFX.createServer({ alpWhitelist: ['b1', 'b2'], maxFrameSize: 1024, + timeout: 500, }); const gateway = LwDFX.Tls.createGateway(server, { diff --git a/src/examples/unixsocket/server.ts b/src/examples/unixsocket/server.ts index f25c87f..e67bb39 100644 --- a/src/examples/unixsocket/server.ts +++ b/src/examples/unixsocket/server.ts @@ -19,6 +19,7 @@ import * as LwDFX from '../../lib'; const server = LwDFX.createServer({ alpWhitelist: ['b1', 'b2'], maxFrameSize: 1024, + timeout: 500, }); const socketGateway = LwDFX.UnixSocket.createGateway(server, { diff --git a/src/lib/AbstractConnection.ts b/src/lib/AbstractConnection.ts index 217c971..be1660e 100644 --- a/src/lib/AbstractConnection.ts +++ b/src/lib/AbstractConnection.ts @@ -51,7 +51,7 @@ export abstract class AbstractConnection extends $Events.EventEmitter implements public constructor( socket: $Net.Socket, - public timeout: number + private _timeout: number ) { super(); @@ -61,6 +61,22 @@ export abstract class AbstractConnection extends $Events.EventEmitter implements socket.setNoDelay(true); } + public get timeout(): number { + + return this._timeout; + } + + public set timeout(v: number) { + + if (!Number.isSafeInteger(v) || v < 0) { + + throw new LwDFXError('invalid_timeout', 'Invalid timeout value.'); + } + + this._timeout = v; + this._socket?.setTimeout(v); + } + public get finished(): boolean { return this._socket?.writableFinished ?? true; @@ -172,8 +188,9 @@ export abstract class AbstractConnection extends $Events.EventEmitter implements this._socket! .removeAllListeners() - .setTimeout(this.timeout, () => { + .on('timeout', () => { + this.emit('timeout'); this._socket?.destroy(new LwDFXError('timeout', 'Connection timeout')); }) .on('close', () => { @@ -201,6 +218,11 @@ export abstract class AbstractConnection extends $Events.EventEmitter implements this.destroy(); } }); + + if (this._timeout) { + + this._socket!.setTimeout(this._timeout); + } } protected abstract _handshake( @@ -237,23 +259,18 @@ export abstract class AbstractConnection extends $Events.EventEmitter implements callback = once(callback); - const timer = setTimeout(() => { - - callback(new LwDFXError('timeout', 'Handshake timeout')); + if (handshakeTimeout > 0) { - }, handshakeTimeout); + this._socket!.setTimeout(handshakeTimeout, () => { - this._socket!.setTimeout(handshakeTimeout, () => { - - this._socket!.destroy(new LwDFXError('timeout', 'Handshake timeout')); - }); + this._socket!.destroy(new LwDFXError('timeout', 'Handshake timeout')); + }); + } this._socket!.on('close', () => { callback(new LwDFXError('conn_lost', 'Connection closed')); - clearTimeout(timer); - this._socket?.removeAllListeners(); this._socket = null; }); @@ -272,8 +289,6 @@ export abstract class AbstractConnection extends $Events.EventEmitter implements return; } - clearTimeout(timer); - this._setupSocket(); callback(null, this); diff --git a/src/lib/Decl.ts b/src/lib/Decl.ts index f5a9b67..72e23e7 100644 --- a/src/lib/Decl.ts +++ b/src/lib/Decl.ts @@ -77,6 +77,11 @@ export interface IConnection { */ readonly ended: boolean; + /** + * The timeout in milliseconds for the connection. + */ + timeout: number; + /** * Register a callback for the `frame` event. * @@ -99,7 +104,7 @@ export interface IConnection { * @param event The event name. * @param callback The callback function. */ - on(event: 'end' | 'finish', callback: () => void): this; + on(event: 'end' | 'finish' | 'timeout', callback: () => void): this; /** * Register a callback for the `error` event. @@ -159,7 +164,9 @@ export interface IConnectOptions { alpWhitelist?: string[]; /** - * The timeout in milliseconds for the connections. + * The timeout in milliseconds for the connections after connection is established. + * + * > Timeout means the connection is idle for a long time, and the connection will be closed. * * > Set to `0` to disable the timeout. * @@ -169,7 +176,9 @@ export interface IConnectOptions { timeout?: number; /** - * The timeout in milliseconds for the handshake process. + * The timeout in milliseconds during the handshake process. + * + * > Timeout means the connection is idle for a long time, and the connection will be closed. * * > Set to `0` to disable the timeout. * diff --git a/src/lib/Server.ts b/src/lib/Server.ts index dfa2283..07191fa 100644 --- a/src/lib/Server.ts +++ b/src/lib/Server.ts @@ -45,7 +45,7 @@ class LwDFXServer extends $Events.EventEmitter implements D.IServer { public constructor( public alpWhitelist: string[], public maxConnections: number, - public timeout: number, + private _timeout: number, public handshakeTimeout: number, public maxFrameSize: number, ) { @@ -53,6 +53,21 @@ class LwDFXServer extends $Events.EventEmitter implements D.IServer { super(); } + public get timeout(): number { + + return this._timeout; + } + + public set timeout(v: number) { + + if (!Number.isSafeInteger(v) || v < 0) { + + throw new LwDFXError('invalid_timeout', 'Invalid timeout value.'); + } + + this._timeout = v; + } + public get connections(): number { return Object.keys(this._conns).length; @@ -85,7 +100,7 @@ class LwDFXServer extends $Events.EventEmitter implements D.IServer { return; } - const conn = new ServerConnection(this._generateNextId(), socket, this.timeout, this.maxFrameSize); + const conn = new ServerConnection(this._generateNextId(), socket, this._timeout, this.maxFrameSize); conn.setup(this.alpWhitelist, this.handshakeTimeout, (err) => { diff --git a/src/lib/Tcp/TcpClient.ts b/src/lib/Tcp/TcpClient.ts index aafabc2..e498411 100644 --- a/src/lib/Tcp/TcpClient.ts +++ b/src/lib/Tcp/TcpClient.ts @@ -51,6 +51,13 @@ export interface ITcpClientOptions extends D.IConnectOptions { * @default null */ socket?: $Net.Socket | D.ISocketFactory | null; + + /** + * The timeout for connecting to the server, in milliseconds. + * + * @default 30000 + */ + connectTimeout?: number; } function netConnect(opts: ITcpClientOptions): Promise<$Net.Socket> { @@ -72,7 +79,6 @@ function netConnect(opts: ITcpClientOptions): Promise<$Net.Socket> { const socket = $Net.connect({ 'host': opts.hostname ?? C.DEFAULT_HOSTNAME, 'port': opts.port ?? C.DEFAULT_PORT, - 'timeout': opts.handshakeTimeout ?? Constants.DEFAULT_HANDSHAKE_TIMEOUT, }, () => { socket.removeAllListeners('error'); @@ -82,6 +88,16 @@ function netConnect(opts: ITcpClientOptions): Promise<$Net.Socket> { resolve(socket); }); + const connectTimeout = opts.connectTimeout ?? C.DEFAULT_CONNECT_TIMEOUT; + + if (connectTimeout) { + + socket.setTimeout(connectTimeout, () => { + + socket.destroy(new LwDFXError('connect_timeout', 'Timeout for connecting to remote server')); + }); + } + socket.on('error', (e) => { socket.removeAllListeners('error'); diff --git a/src/lib/Tcp/TcpCommon.ts b/src/lib/Tcp/TcpCommon.ts index af1411d..2b40398 100644 --- a/src/lib/Tcp/TcpCommon.ts +++ b/src/lib/Tcp/TcpCommon.ts @@ -34,3 +34,10 @@ export const DEFAULT_HOSTNAME: string = 'localhost'; * @type uint32 */ export const DEFAULT_BACKLOG: number = 1023; + +/** + * The default timeout for connecting to the server, in milliseconds. + * + * @type uint32 + */ +export const DEFAULT_CONNECT_TIMEOUT: number = 30_000; diff --git a/src/lib/Tls/TlsClient.ts b/src/lib/Tls/TlsClient.ts index d34089b..23a5ffa 100644 --- a/src/lib/Tls/TlsClient.ts +++ b/src/lib/Tls/TlsClient.ts @@ -51,6 +51,13 @@ export interface ITlsClientOptions extends D.IConnectOptions { */ socket?: $Tls.TLSSocket | D.ISocketFactory | null; + /** + * The timeout for connecting to the server, in milliseconds. + * + * @default 30000 + */ + connectTimeout?: number; + /** * The TLS options for new connections. */ @@ -76,7 +83,6 @@ function netConnect(opts: ITlsClientOptions): Promise<$Net.Socket> { const socket = $Tls.connect(opts.port ?? C.DEFAULT_PORT, opts.hostname ?? C.DEFAULT_HOSTNAME, { // eslint-disable-next-line @typescript-eslint/naming-convention 'ALPNProtocols': [C.DEFAULT_ALPN_PROTOCOL], - 'timeout': opts.handshakeTimeout ?? Constants.DEFAULT_HANDSHAKE_TIMEOUT, ...(opts.tlsOptions ?? {}) }, () => { @@ -87,6 +93,16 @@ function netConnect(opts: ITlsClientOptions): Promise<$Net.Socket> { resolve(socket); }); + const connectTimeout = opts.connectTimeout ?? C.DEFAULT_CONNECT_TIMEOUT; + + if (connectTimeout) { + + socket.setTimeout(connectTimeout, () => { + + socket.destroy(new LwDFXError('connect_timeout', 'Timeout for connecting to remote server')); + }); + } + socket.on('error', (e) => { socket.removeAllListeners('error'); diff --git a/src/lib/Tls/TlsCommon.ts b/src/lib/Tls/TlsCommon.ts index cf99398..4bbce1e 100644 --- a/src/lib/Tls/TlsCommon.ts +++ b/src/lib/Tls/TlsCommon.ts @@ -39,3 +39,10 @@ export const DEFAULT_BACKLOG: number = 1023; * The default TLS ALPN protocol of LwDFXv1. */ export const DEFAULT_ALPN_PROTOCOL: string = 'lwdfx1'; + +/** + * The default timeout for connecting to the server, in milliseconds. + * + * @type uint32 + */ +export const DEFAULT_CONNECT_TIMEOUT: number = 30_000;