/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/node/polyfills/net.ts
3 423 строки
90 KB
Bartek Iwańczuk
chore: bump node_compat test suite to Node.js 26.5.1 (#36391)
03 авг 2026, 12:52
Не верифицирован
03 авг 2026, 12:52
bca6182
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. 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. // deno-lint-ignore-file no-explicit-any (function () { const { core, primordials } = __bootstrap; const { BlockList, SocketAddress } = core.loadExtScript( "ext:deno_node/internal/blocklist.mjs", ); const { EventEmitter } = core.loadExtScript("ext:deno_node/_events.mjs"); const { isIP, isIPv4, isIPv6, kReinitializeHandle, kSetKeepAlive, kSetKeepAliveInitialDelay, kSetNoDelay, normalizedArgsSymbol, } = core.loadExtScript("ext:deno_node/internal/net.ts"); const { Duplex } = core.createLazyLoader("node:stream")(); const { asyncIdSymbol, defaultTriggerAsyncIdScope, emitDestroy, emitInit, executionAsyncId, newAsyncId, ownerSymbol, } = core.loadExtScript("ext:deno_node/internal/async_hooks.ts"); const { AbortError, ERR_INVALID_ADDRESS_FAMILY, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_INVALID_FD_TYPE, ERR_INVALID_IP_ADDRESS, ERR_IP_BLOCKED, ERR_MISSING_ARGS, ERR_SERVER_ALREADY_LISTEN, ERR_SERVER_NOT_RUNNING, ERR_SOCKET_CLOSED, ERR_SOCKET_CLOSED_BEFORE_CONNECTION, ERR_SOCKET_CONNECTION_TIMEOUT, errnoException, exceptionWithHostPort, genericNodeError, NodeAggregateError, uvExceptionWithHostPort, } = core.loadExtScript("ext:deno_node/internal/errors.ts"); type ErrnoException = any; const { kAfterAsyncWrite, kBuffer, kBufferCb, kBufferGen, kHandle, kUpdateTimer, onStreamRead, setStreamTimeout, writeGeneric, writevGeneric, } = core.loadExtScript("ext:deno_node/internal/stream_base_commons.ts"); const { kDestroy, kTimeout } = core.loadExtScript( "ext:deno_node/internal/timers.mjs", ); const { nextTick } = core.loadExtScript("ext:deno_node/_next_tick.ts"); const { DTRACE_NET_SERVER_CONNECTION, DTRACE_NET_STREAM_END, } = core.loadExtScript("ext:deno_node/internal/dtrace.ts"); const { Buffer } = core.loadExtScript("ext:deno_node/internal/buffer.mjs"); type LookupOneOptions = any; const { constants: TCPConstants, setupListenWrap, TCP, TCPConnectWrap, } = core.loadExtScript("ext:deno_node/internal_binding/tcp_wrap.ts"); const { constants: PipeConstants, Pipe, PipeConnectWrap, setupListenWrap: setupPipeListenWrap, } = core.loadExtScript("ext:deno_node/internal_binding/pipe_wrap.ts"); const { ShutdownWrap } = core.loadExtScript( "ext:deno_node/internal_binding/stream_wrap.ts", ); const { default: assert } = core.loadExtScript("ext:deno_node/assert.ts"); const { isWindows } = core.loadExtScript("ext:deno_node/_util/os.ts"); const { ADDRCONFIG, lookup: dnsLookup } = core.createLazyLoader("node:dns")() .default; const { kPermTokenSink } = core.loadExtScript( "ext:deno_node/internal_binding/cares_wrap.ts", ); const { codeMap, UV_ECANCELED, UV_ETIMEDOUT, } = core.loadExtScript("ext:deno_node/internal_binding/uv.ts"); const { guessHandleType } = core.loadExtScript( "ext:deno_node/internal_binding/util.ts", ); const { debuglog } = core.loadExtScript( "ext:deno_node/internal/util/debuglog.ts", ); type DuplexOptions = any; type BufferEncoding = any; type Abortable = any; const { channel, tracingChannel } = core.loadExtScript( "ext:deno_node/diagnostics_channel.js", ); const { registerActiveHandle, unregisterActiveHandle, } = core.loadExtScript("ext:deno_node/internal/process/active_resources.ts"); // Lazily resolved at call sites via `lazyCluster()` to break the cluster // <-> net cycle. Only used inside `_listenInCluster()`. const lazyCluster = core.createLazyLoader("node:cluster"); const { isUint8Array } = core.loadExtScript( "ext:deno_node/internal/util/types.ts", ); const { validateAbortSignal, validateBoolean, validateFunction, validateInt32, validateNumber, validatePort, validateString, validateStringWithoutNullBytes, } = core.loadExtScript("ext:deno_node/internal/validators.mjs"); const { ArrayIsArray, ArrayPrototypeIncludes, ArrayPrototypeIndexOf, ArrayPrototypePush, ArrayPrototypeSplice, Boolean, FunctionPrototypeBind, FunctionPrototypeCall, MathMax, Number, NumberIsNaN, NumberParseInt, ObjectDefineProperty, ObjectHasOwn, ObjectPrototypeIsPrototypeOf, ObjectSetPrototypeOf, Promise, PromiseWithResolvers, ReflectHas, SafeArrayIterator, StringPrototypeCharCodeAt, Symbol, SymbolAsyncDispose, } = primordials; let debug = debuglog("net", (fn) => { debug = fn; }); const kLastWriteQueueSize = Symbol("lastWriteQueueSize"); const kBytesRead = Symbol("kBytesRead"); const kBytesWritten = Symbol("kBytesWritten"); // Holds the async context (AsyncLocalStorage / async_hooks) snapshot captured // when a connect request or listening handle is set up. The native libuv // callbacks in tcp_wrap/pipe_wrap invoke the completion callbacks directly, // outside of any microtask or nextTick, so the snapshot has to be captured at // registration time and restored before dispatching. Otherwise // `AsyncLocalStorage.getStore()` returns `undefined` inside `connect` and // `connection` handlers. See https://github.com/denoland/deno/issues/35154. const kAsyncContext = Symbol("kAsyncContext"); // Runs `run` with the async context snapshot that was active when the // operation was initiated, restoring the previous context afterwards. function _runInAsyncContext(snapshot: any, run: () => void) { if (snapshot === undefined) { run(); return; } const prior = core.getAsyncContext(); core.setAsyncContext(snapshot); try { run(); } finally { core.setAsyncContext(prior); } } const DEFAULT_IPV4_ADDR = "0.0.0.0"; const DEFAULT_IPV6_ADDR = "::"; let autoSelectFamilyDefault = true; let autoSelectFamilyAttemptTimeoutDefault = 500; 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; autoSelectFamily?: boolean | undefined; autoSelectFamilyAttemptTimeout?: number | undefined; } interface IpcSocketConnectOptions extends ConnectOptions { path: string; } type SocketConnectOptions = TcpSocketConnectOptions | IpcSocketConnectOptions; function _getNewAsyncId(handle?: Handle): number { if (!handle || typeof handle.getAsyncId !== "function") { return newAsyncId(); } // Node's ASSIGN_OR_RETURN_UNWRAP in the C++ AsyncWrap::GetAsyncId returns // silently if the receiver isn't a real AsyncWrap, so user-land handles that // expose `getAsyncId` borrowed from a prototype (or that have been wrapped // by libraries like pg/sequelize across socket reuse) don't crash on Node. // Deno's op2 brand check throws `TypeError: expected AsyncWrap` in the same // shape, so match Node's tolerance by falling back to a fresh async id. try { return handle.getAsyncId(); } catch { return newAsyncId(); } } const providerTypeNames = [ "NONE", "DIRHANDLE", "DNSCHANNEL", "ELDHISTOGRAM", "FILEHANDLE", "FILEHANDLECLOSEREQ", "FIXEDSIZEBLOBCOPY", "FSEVENTWRAP", "FSREQCALLBACK", "FSREQPROMISE", "GETADDRINFOREQWRAP", "GETNAMEINFOREQWRAP", "HEAPSNAPSHOT", "HTTP2SESSION", "HTTP2STREAM", "HTTP2PING", "HTTP2SETTINGS", "HTTPINCOMINGMESSAGE", "HTTPCLIENTREQUEST", "JSSTREAM", "JSUDPWRAP", "MESSAGEPORT", "PIPECONNECTWRAP", "PIPESERVERWRAP", "PIPEWRAP", "PROCESSWRAP", "PROMISE", "QUERYWRAP", "SHUTDOWNWRAP", "SIGNALWRAP", "STATWATCHER", "STREAMPIPE", "TCPCONNECTWRAP", "TCPSERVERWRAP", "TCPWRAP", "TLSWRAP", "TTYWRAP", "UDPSENDWRAP", "UDPWRAP", "SIGINTWATCHDOG", "WORKER", "WORKERHEAPSNAPSHOT", "WRITEWRAP", "ZLIB", ]; function _emitHandleInit(handle: Handle, asyncId: number) { if (typeof (handle as any).getProviderType !== "function") { return; } const providerType = (handle as any).getProviderType(); emitInit( asyncId, providerTypeNames[providerType] || "UNKNOWN", executionAsyncId(), handle, ); } interface NormalizedArgs { 0: Partial<NetConnectOptions | ListenOptions>; 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"); const netServerListenChannel = tracingChannel("net.server.listen"); 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()) 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<NetConnectOptions> | number | string; let options: Partial<SocketConnectOptions> = {}; 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 _afterConnect( status: number, handle: any, req: PipeConnectWrap | TCPConnectWrap, readable: boolean, writable: boolean, ) { _runInAsyncContext( handle?.[kAsyncContext], () => _afterConnectImpl(status, handle, req, readable, writable), ); } function _afterConnectImpl( status: number, 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) { // deno-lint-ignore deno-internal/prefer-primordials -- Readable stream method, not Array.prototype.push socket.push(null); socket.read(); } if (socket.writable && !writable) { socket.end(); } if (socket[kSetNoDelay] && socket._handle?.setNoDelay) { socket._handle.setNoDelay(true); } if (socket[kSetKeepAlive] && socket._handle?.setKeepAlive) { socket._handle.setKeepAlive(true, socket[kSetKeepAliveInitialDelay]); } socket._unrefTimer(); socket.emit("connect"); socket.emit("ready"); // Deno specific: run tls handshake if it's from a tls socket if (typeof handle.afterConnectTls === "function") { handle.afterConnectTls(); } // 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 (req.localAddress && req.localPort) { details = req.localAddress + ":" + req.localPort; } const ex = exceptionWithHostPort( status, "connect", req.address, (req as TCPConnectWrap).port, details, ); if (details) { ex.localAddress = req.localAddress; ex.localPort = req.localPort; } socket.destroy(ex); } } function _createConnectionError(req, status) { let details; if (req.localAddress && req.localPort) { details = req.localAddress + ":" + req.localPort; } const ex = exceptionWithHostPort( status, "connect", req.address, req.port, details, ); if (details) { ex.localAddress = req.localAddress; ex.localPort = req.localPort; } return ex; } function _afterConnectMultiple( context, current, status, handle, req, readable, writable, ) { debug( "connect/multiple: connection attempt to %s:%s completed with status %s", req.address, req.port, status, ); // Make sure another connection is not spawned clearTimeout(context[kTimeout]); // One of the connection has completed and correctly dispatched but after timeout, ignore this one if (status === 0 && current !== context.current - 1) { debug( "connect/multiple: ignoring successful but timedout connection to %s:%s", req.address, req.port, ); handle.close(); return; } const self = context.socket; // Some error occurred, add to the list of exceptions if (status !== 0) { const ex = _createConnectionError(req, status); ArrayPrototypePush(context.errors, ex); self.emit( "connectionAttemptFailed", req.address, req.port, req.addressType, ex, ); // Try the next address, unless we were aborted if (context.socket.connecting) { _internalConnectMultiple(context, status === UV_ECANCELED); } return; } _afterConnect(status, self._handle, req, readable, writable); } function _internalConnectMultipleTimeout(context, req, handle) { debug( "connect/multiple: connection to %s:%s timed out", req.address, req.port, ); context.socket.emit( "connectionAttemptTimeout", req.address, req.port, req.addressType, ); req.oncomplete = undefined; ArrayPrototypePush( context.errors, _createConnectionError(req, UV_ETIMEDOUT), ); handle.close(); // Try the next address, unless we were aborted if (context.socket.connecting) { _internalConnectMultiple(context); } } 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<string, never> = {}; err = handle.getsockname(out); if (err === 0 && port !== out.port) { err = codeMap.get("EADDRINUSE")!; } } return err; } function _isPipe( options: Partial<SocketConnectOptions>, ): options is IpcSocketConnectOptions { return ObjectHasOwn(options, "path") && !!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 | undefined, flags: number = 0, ) { assert(socket.connecting); if ( socket.blockList?.check(address, `ipv${addressType}`) ) { socket.destroy(new ERR_IP_BLOCKED(address)); return; } let err; if (localAddress || localPort) { localPort = (localPort ?? 0) | 0; if (addressType === 4) { localAddress = localAddress || DEFAULT_IPV4_ADDR; // deno-lint-ignore deno-internal/prefer-primordials -- libuv handle.bind(), not Function.prototype.bind 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; try { if (addressType === 4) { err = (socket._handle as TCP).connect(req, address, port); } else { err = (socket._handle as TCP).connect6(req, address, port); } } catch (e) { socket.destroy(e); return; } } 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); } } function _internalConnectMultiple(context, canceled?: boolean) { clearTimeout(context[kTimeout]); const self = context.socket; // We were requested to abort. Stop all operations if (self._aborted) { return; } // All connections have been tried without success, destroy with error if (canceled || context.current === context.addresses.length) { if (context.errors.length === 0) { self.destroy(new ERR_SOCKET_CONNECTION_TIMEOUT()); return; } self.destroy(new NodeAggregateError(context.errors)); return; } assert(self.connecting); const current = context.current++; if (current > 0) { self[kReinitializeHandle](new TCP(TCPConstants.SOCKET)); } const { localPort, port, flags } = context; const { address, family: addressType } = context.addresses[current]; let localAddress; let err; if (localPort) { if (addressType === 4) { localAddress = DEFAULT_IPV4_ADDR; // deno-lint-ignore deno-internal/prefer-primordials -- libuv handle.bind(), not Function.prototype.bind err = self._handle.bind(localAddress, localPort); } else { // addressType === 6 localAddress = DEFAULT_IPV6_ADDR; err = self._handle.bind6(localAddress, localPort, flags); } debug( "connect/multiple: binding to localAddress: %s and localPort: %d (addressType: %d)", localAddress, localPort, addressType, ); err = _checkBindError(err, localPort, self._handle); if (err) { ArrayPrototypePush( context.errors, exceptionWithHostPort(err, "bind", localAddress, localPort), ); _internalConnectMultiple(context); return; } } if ( self.blockList?.check(address, `ipv${addressType}`) ) { ArrayPrototypePush( context.errors, new ERR_IP_BLOCKED(address), ); _internalConnectMultiple(context); return; } debug( "connect/multiple: attempting to connect to %s:%d (addressType: %d)", address, port, addressType, ); self.emit("connectionAttempt", address, port, addressType); const req = new TCPConnectWrap(); req.oncomplete = FunctionPrototypeBind( _afterConnectMultiple, undefined, context, current, ); req.address = address; req.port = port; req.localAddress = localAddress; req.localPort = localPort; req.addressType = addressType; ArrayPrototypePush( self.autoSelectFamilyAttemptedAddresses, `${address}:${port}`, ); if (addressType === 4) { err = self._handle.connect(req, address, port); } else { err = self._handle.connect6(req, address, port); } if (err) { const sockname = self._getsockname(); let details; if (sockname) { details = sockname.address + ":" + sockname.port; } const ex = exceptionWithHostPort(err, "connect", address, port, details); ArrayPrototypePush(context.errors, ex); self.emit("connectionAttemptFailed", address, port, addressType, ex); _internalConnectMultiple(context); return; } if (current < context.addresses.length - 1) { debug( "connect/multiple: setting the attempt timeout to %d ms", context.timeout, ); // If the attempt has not returned an error, start the connection timer context[kTimeout] = setTimeout( _internalConnectMultipleTimeout, context.timeout, context, req, self._handle, ); } } // 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, chunk: any, encoding?: | BufferEncoding | null | ((error: Error | null | undefined) => void), cb?: (error: Error | null | undefined) => void, ): boolean { if (!this.writableEnded) { return FunctionPrototypeCall( Duplex.prototype.write, 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; if (this.writable) { // Defer end() to nextTick so that user 'end' handlers registered // after the constructor (which registered _onReadableStreamEnd) // see socket.writable === true, matching Node.js behavior where // the writable getter doesn't reflect the ending state immediately. // deno-lint-ignore no-this-alias const socket = this; nextTick(() => { if (socket.writable && !socket.destroyed) socket.end(); }); } } } // 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) { (socket._handle as any)[ownerSymbol] = socket; socket._handle.onread = onStreamRead; socket[asyncIdSymbol] = _getNewAsyncId(socket._handle); _emitHandleInit(socket._handle, socket[asyncIdSymbol]); 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, autoSelectFamilyAttemptTimeout, autoSelectFamily } = options; validateStringWithoutNullBytes(host, "options.host"); 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 (autoSelectFamily != null) { validateBoolean(autoSelectFamily, "options.autoSelectFamily"); } else { autoSelectFamily = autoSelectFamilyDefault; } if (autoSelectFamilyAttemptTimeout !== undefined) { validateInt32( autoSelectFamilyAttemptTimeout, "options.autoSelectFamilyAttemptTimeout", 1, ); if (autoSelectFamilyAttemptTimeout < 10) { autoSelectFamilyAttemptTimeout = 10; } } else { autoSelectFamilyAttemptTimeout = autoSelectFamilyAttemptTimeoutDefault; } // 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, all: false, }; 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; // Only the built-in DNS lookup is trusted to install a NetPermToken on the // handle (so `--allow-net=<host>` keeps authorizing the IPs Deno resolved // for it). A custom `lookup` returns arbitrary IPs, so its result is checked // against the literal IP with no hostname substitution (GHSA-fhjh-jqv7-m238). // This must stay in sync with the `lookup` selection below: any falsy // `options.lookup` falls back to the built-in resolver, so it is also the // default-lookup path for token purposes. const lookup = options.lookup || dnsLookup; const usingDefaultLookup = lookup === dnsLookup; const getLookupDnsOpts = () => { if (usingDefaultLookup) { return { ...dnsOpts, port }; } return { family: dnsOpts.family, hints: dnsOpts.hints, all: dnsOpts.all, }; }; if ( dnsOpts.family !== 4 && dnsOpts.family !== 6 && !localAddress && autoSelectFamily ) { debug("connect: autodetecting"); dnsOpts.all = true; defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () { _lookupAndConnectMultiple( self, asyncIdSymbol, lookup, host, options, getLookupDnsOpts(), port, localAddress, localPort, autoSelectFamilyAttemptTimeout, usingDefaultLookup, ); }); return; } defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () { function emitLookup( err: ErrnoException | null, ip: string, addressType: number, netPermToken, ) { // Store the permission token from the built-in DNS lookup so connect() // checks permissions against the original hostname instead of the // resolved IP. Only honored on the default-lookup path; a custom lookup // never installs a token (its IPs are checked literally). if ( usingDefaultLookup && netPermToken && self._handle?.setNetPermToken ) { self._handle.setNetPermToken(netPermToken); } 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], nextTick, () => { if (self.connecting) { defaultTriggerAsyncIdScope( self[asyncIdSymbol], _internalConnect, self, ip, port, addressType, localAddress, localPort, ); } }); } } // Tag the trusted callback so the built-in dns.lookup hands it the // NetPermToken; user-supplied lookups never receive it. if (usingDefaultLookup) { emitLookup[kPermTokenSink] = true; } lookup(host, getLookupDnsOpts(), emitLookup); }); } function _lookupAndConnectMultiple( self: Socket, asyncIdSymbol: number, lookup: any, host: string, options: TcpSocketConnectOptions, dnsopts, port: number, localAddress: string, localPort: number, timeout: number | undefined, usingDefaultLookup: boolean, ) { defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () { function emitLookup(err, addresses, _, netPermToken) { // Only the built-in lookup installs a token (see _lookupAndConnect). if ( usingDefaultLookup && netPermToken && self._handle?.setNetPermToken ) { self._handle.setNetPermToken(netPermToken); } // 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; } else if (err) { self.emit("lookup", err, undefined, undefined, host); // 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); return; } // Filter addresses by only keeping the one which are either IPv4 or IPV6. // The first valid address determines which group has preference on the // alternate family sorting which happens later. const validAddresses = [[], []]; const validIps = [[], []]; let destinations; for (let i = 0, l = addresses.length; i < l; i++) { const address = addresses[i]; const { address: ip, family: addressType } = address; self.emit("lookup", err, ip, addressType, host); // It's possible we were destroyed while looking this up. if (!self.connecting) { return; } if (isIP(ip) && (addressType === 4 || addressType === 6)) { destinations ||= addressType === 6 ? { 6: 0, 4: 1 } : { 4: 0, 6: 1 }; const destination = destinations[addressType]; // Only try an address once if (!ArrayPrototypeIncludes(validIps[destination], ip)) { ArrayPrototypePush(validAddresses[destination], address); ArrayPrototypePush(validIps[destination], ip); } } } // When no AAAA or A records are available, fail on the first one if (!validAddresses[0].length && !validAddresses[1].length) { const { address: firstIp, family: firstAddressType } = addresses[0]; if (!isIP(firstIp)) { err = new ERR_INVALID_IP_ADDRESS(firstIp); nextTick(_connectErrorNT, self, err); } else if (firstAddressType !== 4 && firstAddressType !== 6) { err = new ERR_INVALID_ADDRESS_FAMILY( firstAddressType, options.host, options.port, ); nextTick(_connectErrorNT, self, err); } return; } // Sort addresses alternating families const toAttempt = []; for ( let i = 0, l = MathMax(validAddresses[0].length, validAddresses[1].length); i < l; i++ ) { if (ObjectHasOwn(validAddresses[0], i)) { ArrayPrototypePush(toAttempt, validAddresses[0][i]); } if (ObjectHasOwn(validAddresses[1], i)) { ArrayPrototypePush(toAttempt, validAddresses[1][i]); } } if (toAttempt.length === 1) { debug( "connect/multiple: only one address found, switching back to single connection", ); const { address: ip, family: addressType } = toAttempt[0]; self._unrefTimer(); defaultTriggerAsyncIdScope( self[asyncIdSymbol], _internalConnect, self, ip, port, addressType, localAddress, localPort, ); return; } self.autoSelectFamilyAttemptedAddresses = []; debug( "connect/multiple: will try the following addresses", toAttempt, ); const context = { socket: self, addresses: toAttempt, current: 0, port, localPort, flags: 0, timeout, [kTimeout]: null, errors: [], }; self._unrefTimer(); defaultTriggerAsyncIdScope( self[asyncIdSymbol], _internalConnectMultiple, context, ); } // Tag the trusted callback so the built-in dns.lookup hands it the // NetPermToken; user-supplied lookups never receive it. if (usingDefaultLookup) { emitLookup[kPermTokenSink] = true; } lookup(host, dnsopts, emitLookup); }); } function _afterShutdown(this: ShutdownWrap<TCP>) { 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"); } function _addClientAbortSignalOption(socket: Socket, signal: AbortSignal) { if (signal.aborted) { nextTick(() => socket.destroy(new AbortError())); return; } const onAbort = () => { socket.destroy(new AbortError()); }; // Match Node: register on nextTick so synchronous listenerCount checks // immediately after Socket construction don't double-count the listener // already attached by Duplex via addAbortSignal. nextTick(() => { if (socket.destroyed) return; signal.addEventListener("abort", onAbort, { once: true }); socket.once("close", () => { signal.removeEventListener("abort", onAbort); }); }); } // The packages that need socket initialization workaround /** * 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. */ function Socket(options) { if (!ObjectPrototypeIsPrototypeOf(Socket.prototype, this)) { return new Socket(options); } if (typeof options === "number") { // Legacy interface. options = { fd: options }; } else { options = { ...options }; } if (options.objectMode) { throw new ERR_INVALID_ARG_VALUE( "options.objectMode", options.objectMode, "is not supported", ); } if (options.readableObjectMode) { throw new ERR_INVALID_ARG_VALUE( "options.readableObjectMode", options.readableObjectMode, "is not supported", ); } if (options.writableObjectMode) { throw new ERR_INVALID_ARG_VALUE( "options.writableObjectMode", options.writableObjectMode, "is not supported", ); } // 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; FunctionPrototypeCall(Duplex, this, options); this[asyncIdSymbol] = -1; this[kHandle] = null; this[kSetNoDelay] = Boolean(options.noDelay); this[kSetKeepAlive] = Boolean(options.keepAlive); this[kSetKeepAliveInitialDelay] = ~~(options.keepAliveInitialDelay / 1000); this[kLastWriteQueueSize] = 0; this[kTimeout] = null; this[kBuffer] = null; this[kBufferCb] = null; this[kBufferGen] = null; this[kBytesRead] = 0; this[kBytesWritten] = 0; this.server = null; this._server = null; this._peername = undefined; this._sockname = undefined; this._pendingData = null; this._pendingEncoding = ""; this._host = null; this._parent = null; this.autoSelectFamilyAttemptedAddresses = undefined; this.connecting = false; if (options.blockList) { this.blockList = options.blockList; } if (options.handle) { this._handle = options.handle; this[asyncIdSymbol] = _getNewAsyncId(this._handle); _emitHandleInit(this._handle, this[asyncIdSymbol]); } else if (options.fd !== undefined) { const { fd } = options; // createHandle will throw ERR_INVALID_FD_TYPE if `fd` is not // a valid `PIPE` or `TCP` descriptor this._handle = _createHandle(fd, false); const err = this._handle.open(fd); // While difficult to fabricate, in some architectures // `open` may return an error code for valid file descriptors // which cannot be opened. if (err) { throw errnoException(err, "open"); } this[asyncIdSymbol] = _getNewAsyncId(this._handle); _emitHandleInit(this._handle, this[asyncIdSymbol]); if ( (fd === 1 || fd === 2) && ObjectPrototypeIsPrototypeOf(Pipe.prototype, this._handle) && isWindows ) { // Make stdout and stderr blocking on Windows const blockErr = this._handle.setBlocking(true); if (blockErr) { throw errnoException(blockErr, "setBlocking"); } } } const onread = options.onread; if ( onread !== null && typeof onread === "object" ) { // deno-lint-ignore deno-internal/prefer-primordials -- user option object property, not TypedArray#buffer const onreadBuffer = onread.buffer; if ( (isUint8Array(onreadBuffer) || typeof onreadBuffer === "function") && typeof onread.callback === "function" ) { if (typeof onreadBuffer === "function") { this[kBuffer] = true; this[kBufferGen] = onreadBuffer; } else { this[kBuffer] = onreadBuffer; } this[kBufferCb] = onread.callback; } } this.on("end", _onReadableStreamEnd); _initSocketHandle(this); if (this._handle && options.readable !== false) { if (options.pauseOnCreate) { this._handle.reading = false; this._handle.readStop(); this.readableFlowing = false; } else if (!options.manualStart) { this.read(0); } } if (options.signal) { _addClientAbortSignalOption(this, options.signal); } } ObjectSetPrototypeOf(Socket.prototype, Duplex.prototype); ObjectSetPrototypeOf(Socket, Duplex); Socket.prototype.connect = function (...args) { let normalized; if (ArrayIsArray(args[0]) && args[0][normalizedArgsSymbol]) { normalized = args[0]; } else { normalized = _normalizeArgs(args); } const options = normalized[0]; const cb = normalized[1]; if (options.port === undefined && options.path == null) { throw new ERR_MISSING_ARGS(["options", "port", "path"]); } if (netClientSocketChannel.hasSubscribers) { netClientSocketChannel.publish({ socket: this, }); } 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; const pipe = _isPipe(options); debug("pipe", pipe, path); if (!this._handle) { this._handle = pipe ? new Pipe(PipeConstants.SOCKET) : new TCP(TCPConstants.SOCKET); _initSocketHandle(this); } // Capture the async context now, while we are still running synchronously // inside the caller's context. The DNS lookup that precedes the actual // connect is async and would otherwise drop it before `_afterConnect` runs. this._handle[kAsyncContext] = core.getAsyncContext(); 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 { if (options.keepAlive !== undefined) { this.setKeepAlive( !!options.keepAlive, options.keepAliveInitialDelay, ); } if (options.noDelay !== undefined) { this.setNoDelay(options.noDelay); } _lookupAndConnect(this, options); } return this; }; Socket.prototype.pause = function () { if (!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 FunctionPrototypeCall(Duplex.prototype.pause, this); }; Socket.prototype.resume = function () { if (!this.connecting && this._handle && !this._handle.reading) { _tryReadStart(this); } return FunctionPrototypeCall(Duplex.prototype.resume, this); }; Socket.prototype.setTimeout = setStreamTimeout; Socket.prototype.setNoDelay = function (noDelay) { if (!this._handle) { this.once( "connect", noDelay ? this.setNoDelay : () => this.setNoDelay(noDelay), ); return this; } const newValue = noDelay === undefined ? true : !!noDelay; if (newValue !== this[kSetNoDelay]) { this[kSetNoDelay] = newValue; if (ReflectHas(this._handle, "setNoDelay") && this._handle.setNoDelay) { this._handle.setNoDelay(newValue); } } return this; }; Socket.prototype.setKeepAlive = function (enable, initialDelay) { if (!this._handle) { this.once("connect", () => this.setKeepAlive(enable, initialDelay)); return this; } const newEnable = Boolean(enable); const newDelay = ~~(initialDelay / 1000); if ( newEnable !== this[kSetKeepAlive] || newDelay !== this[kSetKeepAliveInitialDelay] ) { this[kSetKeepAlive] = newEnable; this[kSetKeepAliveInitialDelay] = newDelay; if ( ReflectHas(this._handle, "setKeepAlive") && this._handle.setKeepAlive ) { this._handle.setKeepAlive(newEnable, newDelay); } } return this; }; Socket.prototype.address = function () { return this._getsockname(); }; Socket.prototype.unref = function () { if (!this._handle) { this.once("connect", this.unref); return this; } if (typeof this._handle.unref === "function") { this._handle.unref(); } return this; }; Socket.prototype.ref = function () { if (!this._handle) { this.once("connect", this.ref); return this; } if (typeof this._handle.ref === "function") { this._handle.ref(); } return this; }; Socket.prototype.resetAndDestroy = function () { if (this.destroyed) { return this; } if ( !this._handle || !ObjectPrototypeIsPrototypeOf(TCP.prototype, this._handle) ) { this.destroy( new ERR_SOCKET_CLOSED(), ); return this; } if (this.connecting) { this.once("connect", () => this._reset()); this.destroy(); return this; } this._reset(); return this; }; Socket.prototype._reset = function () { this._resetAndClosing = true; this.destroy(); }; ObjectDefineProperty(Socket.prototype, "bufferSize", { __proto__: null, get: function () { if (this._handle) { return this.writableLength; } return undefined; }, }); ObjectDefineProperty(Socket.prototype, "bytesRead", { __proto__: null, get: function () { return this._handle ? (this._handle.getBytesRead?.() ?? this._handle.bytesRead ?? 0) : this[kBytesRead]; }, }); ObjectDefineProperty(Socket.prototype, "bytesWritten", { __proto__: null, get: function () { let bytes = this._bytesDispatched; const data = this._pendingData; const encoding = this._pendingEncoding; const writableBuffer = this.writableBuffer; if (!writableBuffer) { return undefined; } for (const el of new SafeArrayIterator(writableBuffer)) { bytes += ObjectPrototypeIsPrototypeOf(Buffer.prototype, el.chunk) ? el.chunk.length : Buffer.byteLength(el.chunk, el.encoding); } if (ArrayIsArray(data)) { for (let i = 0; i < data.length; i++) { const chunk = data[i]; if ( data.allBuffers || ObjectPrototypeIsPrototypeOf(Buffer.prototype, chunk) ) { bytes += chunk.length; } else { bytes += Buffer.byteLength(chunk.chunk, chunk.encoding); } } } else if (data) { if (typeof data !== "string") { bytes += data.length; } else { bytes += Buffer.byteLength(data, encoding); } } return bytes; }, }); ObjectDefineProperty(Socket.prototype, "localAddress", { __proto__: null, get: function () { return this._getsockname().address; }, }); ObjectDefineProperty(Socket.prototype, "localPort", { __proto__: null, get: function () { return this._getsockname().port; }, }); ObjectDefineProperty(Socket.prototype, "localFamily", { __proto__: null, get: function () { return this._getsockname().family; }, }); ObjectDefineProperty(Socket.prototype, "remoteAddress", { __proto__: null, get: function () { return this._getpeername().address; }, }); ObjectDefineProperty(Socket.prototype, "remoteFamily", { __proto__: null, get: function () { return this._getpeername().family; }, }); ObjectDefineProperty(Socket.prototype, "remotePort", { __proto__: null, get: function () { return this._getpeername().port; }, }); ObjectDefineProperty(Socket.prototype, "pending", { __proto__: null, get: function () { return !this._handle || this.connecting; }, }); ObjectDefineProperty(Socket.prototype, "readyState", { __proto__: null, get: function () { 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"; }, }); Socket.prototype.end = function (data, encoding, cb) { FunctionPrototypeCall(Duplex.prototype.end, this, data, encoding, cb); DTRACE_NET_STREAM_END(this); return this; }; Socket.prototype.read = function (size) { if ( this[kBuffer] && !this.connecting && this._handle && !this._handle.reading ) { _tryReadStart(this); } return FunctionPrototypeCall(Duplex.prototype.read, this, size); }; Socket.prototype.destroySoon = function () { if (this.writable) { this.end(); } if (this.writableFinished) { this.destroy(); } else { this.once("finish", this.destroy); } }; Socket.prototype._unrefTimer = function () { // deno-lint-ignore no-this-alias for (let s = this; s != null; s = s._parent) { if (s[kTimeout]) { s[kTimeout].refresh(); } } }; Socket.prototype._final = function (cb) { if (this.connecting) { 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")) { return cb(); } else if (err !== 0) { return cb(errnoException(err, "shutdown")); } }; Socket.prototype._onTimeout = function () { const handle = this._handle; const lastWriteQueueSize = this[kLastWriteQueueSize]; if (lastWriteQueueSize > 0 && handle) { const { writeQueueSize } = handle; if (lastWriteQueueSize !== writeQueueSize) { this[kLastWriteQueueSize] = writeQueueSize; this._unrefTimer(); return; } } debug("_onTimeout"); this.emit("timeout"); }; Socket.prototype._read = function (size) { 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); } }; Socket.prototype._destroy = function (exception, cb) { debug("destroy"); this.connecting = false; // deno-lint-ignore no-this-alias for (let s = this; s != null; s = s._parent) { if (s[kTimeout]) { s[kTimeout][kDestroy](); } } debug("close"); if (this._handle) { debug("close handle"); const isException = exception ? true : false; this[kBytesRead] = this._handle.getBytesRead?.() ?? this._handle.bytesRead ?? 0; this[kBytesWritten] = this._handle.getBytesWritten?.() ?? this._handle.bytesWritten ?? 0; if (this._resetAndClosing) { this._resetAndClosing = false; if (typeof this._handle.reset === "function") { this._handle.reset(); } } const handle = this._handle; // Call cb first so the stream's error emission (via nextTick) is // scheduled before the handle close callback. This ensures 'error' // fires before 'close', matching Node.js behavior. this._handle = null; this._sockname = undefined; cb(exception); handle.close(() => { handle.onread = _noop; if (this[asyncIdSymbol] > 0) { emitDestroy(this[asyncIdSymbol]); } debug("emit close"); this.emit("close", isException); }); } else { cb(exception); nextTick(_emitCloseNT, this); } if (this._server) { debug("has server"); const server = this._server; server._connections--; if (server._emitCloseIfDrained) { if ( !server._handle && server._connections === 0 && this._httpMessageDetached === true && !this._httpMessage ) { defaultTriggerAsyncIdScope( server[asyncIdSymbol], nextTick, _emitCloseNT, server, ); } else { server._emitCloseIfDrained(); } } } }; Socket.prototype._getpeername = function () { if ( !this._handle || !ReflectHas(this._handle, "getpeername") || this.connecting ) { return this._peername || {}; } else if (!this._peername) { this._peername = {}; this._handle.getpeername(this._peername); } return this._peername; }; Socket.prototype._getsockname = function () { if (!this._handle || !ReflectHas(this._handle, "getsockname")) { return {}; } else if (!this._sockname) { this._sockname = {}; this._handle.getsockname(this._sockname); } return this._sockname; }; Socket.prototype._writeGeneric = function (writev, data, encoding, cb) { if (this.connecting) { this._pendingData = data; this._pendingEncoding = encoding; const onClose = () => { cb(new ERR_SOCKET_CLOSED_BEFORE_CONNECTION()); }; this.once("connect", function connect() { this.off("close", onClose); this._writeGeneric(writev, data, encoding, cb); }); this.once("close", onClose); 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; } }; Socket.prototype._writev = function (chunks, cb) { this._writeGeneric(true, chunks, "", cb); }; Socket.prototype._write = function (data, encoding, cb) { this._writeGeneric(false, data, encoding, cb); }; Socket.prototype[kAfterAsyncWrite] = function () { this[kLastWriteQueueSize] = 0; }; ObjectDefineProperty(Socket.prototype, kUpdateTimer, { __proto__: null, get: function () { return this._unrefTimer; }, }); ObjectDefineProperty(Socket.prototype, "_connecting", { __proto__: null, get: function () { return this.connecting; }, }); ObjectDefineProperty(Socket.prototype, "_bytesDispatched", { __proto__: null, get: function () { return this._handle ? (this._handle.getBytesWritten?.() ?? this._handle.bytesWritten ?? 0) : this[kBytesWritten]; }, }); ObjectDefineProperty(Socket.prototype, "_handle", { __proto__: null, get: function () { return this[kHandle]; }, set: function (v) { if (this[kHandle] && !v) { unregisterActiveHandle(this); } else if (!this[kHandle] && v) { registerActiveHandle( this, ObjectPrototypeIsPrototypeOf(TCP.prototype, v) ? "TCPSocketWrap" : "PipeWrap", ); } this[kHandle] = v; }, }); Socket.prototype[kReinitializeHandle] = function (handle) { this._handle?.close(); // Make sure TLS wrap works after reinitialize. if (typeof this._handle?.afterConnectTls === "function") { const { promise, resolve } = PromiseWithResolvers(); handle.afterConnectTls = this._handle.afterConnectTls; handle.afterConnectTlsResolve = resolve; handle.upgrading = promise; handle.verifyError = this._handle.verifyError; handle._parent = handle; handle._parentWrap = this._handle._parentWrap; } this._handle = handle; this._handle[ownerSymbol] = this; _initSocketHandle(this); }; 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]); // function connect( options: NetConnectOptions, connectionListener?: () => void, ): Socket; function connect( port: number, host?: string, connectionListener?: () => void, ): Socket; function connect(path: string, connectionListener?: () => void): Socket; function connect(...args: unknown[]) { const normalized = _normalizeArgs(args); const options = normalized[0] as Partial<NetConnectOptions>; debug("createConnection", normalized); const socket = new Socket(options); if (options.timeout) { socket.setTimeout(options.timeout); } // `Socket.prototype.connect` publishes `net.client.socket` so that all // entry points (net.connect, net.createConnection, new net.Socket().connect, // tls.connect via TLSSocket extending Socket) emit exactly once. return socket.connect(normalized); } const createConnection = connect; /** https://docs.deno.com/api/node/net/#namespace_getdefaultautoselectfamily */ function getDefaultAutoSelectFamily() { return autoSelectFamilyDefault; } /** https://docs.deno.com/api/node/net/#namespace_setdefaultautoselectfamily */ function setDefaultAutoSelectFamily(value: boolean) { validateBoolean(value, "value"); autoSelectFamilyDefault = value; } /** https://docs.deno.com/api/node/net/#namespace_getdefaultautoselectfamilyattempttimeout */ function getDefaultAutoSelectFamilyAttemptTimeout() { return autoSelectFamilyAttemptTimeoutDefault; } /** https://docs.deno.com/api/node/net/#namespace_setdefaultautoselectfamilyattempttimeout */ function setDefaultAutoSelectFamilyAttemptTimeout(value: number) { validateInt32(value, "value", 1); if (value < 10) { value = 10; } autoSelectFamilyAttemptTimeoutDefault = value; } 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; /** * If set to true, it disables the use of Nagle's algorithm immediately * after a new incoming connection is received. * Default: false */ noDelay?: 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, reusePort?: boolean): number { let flags = 0; if (ipv6Only === true) { flags |= TCPConstants.UV_TCP_IPV6ONLY; } if (reusePort === true) { flags |= TCPConstants.UV_TCP_REUSEPORT; } return flags; } function _listenInCluster( server: Server, address: string | null, port: number | null, addressType: number | null, backlog: number, fd?: number | null, exclusive?: boolean, flags?: number, ) { exclusive = !!exclusive; // Resolved lazily to break the cluster <-> net cycle; by the time a server // is being listen()ed on, both modules are evaluated. const cluster = lazyCluster().default; if (cluster.isPrimary || exclusive) { server._listen2(address, port, addressType, backlog, fd, flags); return; } const serverQuery = { address, port, addressType, // Match Node: pass `undefined` (not null) when no fd was provided so // the primary's `RoundRobinHandle`'s `fd >= 0` check (where `null >= 0` // is `true` in JS) doesn't take the fd branch. fd: fd == null ? undefined : fd, flags, backlog, }; const listeningId = server._listeningId; // Get the primary's server handle, and listen on it. cluster._getServer(server, serverQuery, listenOnPrimaryHandle); function listenOnPrimaryHandle(err: number, handle: Handle) { if (listeningId !== server._listeningId) { if (handle) handle.close(); return; } // Mirrors Node: a SharedHandle bind failure is deferred by libuv (it // returns 0 from bind() and stashes EADDRINUSE for the listen syscall). // Once the worker dups the fd, that delayed_error is gone -- the new // wrap is a fresh tcp_t. Detect the bind failure here by comparing the // expected port against what `getsockname()` actually reports (a failed // bind followed by listen would have the kernel auto-bind to port 0). err = _checkBindError(err, port as number, handle as TCP); if (err) { const ex = uvExceptionWithHostPort(err, "bind", address, port); unregisterActiveHandle(server); server.emit("error", ex); return; } if (server._handle) { server._handle.close(); unregisterActiveHandle(server); } server._handle = handle; server._listen2(address, port, addressType, backlog, fd, flags); } } function _lookupAndListen( server: Server, port: number, address: string, backlog: number, exclusive: boolean, flags: number, ) { const listeningId = server._listeningId; dnsLookup(address, { port }, function doListen(err, ip, addressType) { if (server._listeningId !== listeningId) { return; } 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 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 = NumberParseInt( Deno.env.get("NODE_PENDING_PIPE_INSTANCES") ?? "", ); if (!NumberIsNaN(instances)) { handle.setPendingInstances!(instances); } } } else { handle = new TCP(TCPConstants.SERVER); isTCP = true; } if (address || port || isTCP) { debug("bind to", address || "any"); if (!address) { // Match Node's behavior: when no address is provided, prefer the // IPv6 wildcard (which accepts IPv4 connections via dual-stack) and // fall back to IPv4 if the IPv6 bind fails. // // Windows is kept on the IPv4-only path because the dual-stack // socket option doesn't reliably accept IPv4 connections there in // our environment (verified via CI: server listening on `::` rejects // `127.0.0.1` clients with ECONNREFUSED). // // REF: https://github.com/denoland/deno/issues/10762 if (isWindows) { return _createServerHandle(DEFAULT_IPV4_ADDR, port, 4, null, flags); } err = (handle as TCP).bind6(DEFAULT_IPV6_ADDR, port ?? 0, flags ?? 0); if (err) { handle.close(); return _createServerHandle(DEFAULT_IPV4_ADDR, port, 4, null, flags); } } else if (addressType === 6) { err = (handle as TCP).bind6(address, port ?? 0, flags ?? 0); } else if (isTCP) { err = (handle as TCP).bindWithFlags(address, port ?? 0, flags ?? 0); } else { // deno-lint-ignore deno-internal/prefer-primordials -- libuv handle.bind(), not Function.prototype.bind err = handle.bind(address); } } 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"); } } function _onconnection(this: any, err: number, clientHandle?: Handle) { // deno-lint-ignore no-this-alias const handle = this; _runInAsyncContext( handle[kAsyncContext], () => FunctionPrototypeCall(_onconnectionImpl, handle, err, clientHandle), ); } function _onconnectionImpl(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; } if ( self.blockList && clientHandle && typeof clientHandle.getpeername === "function" ) { const out = {}; if (clientHandle.getpeername(out) === 0) { const { address, family } = out as { address: string; family: string }; const type = family === "IPv6" ? "ipv6" : "ipv4"; if (self.blockList.check(address, type)) { clientHandle.close(); return; } } } const socket = self._createSocket(clientHandle); self._connections++; self.emit("connection", socket); if (netServerSocketChannel.hasSubscribers) { netServerSocketChannel.publish({ socket, }); } } // The libuv-style wrap name Node reports for a server handle from // `process.getActiveResourcesInfo()`. function _serverWrapName(handle: Handle) { return ObjectPrototypeIsPrototypeOf(TCP.prototype, handle) ? "TCPServerWrap" : "PipeWrap"; } 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"); registerActiveHandle(this, _serverWrapName(this._handle)); } else { debug("setupListenHandle: create a handle"); let rval = null; // Try to bind to the unspecified IPv6 address, see if IPv6 is available. // A wildcard IPv6 socket also accepts IPv4 traffic via dual-stack, // matching Node's default. Windows is kept on the IPv4-only path because // the dual-stack socket option doesn't reliably accept IPv4 connections // there in our environment. // // REF: https://github.com/denoland/deno/issues/10762 if (!address && typeof fd !== "number") { if (isWindows) { address = DEFAULT_IPV4_ADDR; addressType = 4; } else { 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); // Publish to the `net.server.listen` tracingChannel synchronously // (before the deferred error emit) so subscribers see the result // even if a caller immediately unsubscribes after `Server.listen()` // returns -- mirrors Node, which the test-diagnostics-channel-net // failing-listen subscribe/unsubscribe pattern relies on. if (netServerListenChannel.hasSubscribers) { netServerListenChannel.error.publish({ server: this, error }); } nextTick(_emitErrorNT, this, error); return; } this._handle = rval; registerActiveHandle(this, _serverWrapName(this._handle)); } this[asyncIdSymbol] = _getNewAsyncId(this._handle); this._handle.onconnection = _onconnection; this._handle[kAsyncContext] = core.getAsyncContext(); this._handle[ownerSymbol] = this; // For TCP and Pipe handles, wrap the onconnection callback to create // client handles and call uv_accept before forwarding to // _onconnection(status, clientHandle). if (ObjectPrototypeIsPrototypeOf(TCP.prototype, this._handle)) { setupListenWrap(this._handle); } else if (ObjectPrototypeIsPrototypeOf(Pipe.prototype, this._handle)) { setupPipeListenWrap(this._handle); } // 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; unregisterActiveHandle(this); // Synchronous channel publish (see comment above on the bind/handle // failure path) -- the deferred `_emitErrorNT` runs after callers have // already unsubscribed. if (netServerListenChannel.hasSubscribers) { netServerListenChannel.error.publish({ server: this, error: ex }); } 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(); } // Synchronous asyncEnd publish so subscribers that unsubscribe right // after `Server.listen()` returns still observe the success result. if (netServerListenChannel.hasSubscribers) { netServerListenChannel.asyncEnd.publish({ server: this }); } defaultTriggerAsyncIdScope( this[asyncIdSymbol], nextTick, _emitListeningNT, this, ); } /** * This class is used to create a TCP or IPC server. * * `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()`. */ function Server(connectionListener?: ConnectionListener); function Server( options?: ServerOptions, connectionListener?: ConnectionListener, ); function Server( options?: ServerOptions | ConnectionListener, connectionListener?: ConnectionListener, ) { if (!ObjectPrototypeIsPrototypeOf(Server.prototype, this)) { return new Server(options, connectionListener); } FunctionPrototypeCall(EventEmitter, this); this[asyncIdSymbol] = -1; this.allowHalfOpen = false; this.pauseOnConnect = false; this.noDelay = false; this._handle = null; this._connections = 0; this._usingWorkers = false; this._workers = []; this._unref = false; this._pipeName = undefined; this._connectionKey = undefined; this._listeningId = 1; if (_isConnectionListener(options)) { this.on("connection", options); } else if (_isServerSocketOptions(options)) { this.allowHalfOpen = options?.allowHalfOpen || false; this.pauseOnConnect = !!options?.pauseOnConnect; this.noDelay = Boolean(options?.noDelay); if (options?.blockList) { this.blockList = options.blockList; } if (_isConnectionListener(connectionListener)) { this.on("connection", connectionListener); } } else { throw new ERR_INVALID_ARG_TYPE("options", "Object", options); } } ObjectSetPrototypeOf(Server.prototype, EventEmitter.prototype); ObjectSetPrototypeOf(Server, EventEmitter); /** * 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: */ Server.prototype.listen = function (...args: unknown[]) { const normalized = _normalizeArgs(args); let options = normalized[0] as Partial<ListenOptions>; const cb = normalized[1]; this._listeningId++; if (this._handle) { throw new ERR_SERVER_ALREADY_LISTEN(); } if (cb !== null) { this.once("listening", cb); } // The 'net.server.listen' tracingChannel publishes the user-visible // listen() options. Doing it here (after _normalizeArgs but before any // pre-existing-handle short-circuits return) lets subscribers see the same // options the caller passed in (including ad-hoc fields like // `customOption` used by test-diagnostics-channel-net to verify the // payload is the original options object). if (netServerListenChannel.hasSubscribers) { netServerListenChannel.asyncStart.publish({ server: this, options }); } 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) options = (options as any)._handle || (options as any).handle || options; const flags = _getFlags(options.ipv6Only, options.reusePort); // (handle[, backlog][, cb]) where handle is an object with a handle. // A Pipe wrap exposes an `fd` getter, so it must be matched here before // the `options.fd` branch below -- otherwise the already-opened fd would // be re-opened and rejected (EEXIST). This is the path taken when a // unix-socket server is transferred over IPC (ChildProcess.send). if ( ObjectPrototypeIsPrototypeOf(TCP.prototype, options) || ObjectPrototypeIsPrototypeOf(Pipe.prototype, options) ) { 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" && ObjectHasOwn(options, "port")) || 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; if (options.reusePort === true) { options.exclusive = true; } // 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, flags, ); } 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; // Abstract Unix sockets (path starts with \0) have no filesystem // entry, so readableAll/writableAll (which use chmod) are invalid. if ( (options.readableAll === true || options.writableAll === true) && StringPrototypeCharCodeAt(pipeName, 0) === 0 ) { throw new ERR_INVALID_ARG_VALUE( "options", options, "can not set readableAll or writableAllt to true when path is abstract unix socket", ); } _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 (!(ObjectHasOwn(options, "port") || ObjectHasOwn(options, "path"))) { 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. */ Server.prototype.close = function (cb?: (err?: Error) => void) { this._listeningId++; 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; unregisterActiveHandle(this); } 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; }; Server.prototype[SymbolAsyncDispose] = function () { return new Promise((resolve) => { this.close(() => resolve()); }); }; /** * 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()`. */ Server.prototype.address = function (): 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`. */ Server.prototype.getConnections = function ( cb: (err: Error | null, count: number) => void, ) { // 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. */ Server.prototype.unref = function () { 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. */ Server.prototype.ref = function () { this._unref = false; if (this._handle) { this._handle.ref(); } return this; }; ObjectDefineProperty(Server.prototype, "listening", { __proto__: null, get: function () { return !!this._handle; }, }); Server.prototype._createSocket = function (clientHandle) { const socket = new Socket({ handle: clientHandle, allowHalfOpen: this.allowHalfOpen, pauseOnCreate: this.pauseOnConnect, readable: true, writable: true, }); if (this.noDelay && clientHandle.setNoDelay) { socket[kSetNoDelay] = true; clientHandle.setNoDelay(true); } socket.server = this; socket._server = this; DTRACE_NET_SERVER_CONNECTION(socket); return socket; }; Server.prototype._listen2 = _setupListenHandle; Server.prototype._emitCloseIfDrained = function () { debug("SERVER _emitCloseIfDrained"); if (this._handle || this._connections) { debug( `SERVER handle? ${!!this._handle} connections? ${this._connections}`, ); return; } // We use a deferred timer 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 // deno-lint-ignore no-this-alias const self = this; // Use a system timer to avoid test sanitizer detection. // Must be ref'd so the event loop waits for the close event. core.createSystemTimer( () => { _emitCloseNT(self); }, 0, true, ); }; Server.prototype._setupWorker = function (socketList: EventEmitter) { this._usingWorkers = true; ArrayPrototypePush(this._workers, socketList); socketList.once("exit", (socketList: any) => { const index = ArrayPrototypeIndexOf(this._workers, socketList); ArrayPrototypeSplice(this._workers, index, 1); }); }; Server.prototype[EventEmitter.captureRejectionSymbol] = function ( 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`. */ function createServer( options?: ServerOptions, connectionListener?: ConnectionListener, ): Server { return new Server(options, connectionListener); } return { BlockList, isIP, isIPv4, isIPv6, SocketAddress, _createServerHandle, _normalizeArgs, connect, createConnection, createServer, getDefaultAutoSelectFamily, getDefaultAutoSelectFamilyAttemptTimeout, Server, setDefaultAutoSelectFamily, setDefaultAutoSelectFamilyAttemptTimeout, Socket, Stream, default: { _createServerHandle, _normalizeArgs, BlockList, connect, createConnection, createServer, getDefaultAutoSelectFamily, getDefaultAutoSelectFamilyAttemptTimeout, isIP, isIPv4, isIPv6, Server, setDefaultAutoSelectFamily, setDefaultAutoSelectFamilyAttemptTimeout, Socket, SocketAddress, Stream, }, }; })();