// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. // TODO(petamoriken): enable prefer-primordials for node polyfills // deno-lint-ignore-file prefer-primordials import { notImplemented } from "ext:deno_node/_utils.ts"; import { EventEmitter } from "node:events"; import { isIP, isIPv4, isIPv6, normalizedArgsSymbol, } from "ext:deno_node/internal/net.ts"; import { Duplex } from "node:stream"; import { asyncIdSymbol, defaultTriggerAsyncIdScope, newAsyncId, ownerSymbol, } from "ext:deno_node/internal/async_hooks.ts"; import { ERR_INVALID_ADDRESS_FAMILY, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_INVALID_FD_TYPE, ERR_INVALID_IP_ADDRESS, ERR_MISSING_ARGS, ERR_SERVER_ALREADY_LISTEN, ERR_SERVER_NOT_RUNNING, ERR_SOCKET_CLOSED, errnoException, exceptionWithHostPort, genericNodeError, uvExceptionWithHostPort, } from "ext:deno_node/internal/errors.ts"; import type { ErrnoException } from "ext:deno_node/internal/errors.ts"; import { Encodings } from "ext:deno_node/_utils.ts"; import { isUint8Array } from "ext:deno_node/internal/util/types.ts"; import { kAfterAsyncWrite, kBuffer, kBufferCb, kBufferGen, kHandle, kUpdateTimer, onStreamRead, setStreamTimeout, writeGeneric, writevGeneric, } from "ext:deno_node/internal/stream_base_commons.ts"; import { kTimeout } from "ext:deno_node/internal/timers.mjs"; import { nextTick } from "ext:deno_node/_next_tick.ts"; import { DTRACE_NET_SERVER_CONNECTION, DTRACE_NET_STREAM_END, } from "ext:deno_node/internal/dtrace.ts"; import { Buffer } from "node:buffer"; import type { LookupOneOptions } from "ext:deno_node/internal/dns/utils.ts"; import { validateAbortSignal, validateFunction, validateInt32, validateNumber, validatePort, validateString, } from "ext:deno_node/internal/validators.mjs"; import { constants as TCPConstants, TCP, TCPConnectWrap, } from "ext:deno_node/internal_binding/tcp_wrap.ts"; import { constants as PipeConstants, Pipe, PipeConnectWrap, } from "ext:deno_node/internal_binding/pipe_wrap.ts"; import { ShutdownWrap } from "ext:deno_node/internal_binding/stream_wrap.ts"; import { assert } from "ext:deno_node/_util/asserts.ts"; import { isWindows } from "ext:deno_node/_util/os.ts"; import { ADDRCONFIG, lookup as dnsLookup } from "node:dns"; import { codeMap } from "ext:deno_node/internal_binding/uv.ts"; import { guessHandleType } from "ext:deno_node/internal_binding/util.ts"; import { debuglog } from "ext:deno_node/internal/util/debuglog.ts"; import type { DuplexOptions } from "ext:deno_node/_stream.d.ts"; import type { BufferEncoding } from "ext:deno_node/_global.d.ts"; import type { Abortable } from "ext:deno_node/_events.d.ts"; import { channel } from "node:diagnostics_channel"; let debug = debuglog("net", (fn) => { debug = fn; }); const kLastWriteQueueSize = Symbol("lastWriteQueueSize"); const kSetNoDelay = Symbol("kSetNoDelay"); const kBytesRead = Symbol("kBytesRead"); const kBytesWritten = Symbol("kBytesWritten"); const DEFAULT_IPV4_ADDR = "0.0.0.0"; const DEFAULT_IPV6_ADDR = "::"; type Handle = TCP | Pipe; interface HandleOptions { pauseOnCreate?: boolean; manualStart?: boolean; handle?: Handle; } interface OnReadOptions { buffer: Uint8Array | (() => Uint8Array); /** * This function is called for every chunk of incoming data. * * Two arguments are passed to it: the number of bytes written to buffer and * a reference to buffer. * * Return `false` from this function to implicitly `pause()` the socket. */ callback(bytesWritten: number, buf: Uint8Array): boolean; } interface ConnectOptions { /** * If specified, incoming data is stored in a single buffer and passed to the * supplied callback when data arrives on the socket. * * Note: this will cause the streaming functionality to not provide any data, * however events like `"error"`, `"end"`, and `"close"` will still be * emitted as normal and methods like `pause()` and `resume()` will also * behave as expected. */ onread?: OnReadOptions; } interface SocketOptions extends ConnectOptions, HandleOptions, DuplexOptions { /** * If specified, wrap around an existing socket with the given file * descriptor, otherwise a new socket will be created. */ fd?: number; /** * If set to `false`, then the socket will automatically end the writable * side when the readable side ends. See `net.createServer()` and the `"end"` * event for details. Default: `false`. */ allowHalfOpen?: boolean; /** * Allow reads on the socket when an fd is passed, otherwise ignored. * Default: `false`. */ readable?: boolean; /** * Allow writes on the socket when an fd is passed, otherwise ignored. * Default: `false`. */ writable?: boolean; /** An Abort signal that may be used to destroy the socket. */ signal?: AbortSignal; } interface TcpNetConnectOptions extends TcpSocketConnectOptions, SocketOptions { timeout?: number; } interface IpcNetConnectOptions extends IpcSocketConnectOptions, SocketOptions { timeout?: number; } type NetConnectOptions = TcpNetConnectOptions | IpcNetConnectOptions; interface AddressInfo { address: string; family?: string; port: number; } type LookupFunction = ( hostname: string, options: LookupOneOptions, callback: ( err: ErrnoException | null, address: string, family: number, ) => void, ) => void; interface TcpSocketConnectOptions extends ConnectOptions { port: number; host?: string; localAddress?: string; localPort?: number; hints?: number; family?: number; lookup?: LookupFunction; } interface IpcSocketConnectOptions extends ConnectOptions { path: string; } type SocketConnectOptions = TcpSocketConnectOptions | IpcSocketConnectOptions; function _getNewAsyncId(handle?: Handle): number { return !handle || typeof handle.getAsyncId !== "function" ? newAsyncId() : handle.getAsyncId(); } interface NormalizedArgs { 0: Partial; 1: ConnectionListener | null; [normalizedArgsSymbol]?: boolean; } const _noop = (_arrayBuffer: Uint8Array, _nread: number): undefined => { return; }; const netClientSocketChannel = channel("net.client.socket"); const netServerSocketChannel = channel("net.server.socket"); function _toNumber(x: unknown): number | false { return (x = Number(x)) >= 0 ? (x as number) : false; } function _isPipeName(s: unknown): s is string { return typeof s === "string" && _toNumber(s) === false; } function _createHandle(fd: number, isServer: boolean): Handle { validateInt32(fd, "fd", 0); const type = guessHandleType(fd); if (type === "PIPE") { return new Pipe(isServer ? PipeConstants.SERVER : PipeConstants.SOCKET); } if (type === "TCP") { return new TCP(isServer ? TCPConstants.SERVER : TCPConstants.SOCKET); } throw new ERR_INVALID_FD_TYPE(type); } // Returns an array [options, cb], where options is an object, // cb is either a function or null. // Used to normalize arguments of `Socket.prototype.connect()` and // `Server.prototype.listen()`. Possible combinations of parameters: // - (options[...][, cb]) // - (path[...][, cb]) // - ([port][, host][...][, cb]) // For `Socket.prototype.connect()`, the [...] part is ignored // For `Server.prototype.listen()`, the [...] part is [, backlog] // but will not be handled here (handled in listen()) export function _normalizeArgs(args: unknown[]): NormalizedArgs { let arr: NormalizedArgs; if (args.length === 0) { arr = [{}, null]; arr[normalizedArgsSymbol] = true; return arr; } const arg0 = args[0] as Partial | number | string; let options: Partial = {}; if (typeof arg0 === "object" && arg0 !== null) { // (options[...][, cb]) options = arg0; } else if (_isPipeName(arg0)) { // (path[...][, cb]) (options as IpcSocketConnectOptions).path = arg0; } else { // ([port][, host][...][, cb]) (options as TcpSocketConnectOptions).port = arg0; if (args.length > 1 && typeof args[1] === "string") { (options as TcpSocketConnectOptions).host = args[1]; } } const cb = args[args.length - 1]; if (!_isConnectionListener(cb)) { arr = [options, null]; } else { arr = [options, cb]; } arr[normalizedArgsSymbol] = true; return arr; } function _isTCPConnectWrap( req: TCPConnectWrap | PipeConnectWrap, ): req is TCPConnectWrap { return "localAddress" in req && "localPort" in req; } function _afterConnect( status: number, // deno-lint-ignore no-explicit-any handle: any, req: PipeConnectWrap | TCPConnectWrap, readable: boolean, writable: boolean, ) { let socket = handle[ownerSymbol]; if (socket.constructor.name === "ReusedHandle") { socket = socket.handle; } // Callback may come after call to destroy if (socket.destroyed) { return; } debug("afterConnect"); assert(socket.connecting); socket.connecting = false; socket._sockname = null; if (status === 0) { if (socket.readable && !readable) { socket.push(null); socket.read(); } if (socket.writable && !writable) { socket.end(); } socket._unrefTimer(); socket.emit("connect"); socket.emit("ready"); // Start the first read, or get an immediate EOF. // this doesn't actually consume any bytes, because len=0. if (readable && !socket.isPaused()) { socket.read(0); } } else { socket.connecting = false; let details; if (_isTCPConnectWrap(req)) { details = req.localAddress + ":" + req.localPort; } const ex = exceptionWithHostPort( status, "connect", req.address, (req as TCPConnectWrap).port, details, ); if (_isTCPConnectWrap(req)) { ex.localAddress = req.localAddress; ex.localPort = req.localPort; } socket.destroy(ex); } } function _checkBindError(err: number, port: number, handle: TCP) { // EADDRINUSE may not be reported until we call `listen()` or `connect()`. // To complicate matters, a failed `bind()` followed by `listen()` or `connect()` // will implicitly bind to a random port. Ergo, check that the socket is // bound to the expected port before calling `listen()` or `connect()`. if (err === 0 && port > 0 && handle.getsockname) { const out: AddressInfo | Record = {}; err = handle.getsockname(out); if (err === 0 && port !== out.port) { err = codeMap.get("EADDRINUSE")!; } } return err; } function _isPipe( options: Partial, ): options is IpcSocketConnectOptions { return "path" in options && !!options.path; } function _connectErrorNT(socket: Socket, err: Error) { socket.destroy(err); } function _internalConnect( socket: Socket, address: string, port: number, addressType: number, localAddress: string, localPort: number, flags: number, ) { assert(socket.connecting); let err; if (localAddress || localPort) { if (addressType === 4) { localAddress = localAddress || DEFAULT_IPV4_ADDR; err = (socket._handle as TCP).bind(localAddress, localPort); } else { // addressType === 6 localAddress = localAddress || DEFAULT_IPV6_ADDR; err = (socket._handle as TCP).bind6(localAddress, localPort, flags); } debug( "binding to localAddress: %s and localPort: %d (addressType: %d)", localAddress, localPort, addressType, ); err = _checkBindError(err, localPort, socket._handle as TCP); if (err) { const ex = exceptionWithHostPort(err, "bind", localAddress, localPort); socket.destroy(ex); return; } } if (addressType === 6 || addressType === 4) { const req = new TCPConnectWrap(); req.oncomplete = _afterConnect; req.address = address; req.port = port; req.localAddress = localAddress; req.localPort = localPort; if (addressType === 4) { err = (socket._handle as TCP).connect(req, address, port); } else { err = (socket._handle as TCP).connect6(req, address, port); } } else { const req = new PipeConnectWrap(); req.oncomplete = _afterConnect; req.address = address; err = (socket._handle as Pipe).connect(req, address); } if (err) { let details = ""; const sockname = socket._getsockname(); if (sockname) { details = `${sockname.address}:${sockname.port}`; } const ex = exceptionWithHostPort(err, "connect", address, port, details); socket.destroy(ex); } } // Provide a better error message when we call end() as a result // of the other side sending a FIN. The standard "write after end" // is overly vague, and makes it seem like the user's code is to blame. function _writeAfterFIN( this: Socket, // deno-lint-ignore no-explicit-any chunk: any, encoding?: | BufferEncoding | null | ((error: Error | null | undefined) => void), cb?: (error: Error | null | undefined) => void, ): boolean { if (!this.writableEnded) { return Duplex.prototype.write.call( this, chunk, encoding as BufferEncoding | null, // @ts-expect-error Using `call` seem to be interfering with the overload for write cb, ); } if (typeof encoding === "function") { cb = encoding; encoding = null; } const err = genericNodeError( "This socket has been ended by the other party", { code: "EPIPE" }, ); if (typeof cb === "function") { defaultTriggerAsyncIdScope(this[asyncIdSymbol], nextTick, cb, err); } if (this._server) { nextTick(() => this.destroy(err)); } else { this.destroy(err); } return false; } function _tryReadStart(socket: Socket) { // Not already reading, start the flow. debug("Socket._handle.readStart"); socket._handle!.reading = true; const err = socket._handle!.readStart(); if (err) { socket.destroy(errnoException(err, "read")); } } // Called when the "end" event is emitted. function _onReadableStreamEnd(this: Socket) { if (!this.allowHalfOpen) { this.write = _writeAfterFIN; } } // Called when creating new Socket, or when re-using a closed Socket function _initSocketHandle(socket: Socket) { socket._undestroy(); socket._sockname = undefined; // Handle creation may be deferred to bind() or connect() time. if (socket._handle) { // deno-lint-ignore no-explicit-any (socket._handle as any)[ownerSymbol] = socket; socket._handle.onread = onStreamRead; socket[asyncIdSymbol] = _getNewAsyncId(socket._handle); let userBuf = socket[kBuffer]; if (userBuf) { const bufGen = socket[kBufferGen]; if (bufGen !== null) { userBuf = bufGen(); if (!isUint8Array(userBuf)) { return; } socket[kBuffer] = userBuf; } socket._handle.useUserBuffer(userBuf); } } } function _lookupAndConnect( self: Socket, options: TcpSocketConnectOptions, ) { const { localAddress, localPort } = options; const host = options.host || "localhost"; let { port } = options; if (localAddress && !isIP(localAddress)) { throw new ERR_INVALID_IP_ADDRESS(localAddress); } if (localPort) { validateNumber(localPort, "options.localPort"); } if (typeof port !== "undefined") { if (typeof port !== "number" && typeof port !== "string") { throw new ERR_INVALID_ARG_TYPE( "options.port", ["number", "string"], port, ); } validatePort(port); } port |= 0; // If host is an IP, skip performing a lookup const addressType = isIP(host); if (addressType) { defaultTriggerAsyncIdScope(self[asyncIdSymbol], nextTick, () => { if (self.connecting) { defaultTriggerAsyncIdScope( self[asyncIdSymbol], _internalConnect, self, host, port, addressType, localAddress, localPort, ); } }); return; } if (options.lookup !== undefined) { validateFunction(options.lookup, "options.lookup"); } const dnsOpts = { family: options.family, hints: options.hints || 0, }; if ( !isWindows && dnsOpts.family !== 4 && dnsOpts.family !== 6 && dnsOpts.hints === 0 ) { dnsOpts.hints = ADDRCONFIG; } debug("connect: find host", host); debug("connect: dns options", dnsOpts); self._host = host; const lookup = options.lookup || dnsLookup; defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () { lookup( host, dnsOpts, function emitLookup( err: ErrnoException | null, ip: string, addressType: number, ) { self.emit("lookup", err, ip, addressType, host); // It's possible we were destroyed while looking this up. // XXX it would be great if we could cancel the promise returned by // the look up. if (!self.connecting) { return; } if (err) { // net.createConnection() creates a net.Socket object and immediately // calls net.Socket.connect() on it (that's us). There are no event // listeners registered yet so defer the error event to the next tick. nextTick(_connectErrorNT, self, err); } else if (!isIP(ip)) { err = new ERR_INVALID_IP_ADDRESS(ip); nextTick(_connectErrorNT, self, err); } else if (addressType !== 4 && addressType !== 6) { err = new ERR_INVALID_ADDRESS_FAMILY( `${addressType}`, options.host!, options.port, ); nextTick(_connectErrorNT, self, err); } else { self._unrefTimer(); defaultTriggerAsyncIdScope( self[asyncIdSymbol], _internalConnect, self, ip, port, addressType, localAddress, localPort, ); } }, ); }); } function _afterShutdown(this: ShutdownWrap) { // deno-lint-ignore no-explicit-any const self: any = this.handle[ownerSymbol]; debug("afterShutdown destroyed=%j", self.destroyed, self._readableState); this.callback(); } function _emitCloseNT(s: Socket | Server) { debug("SERVER: emit close"); s.emit("close"); } /** * This class is an abstraction of a TCP socket or a streaming `IPC` endpoint * (uses named pipes on Windows, and Unix domain sockets otherwise). It is also * an `EventEmitter`. * * A `net.Socket` can be created by the user and used directly to interact with * a server. For example, it is returned by `createConnection`, * so the user can use it to talk to the server. * * It can also be created by Node.js and passed to the user when a connection * is received. For example, it is passed to the listeners of a `"connection"` event emitted on a `Server`, so the user can use * it to interact with the client. */ export class Socket extends Duplex { // Problem with this is that users can supply their own handle, that may not // have `handle.getAsyncId()`. In this case an `[asyncIdSymbol]` should // probably be supplied by `async_hooks`. [asyncIdSymbol] = -1; [kHandle]: Handle | null = null; [kSetNoDelay] = false; [kLastWriteQueueSize] = 0; // deno-lint-ignore no-explicit-any [kTimeout]: any = null; [kBuffer]: Uint8Array | boolean | null = null; [kBufferCb]: OnReadOptions["callback"] | null = null; [kBufferGen]: (() => Uint8Array) | null = null; // Used after `.destroy()` [kBytesRead] = 0; [kBytesWritten] = 0; // Reserved properties server = null; // deno-lint-ignore no-explicit-any _server: any = null; _peername?: AddressInfo | Record; _sockname?: AddressInfo | Record; _pendingData: Uint8Array | string | null = null; _pendingEncoding = ""; _host: string | null = null; // deno-lint-ignore no-explicit-any _parent: any = null; constructor(options: SocketOptions | number) { if (typeof options === "number") { // Legacy interface. options = { fd: options }; } else { options = { ...options }; } // Default to *not* allowing half open sockets. options.allowHalfOpen = Boolean(options.allowHalfOpen); // For backwards compat do not emit close on destroy. options.emitClose = false; options.autoDestroy = true; // Handle strings directly. options.decodeStrings = false; super(options); if (options.handle) { this._handle = options.handle; this[asyncIdSymbol] = _getNewAsyncId(this._handle); } else if (options.fd !== undefined) { // REF: https://github.com/denoland/deno/issues/6529 notImplemented("net.Socket.prototype.constructor with fd option"); } const onread = options.onread; if ( onread !== null && typeof onread === "object" && (isUint8Array(onread.buffer) || typeof onread.buffer === "function") && typeof onread.callback === "function" ) { if (typeof onread.buffer === "function") { this[kBuffer] = true; this[kBufferGen] = onread.buffer; } else { this[kBuffer] = onread.buffer; } this[kBufferCb] = onread.callback; } this.on("end", _onReadableStreamEnd); _initSocketHandle(this); // If we have a handle, then start the flow of data into the // buffer. If not, then this will happen when we connect. if (this._handle && options.readable !== false) { if (options.pauseOnCreate) { // Stop the handle from reading and pause the stream this._handle.reading = false; this._handle.readStop(); // @ts-expect-error This property shouldn't be modified this.readableFlowing = false; } else if (!options.manualStart) { this.read(0); } } } /** * Initiate a connection on a given socket. * * Possible signatures: * * - `socket.connect(options[, connectListener])` * - `socket.connect(path[, connectListener])` for `IPC` connections. * - `socket.connect(port[, host][, connectListener])` for TCP connections. * - Returns: `net.Socket` The socket itself. * * This function is asynchronous. When the connection is established, the `"connect"` event will be emitted. If there is a problem connecting, * instead of a `"connect"` event, an `"error"` event will be emitted with * the error passed to the `"error"` listener. * The last parameter `connectListener`, if supplied, will be added as a listener * for the `"connect"` event **once**. * * This function should only be used for reconnecting a socket after `"close"` has been emitted or otherwise it may lead to undefined * behavior. */ connect( options: SocketConnectOptions | NormalizedArgs, connectionListener?: ConnectionListener, ): this; connect( port: number, host: string, connectionListener?: ConnectionListener, ): this; connect(port: number, connectionListener?: ConnectionListener): this; connect(path: string, connectionListener?: ConnectionListener): this; connect(...args: unknown[]): this { let normalized: NormalizedArgs; // If passed an array, it's treated as an array of arguments that have // already been normalized (so we don't normalize more than once). This has // been solved before in https://github.com/nodejs/node/pull/12342, but was // reverted as it had unintended side effects. if ( Array.isArray(args[0]) && (args[0] as unknown as NormalizedArgs)[normalizedArgsSymbol] ) { normalized = args[0] as unknown as NormalizedArgs; } else { normalized = _normalizeArgs(args); } const options = normalized[0]; const cb = normalized[1]; // `options.port === null` will be checked later. if ( (options as TcpSocketConnectOptions).port === undefined && (options as IpcSocketConnectOptions).path == null ) { throw new ERR_MISSING_ARGS(["options", "port", "path"]); } if (this.write !== Socket.prototype.write) { this.write = Socket.prototype.write; } if (this.destroyed) { this._handle = null; this._peername = undefined; this._sockname = undefined; } const { path } = options as IpcNetConnectOptions; const pipe = _isPipe(options); debug("pipe", pipe, path); if (!this._handle) { this._handle = pipe ? new Pipe(PipeConstants.SOCKET) : new TCP(TCPConstants.SOCKET); _initSocketHandle(this); } if (cb !== null) { this.once("connect", cb); } this._unrefTimer(); this.connecting = true; if (pipe) { validateString(path, "options.path"); defaultTriggerAsyncIdScope( this[asyncIdSymbol], _internalConnect, this, path, ); } else { _lookupAndConnect(this, options as TcpSocketConnectOptions); } return this; } /** * Pauses the reading of data. That is, `"data"` events will not be emitted. * Useful to throttle back an upload. * * @return The socket itself. */ override pause(): this { if ( this[kBuffer] && !this.connecting && this._handle && this._handle.reading ) { this._handle.reading = false; if (!this.destroyed) { const err = this._handle.readStop(); if (err) { this.destroy(errnoException(err, "read")); } } } return Duplex.prototype.pause.call(this) as unknown as this; } /** * Resumes reading after a call to `socket.pause()`. * * @return The socket itself. */ override resume(): this { if ( this[kBuffer] && !this.connecting && this._handle && !this._handle.reading ) { _tryReadStart(this); } return Duplex.prototype.resume.call(this) as this; } /** * Sets the socket to timeout after `timeout` milliseconds of inactivity on * the socket. By default `net.Socket` do not have a timeout. * * When an idle timeout is triggered the socket will receive a `"timeout"` event but the connection will not be severed. The user must manually call `socket.end()` or `socket.destroy()` to * end the connection. * * If `timeout` is `0`, then the existing idle timeout is disabled. * * The optional `callback` parameter will be added as a one-time listener for the `"timeout"` event. * @return The socket itself. */ setTimeout = setStreamTimeout; /** * Enable/disable the use of Nagle's algorithm. * * When a TCP connection is created, it will have Nagle's algorithm enabled. * * Nagle's algorithm delays data before it is sent via the network. It attempts * to optimize throughput at the expense of latency. * * Passing `true` for `noDelay` or not passing an argument will disable Nagle's * algorithm for the socket. Passing `false` for `noDelay` will enable Nagle's * algorithm. * * @param noDelay * @return The socket itself. */ setNoDelay(noDelay?: boolean): this { if (!this._handle) { this.once( "connect", noDelay ? this.setNoDelay : () => this.setNoDelay(noDelay), ); return this; } // Backwards compatibility: assume true when `noDelay` is omitted const newValue = noDelay === undefined ? true : !!noDelay; if ( "setNoDelay" in this._handle && this._handle.setNoDelay && newValue !== this[kSetNoDelay] ) { this[kSetNoDelay] = newValue; this._handle.setNoDelay(newValue); } return this; } /** * Enable/disable keep-alive functionality, and optionally set the initial * delay before the first keepalive probe is sent on an idle socket. * * Set `initialDelay` (in milliseconds) to set the delay between the last * data packet received and the first keepalive probe. Setting `0` for`initialDelay` will leave the value unchanged from the default * (or previous) setting. * * Enabling the keep-alive functionality will set the following socket options: * * - `SO_KEEPALIVE=1` * - `TCP_KEEPIDLE=initialDelay` * - `TCP_KEEPCNT=10` * - `TCP_KEEPINTVL=1` * * @param enable * @param initialDelay * @return The socket itself. */ setKeepAlive(enable: boolean, initialDelay?: number): this { if (!this._handle) { this.once("connect", () => this.setKeepAlive(enable, initialDelay)); return this; } if ("setKeepAlive" in this._handle) { this._handle.setKeepAlive(enable, ~~(initialDelay! / 1000)); } return this; } /** * Returns the bound `address`, the address `family` name and `port` of the * socket as reported by the operating system:`{ port: 12346, family: "IPv4", address: "127.0.0.1" }` */ address(): AddressInfo | Record { return this._getsockname(); } /** * Calling `unref()` on a socket will allow the program to exit if this is the only * active socket in the event system. If the socket is already `unref`ed calling`unref()` again will have no effect. * * @return The socket itself. */ unref(): this { if (!this._handle) { this.once("connect", this.unref); return this; } if (typeof this._handle.unref === "function") { this._handle.unref(); } return this; } /** * Opposite of `unref()`, calling `ref()` on a previously `unref`ed socket will_not_ let the program exit if it's the only socket left (the default behavior). * If the socket is `ref`ed calling `ref` again will have no effect. * * @return The socket itself. */ ref(): this { if (!this._handle) { this.once("connect", this.ref); return this; } if (typeof this._handle.ref === "function") { this._handle.ref(); } return this; } /** * This property shows the number of characters buffered for writing. The buffer * may contain strings whose length after encoding is not yet known. So this number * is only an approximation of the number of bytes in the buffer. * * `net.Socket` has the property that `socket.write()` always works. This is to * help users get up and running quickly. The computer cannot always keep up * with the amount of data that is written to a socket. The network connection * simply might be too slow. Node.js will internally queue up the data written to a * socket and send it out over the wire when it is possible. * * The consequence of this internal buffering is that memory may grow. * Users who experience large or growing `bufferSize` should attempt to * "throttle" the data flows in their program with `socket.pause()` and `socket.resume()`. * * @deprecated Use `writableLength` instead. */ get bufferSize(): number { if (this._handle) { return this.writableLength; } return 0; } /** * The amount of received bytes. */ get bytesRead(): number { return this._handle ? this._handle.bytesRead : this[kBytesRead]; } /** * The amount of bytes sent. */ get bytesWritten(): number | undefined { let bytes = this._bytesDispatched; const data = this._pendingData; const encoding = this._pendingEncoding; const writableBuffer = this.writableBuffer; if (!writableBuffer) { return undefined; } for (const el of writableBuffer) { bytes += el!.chunk instanceof Buffer ? el!.chunk.length : Buffer.byteLength(el!.chunk, el!.encoding); } if (Array.isArray(data)) { // Was a writev, iterate over chunks to get total length for (let i = 0; i < data.length; i++) { const chunk = data[i]; // deno-lint-ignore no-explicit-any if ((data as any).allBuffers || chunk instanceof Buffer) { bytes += chunk.length; } else { bytes += Buffer.byteLength(chunk.chunk, chunk.encoding); } } } else if (data) { // Writes are either a string or a Buffer. if (typeof data !== "string") { bytes += (data as Buffer).length; } else { bytes += Buffer.byteLength(data, encoding); } } return bytes; } /** * If `true`,`socket.connect(options[, connectListener])` was * called and has not yet finished. It will stay `true` until the socket becomes * connected, then it is set to `false` and the `"connect"` event is emitted. Note * that the `socket.connect(options[, connectListener])` callback is a listener for the `"connect"` event. */ connecting = false; /** * The string representation of the local IP address the remote client is * connecting on. For example, in a server listening on `"0.0.0.0"`, if a client * connects on `"192.168.1.1"`, the value of `socket.localAddress` would be`"192.168.1.1"`. */ get localAddress(): string { return this._getsockname().address; } /** * The numeric representation of the local port. For example, `80` or `21`. */ get localPort(): number { return this._getsockname().port; } /** * The string representation of the local IP family. `"IPv4"` or `"IPv6"`. */ get localFamily(): string | undefined { return this._getsockname().family; } /** * The string representation of the remote IP address. For example,`"74.125.127.100"` or `"2001:4860:a005::68"`. Value may be `undefined` if * the socket is destroyed (for example, if the client disconnected). */ get remoteAddress(): string | undefined { return this._getpeername().address; } /** * The string representation of the remote IP family. `"IPv4"` or `"IPv6"`. */ get remoteFamily(): string | undefined { const { family } = this._getpeername(); return family ? `IPv${family}` : family; } /** * The numeric representation of the remote port. For example, `80` or `21`. */ get remotePort(): number | undefined { return this._getpeername().port; } get pending(): boolean { return !this._handle || this.connecting; } get readyState(): string { if (this.connecting) { return "opening"; } else if (this.readable && this.writable) { return "open"; } else if (this.readable && !this.writable) { return "readOnly"; } else if (!this.readable && this.writable) { return "writeOnly"; } return "closed"; } /** * Half-closes the socket. i.e., it sends a FIN packet. It is possible the * server will still send some data. * * See `writable.end()` for further details. * * @param encoding Only used when data is `string`. * @param cb Optional callback for when the socket is finished. * @return The socket itself. */ override end(cb?: () => void): this; override end(buffer: Uint8Array | string, cb?: () => void): this; override end( data: Uint8Array | string, encoding?: Encodings, cb?: () => void, ): this; override end( data?: Uint8Array | string | (() => void), encoding?: Encodings | (() => void), cb?: () => void, ): this { Duplex.prototype.end.call(this, data, encoding as Encodings, cb); DTRACE_NET_STREAM_END(this); return this; } /** * @param size Optional argument to specify how much data to read. */ override read( size?: number, ): string | Uint8Array | Buffer | null | undefined { if ( this[kBuffer] && !this.connecting && this._handle && !this._handle.reading ) { _tryReadStart(this); } return Duplex.prototype.read.call(this, size); } destroySoon() { if (this.writable) { this.end(); } if (this.writableFinished) { this.destroy(); } else { this.once("finish", this.destroy); } } _unrefTimer() { // deno-lint-ignore no-this-alias for (let s = this; s !== null; s = s._parent) { if (s[kTimeout]) { s[kTimeout].refresh(); } } } // The user has called .end(), and all the bytes have been // sent out to the other side. // deno-lint-ignore no-explicit-any override _final(cb: any): any { // If still connecting - defer handling `_final` until 'connect' will happen if (this.pending) { debug("_final: not yet connected"); return this.once("connect", () => this._final(cb)); } if (!this._handle) { return cb(); } debug("_final: not ended, call shutdown()"); const req = new ShutdownWrap(); req.oncomplete = _afterShutdown; req.handle = this._handle; req.callback = cb; const err = this._handle.shutdown(req); if (err === 1 || err === codeMap.get("ENOTCONN")) { // synchronous finish return cb(); } else if (err !== 0) { return cb(errnoException(err, "shutdown")); } } _onTimeout() { const handle = this._handle; const lastWriteQueueSize = this[kLastWriteQueueSize]; if (lastWriteQueueSize > 0 && handle) { // `lastWriteQueueSize !== writeQueueSize` means there is // an active write in progress, so we suppress the timeout. const { writeQueueSize } = handle; if (lastWriteQueueSize !== writeQueueSize) { this[kLastWriteQueueSize] = writeQueueSize; this._unrefTimer(); return; } } debug("_onTimeout"); this.emit("timeout"); } override _read(size?: number) { debug("_read"); if (this.connecting || !this._handle) { debug("_read wait for connection"); this.once("connect", () => this._read(size)); } else if (!this._handle.reading) { _tryReadStart(this); } } override _destroy(exception: Error | null, cb: (err: Error | null) => void) { debug("destroy"); this.connecting = false; // deno-lint-ignore no-this-alias for (let s = this; s !== null; s = s._parent) { clearTimeout(s[kTimeout]); } debug("close"); if (this._handle) { debug("close handle"); const isException = exception ? true : false; // `bytesRead` and `kBytesWritten` should be accessible after `.destroy()` this[kBytesRead] = this._handle.bytesRead; this[kBytesWritten] = this._handle.bytesWritten; this._handle.close(() => { this._handle!.onread = _noop; this._handle = null; this._sockname = undefined; debug("emit close"); this.emit("close", isException); }); cb(exception); } else { cb(exception); nextTick(_emitCloseNT, this); } if (this._server) { debug("has server"); this._server._connections--; if (this._server._emitCloseIfDrained) { this._server._emitCloseIfDrained(); } } } _getpeername(): AddressInfo | Record { if (!this._handle || !("getpeername" in this._handle) || this.connecting) { return this._peername || {}; } else if (!this._peername) { this._peername = {}; this._handle.getpeername(this._peername); } return this._peername; } _getsockname(): AddressInfo | Record { if (!this._handle || !("getsockname" in this._handle)) { return {}; } else if (!this._sockname) { this._sockname = {}; this._handle.getsockname(this._sockname); } return this._sockname; } _writeGeneric( writev: boolean, // deno-lint-ignore no-explicit-any data: any, encoding: string, cb: (error?: Error | null) => void, ) { // If we are still connecting, then buffer this for later. // The Writable logic will buffer up any more writes while // waiting for this one to be done. if (this.connecting) { this._pendingData = data; this._pendingEncoding = encoding; this.once("connect", function connect(this: Socket) { this._writeGeneric(writev, data, encoding, cb); }); return; } this._pendingData = null; this._pendingEncoding = ""; if (!this._handle) { cb(new ERR_SOCKET_CLOSED()); return false; } this._unrefTimer(); let req; if (writev) { req = writevGeneric(this, data, cb); } else { req = writeGeneric(this, data, encoding, cb); } if (req.async) { this[kLastWriteQueueSize] = req.bytes; } } // @ts-ignore Duplex defining as a property when want a method. _writev( // deno-lint-ignore no-explicit-any chunks: Array<{ chunk: any; encoding: string }>, cb: (error?: Error | null) => void, ) { this._writeGeneric(true, chunks, "", cb); } override _write( // deno-lint-ignore no-explicit-any data: any, encoding: string, cb: (error?: Error | null) => void, ) { this._writeGeneric(false, data, encoding, cb); } [kAfterAsyncWrite]() { this[kLastWriteQueueSize] = 0; } get [kUpdateTimer]() { return this._unrefTimer; } get _connecting(): boolean { return this.connecting; } // Legacy alias. Having this is probably being overly cautious, but it doesn't // really hurt anyone either. This can probably be removed safely if desired. get _bytesDispatched(): number { return this._handle ? this._handle.bytesWritten : this[kBytesWritten]; } get _handle(): Handle | null { return this[kHandle]; } set _handle(v: Handle | null) { this[kHandle] = v; } } export const Stream = Socket; // Target API: // // let s = net.connect({port: 80, host: 'google.com'}, function() { // ... // }); // // There are various forms: // // connect(options, [cb]) // connect(port, [host], [cb]) // connect(path, [cb]); // export function connect( options: NetConnectOptions, connectionListener?: () => void, ): Socket; export function connect( port: number, host?: string, connectionListener?: () => void, ): Socket; export function connect(path: string, connectionListener?: () => void): Socket; export function connect(...args: unknown[]) { const normalized = _normalizeArgs(args); const options = normalized[0] as Partial; debug("createConnection", normalized); const socket = new Socket(options); if (netClientSocketChannel.hasSubscribers) { netClientSocketChannel.publish({ socket, }); } if (options.timeout) { socket.setTimeout(options.timeout); } return socket.connect(normalized); } export const createConnection = connect; export interface ListenOptions extends Abortable { fd?: number; port?: number | undefined; host?: string | undefined; backlog?: number | undefined; path?: string | undefined; exclusive?: boolean | undefined; readableAll?: boolean | undefined; writableAll?: boolean | undefined; /** * Default: `false` */ ipv6Only?: boolean | undefined; } type ConnectionListener = (socket: Socket) => void; interface ServerOptions { /** * Indicates whether half-opened TCP connections are allowed. * Default: false */ allowHalfOpen?: boolean | undefined; /** * Indicates whether the socket should be paused on incoming connections. * Default: false */ pauseOnConnect?: boolean | undefined; } function _isServerSocketOptions( options: unknown, ): options is null | undefined | ServerOptions { return ( options === null || typeof options === "undefined" || typeof options === "object" ); } function _isConnectionListener( connectionListener: unknown, ): connectionListener is ConnectionListener { return typeof connectionListener === "function"; } function _getFlags(ipv6Only?: boolean): number { return ipv6Only === true ? TCPConstants.UV_TCP_IPV6ONLY : 0; } function _listenInCluster( server: Server, address: string | null, port: number | null, addressType: number | null, backlog: number, fd?: number | null, exclusive?: boolean, flags?: number, ) { exclusive = !!exclusive; // TODO(cmorten): here we deviate somewhat from the Node implementation which // makes use of the https://nodejs.org/api/cluster.html module to run servers // across a "cluster" of Node processes to take advantage of multi-core // systems. // // Though Deno has has a Worker capability from which we could simulate this, // for now we assert that we are _always_ on the primary process. const isPrimary = true; if (isPrimary || exclusive) { // Will create a new handle // _listen2 sets up the listened handle, it is still named like this // to avoid breaking code that wraps this method server._listen2(address, port, addressType, backlog, fd, flags); return; } } function _lookupAndListen( server: Server, port: number, address: string, backlog: number, exclusive: boolean, flags: number, ) { dnsLookup(address, function doListen(err, ip, addressType) { if (err) { server.emit("error", err); } else { addressType = ip ? addressType : 4; _listenInCluster( server, ip, port, addressType, backlog, null, exclusive, flags, ); } }); } function _addAbortSignalOption(server: Server, options: ListenOptions) { if (options?.signal === undefined) { return; } validateAbortSignal(options.signal, "options.signal"); const { signal } = options; const onAborted = () => { server.close(); }; if (signal.aborted) { nextTick(onAborted); } else { signal.addEventListener("abort", onAborted); server.once("close", () => signal.removeEventListener("abort", onAborted)); } } // Returns handle if it can be created, or error code if it can't export function _createServerHandle( address: string | null, port: number | null, addressType: number | null, fd?: number | null, flags?: number, ): Handle | number { let err = 0; // Assign handle in listen, and clean up if bind or listen fails let handle; let isTCP = false; if (typeof fd === "number" && fd >= 0) { try { handle = _createHandle(fd, true); } catch (e) { // Not a fd we can listen on. This will trigger an error. debug("listen invalid fd=%d:", fd, (e as Error).message); return codeMap.get("EINVAL")!; } err = handle.open(fd); if (err) { return err; } assert(!address && !port); } else if (port === -1 && addressType === -1) { handle = new Pipe(PipeConstants.SERVER); if (isWindows) { const instances = Number.parseInt( Deno.env.get("NODE_PENDING_PIPE_INSTANCES") ?? "", ); if (!Number.isNaN(instances)) { handle.setPendingInstances!(instances); } } } else { handle = new TCP(TCPConstants.SERVER); isTCP = true; } if (address || port || isTCP) { debug("bind to", address || "any"); if (!address) { // TODO(@bartlomieju): differs from Node which tries to bind to IPv6 first when no // address is provided. // // Forcing IPv4 as a workaround for Deno not aligning with Node on // implicit binding on Windows. // // REF: https://github.com/denoland/deno/issues/10762 // Try binding to ipv6 first // err = (handle as TCP).bind6(DEFAULT_IPV6_ADDR, port ?? 0, flags ?? 0); // if (err) { // handle.close(); // Fallback to ipv4 return _createServerHandle(DEFAULT_IPV4_ADDR, port, 4, null, flags); // } } else if (addressType === 6) { err = (handle as TCP).bind6(address, port ?? 0, flags ?? 0); } else { err = (handle as TCP).bind(address, port ?? 0); } } if (err) { handle.close(); return err; } return handle; } function _emitErrorNT(server: Server, err: Error) { server.emit("error", err); } function _emitListeningNT(server: Server) { // Ensure handle hasn't closed if (server._handle) { server.emit("listening"); } } // deno-lint-ignore no-explicit-any function _onconnection(this: any, err: number, clientHandle?: Handle) { // deno-lint-ignore no-this-alias const handle = this; const self = handle[ownerSymbol]; debug("onconnection"); if (err) { self.emit("error", errnoException(err, "accept")); return; } if (self.maxConnections && self._connections >= self.maxConnections) { clientHandle!.close(); return; } const socket = self._createSocket(clientHandle); this._connections++; self.emit("connection", socket); if (netServerSocketChannel.hasSubscribers) { netServerSocketChannel.publish({ socket, }); } } function _setupListenHandle( this: Server, address: string | null, port: number | null, addressType: number | null, backlog: number, fd?: number | null, flags?: number, ) { debug("setupListenHandle", address, port, addressType, backlog, fd); // If there is not yet a handle, we need to create one and bind. // In the case of a server sent via IPC, we don't need to do this. if (this._handle) { debug("setupListenHandle: have a handle already"); } else { debug("setupListenHandle: create a handle"); let rval = null; // Try to bind to the unspecified IPv6 address, see if IPv6 is available if (!address && typeof fd !== "number") { // TODO(@bartlomieju): differs from Node which tries to bind to IPv6 first // when no address is provided. // // Forcing IPv4 as a workaround for Deno not aligning with Node on // implicit binding on Windows. // // REF: https://github.com/denoland/deno/issues/10762 // rval = _createServerHandle(DEFAULT_IPV6_ADDR, port, 6, fd, flags); // if (typeof rval === "number") { // rval = null; address = DEFAULT_IPV4_ADDR; addressType = 4; // } else { // address = DEFAULT_IPV6_ADDR; // addressType = 6; // } } if (rval === null) { rval = _createServerHandle(address, port, addressType, fd, flags); } if (typeof rval === "number") { const error = uvExceptionWithHostPort(rval, "listen", address, port); nextTick(_emitErrorNT, this, error); return; } this._handle = rval; } this[asyncIdSymbol] = _getNewAsyncId(this._handle); this._handle.onconnection = _onconnection; this._handle[ownerSymbol] = this; // Use a backlog of 512 entries. We pass 511 to the listen() call because // the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1); // which will thus give us a backlog of 512 entries. const err = this._handle.listen(backlog || 511); if (err) { const ex = uvExceptionWithHostPort(err, "listen", address, port); this._handle.close(); this._handle = null; defaultTriggerAsyncIdScope( this[asyncIdSymbol], nextTick, _emitErrorNT, this, ex, ); return; } // Generate connection key, this should be unique to the connection this._connectionKey = addressType + ":" + address + ":" + port; // Unref the handle if the server was unref'ed prior to listening if (this._unref) { this.unref(); } defaultTriggerAsyncIdScope( this[asyncIdSymbol], nextTick, _emitListeningNT, this, ); } /** This class is used to create a TCP or IPC server. */ export class Server extends EventEmitter { [asyncIdSymbol] = -1; allowHalfOpen = false; pauseOnConnect = false; // deno-lint-ignore no-explicit-any _handle: any = null; _connections = 0; _usingWorkers = false; // deno-lint-ignore no-explicit-any _workers: any[] = []; _unref = false; _pipeName?: string; _connectionKey?: string; /** * `net.Server` is an `EventEmitter` with the following events: * * - `"close"` - Emitted when the server closes. If connections exist, this * event is not emitted until all connections are ended. * - `"connection"` - Emitted when a new connection is made. `socket` is an * instance of `net.Socket`. * - `"error"` - Emitted when an error occurs. Unlike `net.Socket`, the * `"close"` event will not be emitted directly following this event unless * `server.close()` is manually called. See the example in discussion of * `server.listen()`. * - `"listening"` - Emitted when the server has been bound after calling * `server.listen()`. */ constructor(connectionListener?: ConnectionListener); constructor(options?: ServerOptions, connectionListener?: ConnectionListener); constructor( options?: ServerOptions | ConnectionListener, connectionListener?: ConnectionListener, ) { super(); if (_isConnectionListener(options)) { this.on("connection", options); } else if (_isServerSocketOptions(options)) { this.allowHalfOpen = options?.allowHalfOpen || false; this.pauseOnConnect = !!options?.pauseOnConnect; if (_isConnectionListener(connectionListener)) { this.on("connection", connectionListener); } } else { throw new ERR_INVALID_ARG_TYPE("options", "Object", options); } } /** * Start a server listening for connections. A `net.Server` can be a TCP or * an `IPC` server depending on what it listens to. * * Possible signatures: * * - `server.listen(handle[, backlog][, callback])` * - `server.listen(options[, callback])` * - `server.listen(path[, backlog][, callback])` for `IPC` servers * - `server.listen([port[, host[, backlog]]][, callback])` for TCP servers * * This function is asynchronous. When the server starts listening, the `'listening'` event will be emitted. The last parameter `callback`will be added as a listener for the `'listening'` * event. * * All `listen()` methods can take a `backlog` parameter to specify the maximum * length of the queue of pending connections. The actual length will be determined * by the OS through sysctl settings such as `tcp_max_syn_backlog` and `somaxconn` on Linux. The default value of this parameter is 511 (not 512). * * All `Socket` are set to `SO_REUSEADDR` (see [`socket(7)`](https://man7.org/linux/man-pages/man7/socket.7.html) for * details). * * The `server.listen()` method can be called again if and only if there was an * error during the first `server.listen()` call or `server.close()` has been * called. Otherwise, an `ERR_SERVER_ALREADY_LISTEN` error will be thrown. * * One of the most common errors raised when listening is `EADDRINUSE`. * This happens when another server is already listening on the requested`port`/`path`/`handle`. One way to handle this would be to retry * after a certain amount of time: */ listen( port?: number, hostname?: string, backlog?: number, listeningListener?: () => void, ): this; listen( port?: number, hostname?: string, listeningListener?: () => void, ): this; listen(port?: number, backlog?: number, listeningListener?: () => void): this; listen(port?: number, listeningListener?: () => void): this; listen(path: string, backlog?: number, listeningListener?: () => void): this; listen(path: string, listeningListener?: () => void): this; listen(options: ListenOptions, listeningListener?: () => void): this; // deno-lint-ignore no-explicit-any listen(handle: any, backlog?: number, listeningListener?: () => void): this; // deno-lint-ignore no-explicit-any listen(handle: any, listeningListener?: () => void): this; listen(...args: unknown[]): this { const normalized = _normalizeArgs(args); let options = normalized[0] as Partial; const cb = normalized[1]; if (this._handle) { throw new ERR_SERVER_ALREADY_LISTEN(); } if (cb !== null) { this.once("listening", cb); } const backlogFromArgs: number = // (handle, backlog) or (path, backlog) or (port, backlog) _toNumber(args.length > 1 && args[1]) || (_toNumber(args.length > 2 && args[2]) as number); // (port, host, backlog) // deno-lint-ignore no-explicit-any options = (options as any)._handle || (options as any).handle || options; const flags = _getFlags(options.ipv6Only); // (handle[, backlog][, cb]) where handle is an object with a handle if (options instanceof TCP) { this._handle = options; this[asyncIdSymbol] = this._handle.getAsyncId(); _listenInCluster(this, null, -1, -1, backlogFromArgs); return this; } _addAbortSignalOption(this, options); // (handle[, backlog][, cb]) where handle is an object with a fd if (typeof options.fd === "number" && options.fd >= 0) { _listenInCluster(this, null, null, null, backlogFromArgs, options.fd); return this; } // ([port][, host][, backlog][, cb]) where port is omitted, // that is, listen(), listen(null), listen(cb), or listen(null, cb) // or (options[, cb]) where options.port is explicitly set as undefined or // null, bind to an arbitrary unused port if ( args.length === 0 || typeof args[0] === "function" || (typeof options.port === "undefined" && "port" in options) || options.port === null ) { options.port = 0; } // ([port][, host][, backlog][, cb]) where port is specified // or (options[, cb]) where options.port is specified // or if options.port is normalized as 0 before let backlog; if (typeof options.port === "number" || typeof options.port === "string") { validatePort(options.port, "options.port"); backlog = options.backlog || backlogFromArgs; // start TCP server listening on host:port if (options.host) { _lookupAndListen( this, options.port | 0, options.host, backlog, !!options.exclusive, flags, ); } else { // Undefined host, listens on unspecified address // Default addressType 4 will be used to search for primary server _listenInCluster( this, null, options.port | 0, 4, backlog, undefined, options.exclusive, ); } return this; } // (path[, backlog][, cb]) or (options[, cb]) // where path or options.path is a UNIX domain socket or Windows pipe if (options.path && _isPipeName(options.path)) { const pipeName = (this._pipeName = options.path); backlog = options.backlog || backlogFromArgs; _listenInCluster( this, pipeName, -1, -1, backlog, undefined, options.exclusive, ); if (!this._handle) { // Failed and an error shall be emitted in the next tick. // Therefore, we directly return. return this; } let mode = 0; if (options.readableAll === true) { mode |= PipeConstants.UV_READABLE; } if (options.writableAll === true) { mode |= PipeConstants.UV_WRITABLE; } if (mode !== 0) { const err = this._handle.fchmod(mode); if (err) { this._handle.close(); this._handle = null; throw errnoException(err, "uv_pipe_chmod"); } } return this; } if (!("port" in options || "path" in options)) { throw new ERR_INVALID_ARG_VALUE( "options", options, 'must have the property "port" or "path"', ); } throw new ERR_INVALID_ARG_VALUE("options", options); } /** * Stops the server from accepting new connections and keeps existing * connections. This function is asynchronous, the server is finally closed * when all connections are ended and the server emits a `"close"` event. * The optional `callback` will be called once the `"close"` event occurs. Unlike * that event, it will be called with an `Error` as its only argument if the server * was not open when it was closed. * * @param cb Called when the server is closed. */ close(cb?: (err?: Error) => void): this { if (typeof cb === "function") { if (!this._handle) { this.once("close", function close() { cb(new ERR_SERVER_NOT_RUNNING()); }); } else { this.once("close", cb); } } if (this._handle) { (this._handle as TCP).close(); this._handle = null; } if (this._usingWorkers) { let left = this._workers.length; const onWorkerClose = () => { if (--left !== 0) { return; } this._connections = 0; this._emitCloseIfDrained(); }; // Increment connections to be sure that, even if all sockets will be closed // during polling of workers, `close` event will be emitted only once. this._connections++; // Poll workers for (let n = 0; n < this._workers.length; n++) { this._workers[n].close(onWorkerClose); } } else { this._emitCloseIfDrained(); } return this; } /** * Returns the bound `address`, the address `family` name, and `port` of the server * as reported by the operating system if listening on an IP socket * (useful to find which port was assigned when getting an OS-assigned address):`{ port: 12346, family: "IPv4", address: "127.0.0.1" }`. * * For a server listening on a pipe or Unix domain socket, the name is returned * as a string. * * `server.address()` returns `null` before the `"listening"` event has been * emitted or after calling `server.close()`. */ address(): AddressInfo | string | null { if (this._handle && this._handle.getsockname) { const out = {}; const err = this._handle.getsockname(out); if (err) { throw errnoException(err, "address"); } return out as AddressInfo; } else if (this._pipeName) { return this._pipeName; } return null; } /** * Asynchronously get the number of concurrent connections on the server. Works * when sockets were sent to forks. * * Callback should take two arguments `err` and `count`. */ getConnections(cb: (err: Error | null, count: number) => void): this { // deno-lint-ignore no-this-alias const server = this; function end(err: Error | null, connections?: number) { defaultTriggerAsyncIdScope( server[asyncIdSymbol], nextTick, cb, err, connections, ); } if (!this._usingWorkers) { end(null, this._connections); return this; } // Poll workers let left = this._workers.length; let total = this._connections; function oncount(err: Error, count: number) { if (err) { left = -1; return end(err); } total += count; if (--left === 0) { return end(null, total); } } for (let n = 0; n < this._workers.length; n++) { this._workers[n].getConnections(oncount); } return this; } /** * Calling `unref()` on a server will allow the program to exit if this is the only * active server in the event system. If the server is already `unref`ed calling `unref()` again will have no effect. */ unref(): this { this._unref = true; if (this._handle) { this._handle.unref(); } return this; } /** * Opposite of `unref()`, calling `ref()` on a previously `unref`ed server will _not_ let the program exit if it's the only server left (the default behavior). * If the server is `ref`ed calling `ref()` again will have no effect. */ ref(): this { this._unref = false; if (this._handle) { this._handle.ref(); } return this; } /** * Indicates whether or not the server is listening for connections. */ get listening(): boolean { return !!this._handle; } _createSocket(clientHandle) { const socket = new Socket({ handle: clientHandle, allowHalfOpen: this.allowHalfOpen, pauseOnCreate: this.pauseOnConnect, readable: true, writable: true, }); // TODO(@bartlomieju): implement noDelay and setKeepAlive socket.server = this; socket._server = this; DTRACE_NET_SERVER_CONNECTION(socket); return socket; } _listen2 = _setupListenHandle; _emitCloseIfDrained() { debug("SERVER _emitCloseIfDrained"); if (this._handle || this._connections) { debug( `SERVER handle? ${!!this._handle} connections? ${this._connections}`, ); return; } // We use setTimeout instead of nextTick here to avoid EADDRINUSE error // when the same port listened immediately after the 'close' event. // ref: https://github.com/denoland/deno_std/issues/2788 defaultTriggerAsyncIdScope( this[asyncIdSymbol], setTimeout, _emitCloseNT, 0, this, ); } _setupWorker(socketList: EventEmitter) { this._usingWorkers = true; this._workers.push(socketList); // deno-lint-ignore no-explicit-any socketList.once("exit", (socketList: any) => { const index = this._workers.indexOf(socketList); this._workers.splice(index, 1); }); } [EventEmitter.captureRejectionSymbol]( err: Error, event: string, sock: Socket, ) { switch (event) { case "connection": { sock.destroy(err); break; } default: { this.emit("error", err); } } } } /** * Creates a new TCP or IPC server. * * Accepts an `options` object with properties `allowHalfOpen` (default `false`) * and `pauseOnConnect` (default `false`). * * If `allowHalfOpen` is set to `false`, then the socket will * automatically end the writable side when the readable side ends. * * If `allowHalfOpen` is set to `true`, when the other end of the socket * signals the end of transmission, the server will only send back the end of * transmission when `socket.end()` is explicitly called. For example, in the * context of TCP, when a FIN packed is received, a FIN packed is sent back * only when `socket.end()` is explicitly called. Until then the connection is * half-closed (non-readable but still writable). See `"end"` event and RFC 1122 * (section 4.2.2.13) for more information. * * `pauseOnConnect` indicates whether the socket should be paused on incoming * connections. * * If `pauseOnConnect` is set to `true`, then the socket associated with each * incoming connection will be paused, and no data will be read from its * handle. This allows connections to be passed between processes without any * data being read by the original process. To begin reading data from a paused * socket, call `socket.resume()`. * * The server can be a TCP server or an IPC server, depending on what it * `listen()` to. * * Here is an example of an TCP echo server which listens for connections on * port 8124: * * @param options Socket options. * @param connectionListener Automatically set as a listener for the `"connection"` event. * @return A `net.Server`. */ export function createServer( options?: ServerOptions, connectionListener?: ConnectionListener, ): Server { return new Server(options, connectionListener); } export { isIP, isIPv4, isIPv6 }; export default { _createServerHandle, _normalizeArgs, isIP, isIPv4, isIPv6, connect, createConnection, createServer, Server, Socket, Stream, };