visaljs 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/THIRD_PARTY_LICENSES +1 -1
- package/cli.js +55 -4
- package/node/prelude/_http_agent.js +5 -0
- package/node/prelude/_http_server.js +7 -0
- package/node/prelude/dns.js +7 -1
- package/node/prelude/events.js +7 -3
- package/node/prelude/http.js +685 -279
- package/node/prelude/https.js +14 -3
- package/node/prelude/internal/events_extras.js +2 -5
- package/node/prelude/internal/features.js +17 -0
- package/node/prelude/internal/http_agent.js +88 -2
- package/node/prelude/internal/http_symbols.js +3 -0
- package/node/prelude/internal/net_bound.js +123 -0
- package/node/prelude/internal/net_onread.js +73 -0
- package/node/prelude/internal/net_tos.js +54 -0
- package/node/prelude/internal/perf_observe.js +7 -1
- package/node/prelude/internal/tls/wrap.js +2 -1
- package/node/prelude/module.js +42 -18
- package/node/prelude/net.js +195 -48
- package/node/prelude/perf_hooks.js +4 -0
- package/node/src/host_http.c +151 -21
- package/node/src/host_net.c +75 -9
- package/package.json +1 -1
- package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
- package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
- package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
- package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
- package/runtime/include/js_atoms.h +4 -0
- package/runtime/include/js_web.h +3 -0
- package/runtime/include/jscontext.h +8 -0
- package/runtime/src/atoms.c +37 -3
- package/runtime/src/events.c +19 -0
- package/runtime/src/gc.c +4 -4
- package/runtime/src/internal.h +4 -4
- package/runtime/src/regexp.c +569 -12
- package/runtime/src/shape.c +68 -1
- package/runtime/src/weak.c +95 -24
package/node/prelude/net.js
CHANGED
|
@@ -9,15 +9,18 @@
|
|
|
9
9
|
// Ported from Node.js (Copyright Node.js contributors, MIT): node/third_party/nodejs/LICENSE.
|
|
10
10
|
import {
|
|
11
11
|
errnoMessage, errnoName, netAddress, netBind, netCallback, netChmod, netClose, netConnect, netGuessHandle, netKeepAlive, netListen,
|
|
12
|
-
netNoDelay, netOpen, netOpenFd, netReadStart, netReadStop, netRef, netReset, netShutdown, netWrite, timerUnref,
|
|
12
|
+
netFd, netNoDelay, netOpen, netOpenFd, netReadStart, netReadStop, netRef, netReset, netShutdown, netWrite, timerUnref,
|
|
13
13
|
} from "visaljs:host";
|
|
14
|
-
import { lookup } from "./dns.js";
|
|
14
|
+
import { ADDRCONFIG, lookup } from "./dns.js";
|
|
15
15
|
import { EventEmitter } from "./events.js";
|
|
16
16
|
import { bindContext } from "./internal/async_context.js";
|
|
17
|
-
import { codeError, invalidArgType, invalidArgValue,
|
|
17
|
+
import { codeError, invalidArgType, invalidArgValue, outOfRange, validateFunction, validateString } from "./internal/errors.js";
|
|
18
18
|
import { isIP, isIPv4, isIPv6 } from "./internal/net.js";
|
|
19
19
|
import { BlockList, SocketAddress } from "./internal/socketaddress.js";
|
|
20
|
+
import { AbortError } from "./internal/streams/errors.js";
|
|
21
|
+
import { validateInt32 } from "./internal/streams/validators.js";
|
|
20
22
|
import { Duplex } from "./stream.js";
|
|
23
|
+
import { callableClass, feature, features, lazyClass, lazyFunction } from "./internal/features.js";
|
|
21
24
|
|
|
22
25
|
export { BlockList, isIP, isIPv4, isIPv6, SocketAddress };
|
|
23
26
|
|
|
@@ -81,6 +84,9 @@ function validatePort(port, name = "Port", allowZero = true) {
|
|
|
81
84
|
return +port;
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
/* for internal/net_bound.js (BoundSocket) */
|
|
88
|
+
features.net = { errnoException, exceptionWithHostPort, validatePort };
|
|
89
|
+
|
|
84
90
|
const toNumber = (x) => ((x = Number(x)) >= 0 ? x : false);
|
|
85
91
|
const isPipeName = (s) => typeof s === "string" && toNumber(s) === false;
|
|
86
92
|
|
|
@@ -120,6 +126,7 @@ export function setDefaultAutoSelectFamilyAttemptTimeout(value) {
|
|
|
120
126
|
|
|
121
127
|
/* a descriptor as a handle: Node's createHandle */
|
|
122
128
|
function handleForFd(fd, cb) {
|
|
129
|
+
validateInt32(fd, "fd", 0);
|
|
123
130
|
const type = netGuessHandle(fd);
|
|
124
131
|
if (type !== "PIPE" && type !== "TCP") throw codeError(TypeError, "ERR_INVALID_FD_TYPE", "Unsupported fd type: " + type);
|
|
125
132
|
return netOpen(type === "PIPE", bindContext(cb));
|
|
@@ -129,6 +136,14 @@ function validateBlockList(value, name) {
|
|
|
129
136
|
if (value !== undefined && !BlockList.isBlockList(value)) throw invalidArgType(name, "an net.BlockList", value);
|
|
130
137
|
}
|
|
131
138
|
|
|
139
|
+
/* Node's ERR_INVALID_ADDRESS_FAMILY, with the host and port */
|
|
140
|
+
function addressFamilyError(addressType, host, port) {
|
|
141
|
+
const err = codeError(RangeError, "ERR_INVALID_ADDRESS_FAMILY", "Invalid address family: " + addressType + " " + host + ":" + port);
|
|
142
|
+
err.host = host;
|
|
143
|
+
err.port = port;
|
|
144
|
+
return err;
|
|
145
|
+
}
|
|
146
|
+
|
|
132
147
|
const ipBlocked = (ip) => codeError(Error, "ERR_IP_BLOCKED", "IP(" + ip + ") is blocked by net.BlockList");
|
|
133
148
|
|
|
134
149
|
/* ---- Socket -------------------------------------------------------------------------------- */
|
|
@@ -148,13 +163,30 @@ function writeAfterFIN(chunk, encoding, cb) {
|
|
|
148
163
|
return false;
|
|
149
164
|
}
|
|
150
165
|
|
|
151
|
-
|
|
166
|
+
class SocketClass extends Duplex {
|
|
152
167
|
constructor(options) {
|
|
153
168
|
if (typeof options === "number") options = { fd: options };
|
|
154
169
|
else options = { ...options };
|
|
170
|
+
if (options.objectMode) throw invalidArgValue("options.objectMode", "is not supported", options.objectMode);
|
|
171
|
+
if (options.readableObjectMode || options.writableObjectMode) {
|
|
172
|
+
const name = options.readableObjectMode ? "readableObjectMode" : "writableObjectMode";
|
|
173
|
+
throw invalidArgValue("options." + name, "is not supported", options[name]);
|
|
174
|
+
}
|
|
155
175
|
/* Node's: not half-open by default, 'close' emitted by the socket itself, strings written as given */
|
|
156
176
|
const allowHalfOpen = Boolean(options.allowHalfOpen);
|
|
157
|
-
super({ ...options, allowHalfOpen, emitClose: false, autoDestroy: true, decodeStrings: false });
|
|
177
|
+
super({ ...options, signal: undefined, allowHalfOpen, emitClose: false, autoDestroy: true, decodeStrings: false });
|
|
178
|
+
/* the signal option, as Node's socket has it: aborted, the socket is destroyed with an AbortError
|
|
179
|
+
* (one already aborted: on the next tick, once connect() has begun) */
|
|
180
|
+
if (options.signal !== undefined) {
|
|
181
|
+
const signal = options.signal;
|
|
182
|
+
if (signal === null || typeof signal !== "object" || !("aborted" in signal)) throw invalidArgType("options.signal", "an instance of AbortSignal", signal);
|
|
183
|
+
const onAbort = () => this.destroy(new AbortError(undefined, { cause: signal.reason }));
|
|
184
|
+
if (signal.aborted) process.nextTick(onAbort);
|
|
185
|
+
else {
|
|
186
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
187
|
+
this.once("close", () => signal.removeEventListener("abort", onAbort));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
158
190
|
this.connecting = false;
|
|
159
191
|
this._hadError = false;
|
|
160
192
|
this._handle = null; /* the host handle's id while open */
|
|
@@ -181,6 +213,12 @@ export class Socket extends Duplex {
|
|
|
181
213
|
this._timeoutMs = 0;
|
|
182
214
|
this._lastActive = 0;
|
|
183
215
|
this._onread = null;
|
|
216
|
+
if (options.typeOfService !== undefined) feature("validateTypeOfService")(options.typeOfService);
|
|
217
|
+
this._tos = options.typeOfService; /* internal/net_tos.js's: the type of service, set at connect when given before it */
|
|
218
|
+
this._boundSource = false; /* the handle is an adopted BoundSocket's */
|
|
219
|
+
this._boundPath = undefined; /* an adopted pipe BoundSocket's path, its local address */
|
|
220
|
+
this._perfConnect = null; /* a TCP connect's start, while a PerformanceObserver takes 'net' entries */ /* set at connect when given before it */
|
|
221
|
+
this._onreadRest = null; /* internal/net_onread.js's: the bytes read but not yet given when reading stopped */
|
|
184
222
|
this.timeout = undefined;
|
|
185
223
|
this.autoSelectFamilyAttemptedAddresses = undefined;
|
|
186
224
|
validateBlockList(options.blockList, "options.blockList");
|
|
@@ -188,10 +226,28 @@ export class Socket extends Duplex {
|
|
|
188
226
|
const onread = options.onread;
|
|
189
227
|
if (onread !== null && typeof onread === "object" && (typeof onread.buffer === "function" || onread.buffer instanceof Uint8Array) &&
|
|
190
228
|
typeof onread.callback === "function") {
|
|
229
|
+
feature("onread"); /* internal/net_onread.js's */
|
|
191
230
|
this._onread = onread;
|
|
192
231
|
}
|
|
193
232
|
this.on("end", onReadableStreamEnd);
|
|
194
|
-
|
|
233
|
+
/* an adopted BoundSocket's handle: bound, not yet connected, so not read until it is */
|
|
234
|
+
const bound = options.handle !== undefined ? features.adoptBoundSocket?.(options.handle) : null;
|
|
235
|
+
if (bound) {
|
|
236
|
+
const { id, path } = bound;
|
|
237
|
+
this._handle = id;
|
|
238
|
+
this._boundSource = true;
|
|
239
|
+
this._isPipe = path !== undefined;
|
|
240
|
+
netCallback(id, bindContext((event, value) => this._onEvent(event, value, id)));
|
|
241
|
+
if (path !== undefined) {
|
|
242
|
+
this._boundPath = path;
|
|
243
|
+
/* a pipe's bind leaves its stream unopened: opened on its own descriptor, as Node's */
|
|
244
|
+
const fd = netFd(id);
|
|
245
|
+
if (fd >= 0) {
|
|
246
|
+
const err = netOpenFd(id, fd);
|
|
247
|
+
if (err) throw errnoException(err, "open");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} else if (options.handle !== undefined) {
|
|
195
251
|
const id = options.handle;
|
|
196
252
|
this._isPipe = !!options._isPipe;
|
|
197
253
|
this._attach(id);
|
|
@@ -209,7 +265,7 @@ export class Socket extends Duplex {
|
|
|
209
265
|
}
|
|
210
266
|
this._attach(id);
|
|
211
267
|
}
|
|
212
|
-
if (this._handle !== null && options.readable !== false) {
|
|
268
|
+
if (this._handle !== null && !bound && options.readable !== false) {
|
|
213
269
|
if (options.pauseOnCreate) this._readableState.flowing = false;
|
|
214
270
|
else if (!options.manualStart) this.read(0);
|
|
215
271
|
}
|
|
@@ -257,7 +313,7 @@ export class Socket extends Duplex {
|
|
|
257
313
|
this._bytesRead += value.byteLength;
|
|
258
314
|
this._unrefTimer();
|
|
259
315
|
let more;
|
|
260
|
-
if (this._onread) more =
|
|
316
|
+
if (this._onread) more = features.onread.deliver(this, value);
|
|
261
317
|
else more = this.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength));
|
|
262
318
|
if (!more) {
|
|
263
319
|
this._reading = false;
|
|
@@ -273,22 +329,31 @@ export class Socket extends Duplex {
|
|
|
273
329
|
this.destroy(errnoException(value, "read"));
|
|
274
330
|
return;
|
|
275
331
|
}
|
|
332
|
+
if (this._onread) features.onread.eof(this);
|
|
276
333
|
this.push(null);
|
|
277
334
|
this.read(0);
|
|
278
335
|
}
|
|
279
336
|
|
|
280
|
-
/* onread
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
337
|
+
/* with onread, pausing stops reading from the handle and resuming starts it, as Node's */
|
|
338
|
+
pause() {
|
|
339
|
+
if (this._onread) features.onread.pause(this);
|
|
340
|
+
return super.pause();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
resume() {
|
|
344
|
+
if (this._onread) features.onread.resume(this);
|
|
345
|
+
return super.resume();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* reading from the handle again (with onread, the bytes left from the last read first) */
|
|
349
|
+
_startReading() {
|
|
350
|
+
this._reading = true;
|
|
351
|
+
if (this._onreadRest) {
|
|
352
|
+
features.onread.rest(this);
|
|
353
|
+
return;
|
|
290
354
|
}
|
|
291
|
-
|
|
355
|
+
const err = netReadStart(this._handle);
|
|
356
|
+
if (err) this.destroy(errnoException(err, "read"));
|
|
292
357
|
}
|
|
293
358
|
|
|
294
359
|
get pending() {
|
|
@@ -304,25 +369,36 @@ export class Socket extends Duplex {
|
|
|
304
369
|
}
|
|
305
370
|
|
|
306
371
|
get bufferSize() {
|
|
307
|
-
return this._handle !== null ? this.writableLength : undefined;
|
|
372
|
+
return this._handle !== null || this.connecting ? this.writableLength : undefined;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
get _connecting() {
|
|
376
|
+
return this.connecting;
|
|
308
377
|
}
|
|
309
378
|
|
|
310
379
|
get bytesRead() {
|
|
311
380
|
return this._bytesRead;
|
|
312
381
|
}
|
|
313
382
|
|
|
383
|
+
/* Node's: what was sent, and what's still buffered (undefined on the prototype) */
|
|
314
384
|
get bytesWritten() {
|
|
385
|
+
const writableBuffer = this.writableBuffer;
|
|
386
|
+
if (!writableBuffer) return undefined;
|
|
315
387
|
let bytes = this._bytesDispatched;
|
|
316
|
-
const
|
|
317
|
-
for (const el of state.buffered) bytes += typeof el.chunk === "string" ? Buffer.byteLength(el.chunk, el.encoding) : el.chunk.length;
|
|
388
|
+
for (const el of writableBuffer) bytes += typeof el.chunk === "string" ? Buffer.byteLength(el.chunk, el.encoding) : el.chunk.length;
|
|
318
389
|
const data = this._pendingData;
|
|
319
|
-
if (
|
|
390
|
+
if (Array.isArray(data)) {
|
|
391
|
+
for (const el of data) bytes += typeof el.chunk === "string" ? Buffer.byteLength(el.chunk, el.encoding) : el.chunk.length;
|
|
392
|
+
} else if (data) {
|
|
393
|
+
bytes += typeof data === "string" ? Buffer.byteLength(data, this._pendingEncoding) : data.length;
|
|
394
|
+
}
|
|
320
395
|
return bytes;
|
|
321
396
|
}
|
|
322
397
|
|
|
323
|
-
/* a pipe's ends have no address object: Node's Pipe has no getpeername/getsockname
|
|
398
|
+
/* a pipe's ends have no address object: Node's Pipe has no getpeername/getsockname;
|
|
399
|
+
* once closed, the address read while connected */
|
|
324
400
|
_getpeername() {
|
|
325
|
-
if (this._handle === null || this._isPipe) return {};
|
|
401
|
+
if (this._handle === null || this._isPipe || this.connecting) return this._peername || {};
|
|
326
402
|
if (!this._peername) {
|
|
327
403
|
const a = netAddress(this._handle, true);
|
|
328
404
|
if (typeof a === "number") return {};
|
|
@@ -332,7 +408,8 @@ export class Socket extends Duplex {
|
|
|
332
408
|
}
|
|
333
409
|
|
|
334
410
|
_getsockname() {
|
|
335
|
-
if (this.
|
|
411
|
+
if (this._boundPath !== undefined) return { address: this._boundPath };
|
|
412
|
+
if (this._handle === null || this._isPipe) return this._sockname || {};
|
|
336
413
|
if (!this._sockname) {
|
|
337
414
|
const a = netAddress(this._handle, false);
|
|
338
415
|
if (typeof a === "number") return {};
|
|
@@ -448,11 +525,7 @@ export class Socket extends Duplex {
|
|
|
448
525
|
|
|
449
526
|
_read(n) {
|
|
450
527
|
if (this.connecting || this._handle === null) this.once("connect", () => this._read(n));
|
|
451
|
-
else if (!this._reading)
|
|
452
|
-
this._reading = true;
|
|
453
|
-
const err = netReadStart(this._handle);
|
|
454
|
-
if (err) this.destroy(errnoException(err, "read"));
|
|
455
|
-
}
|
|
528
|
+
else if (!this._reading) this._startReading();
|
|
456
529
|
}
|
|
457
530
|
|
|
458
531
|
/* Node's _writeGeneric: a write before the connection waits for it; a write the kernel takes at
|
|
@@ -498,7 +571,7 @@ export class Socket extends Duplex {
|
|
|
498
571
|
|
|
499
572
|
/* end(): shut the write side down once the writes are out */
|
|
500
573
|
_final(cb) {
|
|
501
|
-
if (this.
|
|
574
|
+
if (this.connecting) {
|
|
502
575
|
this.once("connect", () => this._final(cb));
|
|
503
576
|
return;
|
|
504
577
|
}
|
|
@@ -551,7 +624,7 @@ export class Socket extends Duplex {
|
|
|
551
624
|
|
|
552
625
|
/* close with a TCP RST instead of a FIN */
|
|
553
626
|
resetAndDestroy() {
|
|
554
|
-
if (this._handle !== null) {
|
|
627
|
+
if (this._handle !== null || this.connecting) {
|
|
555
628
|
if (this._isPipe) throw codeError(TypeError, "ERR_INVALID_HANDLE_TYPE", "This handle type cannot be sent");
|
|
556
629
|
if (this.connecting) this.once("connect", () => this._reset());
|
|
557
630
|
else this._reset();
|
|
@@ -574,12 +647,27 @@ export class Socket extends Duplex {
|
|
|
574
647
|
connect(...args) {
|
|
575
648
|
const [options, cb] = args[0] && args[0][normalizedArgs] ? args[0] : normalizeArgs(args);
|
|
576
649
|
if (this.write !== Socket.prototype.write) this.write = Socket.prototype.write;
|
|
577
|
-
if (
|
|
650
|
+
if (options.port === undefined && options.path == null) {
|
|
651
|
+
throw codeError(TypeError, "ERR_MISSING_ARGS", 'The "options" or "port" or "path" argument must be specified');
|
|
652
|
+
}
|
|
653
|
+
/* a closed socket connects again, as Node's: its streams undestroyed, its counts the new handle's */
|
|
654
|
+
if (this.destroyed) {
|
|
655
|
+
this._handle = null;
|
|
656
|
+
this._peername = null;
|
|
657
|
+
this._sockname = null;
|
|
658
|
+
this._bytesRead = 0;
|
|
659
|
+
this._bytesDispatched = 0;
|
|
660
|
+
this._hadError = false;
|
|
661
|
+
this._perfConnect = null;
|
|
662
|
+
this._undestroy();
|
|
663
|
+
}
|
|
578
664
|
const { path } = options;
|
|
579
665
|
if (cb !== null) this.once("connect", cb);
|
|
580
666
|
this._unrefTimer();
|
|
581
667
|
this.connecting = true;
|
|
582
|
-
|
|
668
|
+
/* an adopted handle is a pipe or not whatever the options say; once it's gone, the path does */
|
|
669
|
+
const pipe = this._boundSource && this._handle !== null ? this._isPipe : !!path;
|
|
670
|
+
if (pipe) {
|
|
583
671
|
validateString(path, "options.path");
|
|
584
672
|
this._isPipe = true;
|
|
585
673
|
this._internalConnect(path);
|
|
@@ -594,23 +682,37 @@ export class Socket extends Duplex {
|
|
|
594
682
|
const host = options.host || "localhost";
|
|
595
683
|
let { port, autoSelectFamilyAttemptTimeout: attemptTimeout, autoSelectFamily: selectFamily } = options;
|
|
596
684
|
if (localAddress && !isIP(localAddress)) throw codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + localAddress);
|
|
597
|
-
if (localPort
|
|
685
|
+
if (localPort && typeof localPort !== "number") throw invalidArgType("options.localPort", "of type number", localPort);
|
|
598
686
|
if (typeof host !== "string") throw invalidArgType("options.host", "of type string", host);
|
|
687
|
+
if (host.includes("\0")) throw invalidArgValue("options.host", "must be a string without null bytes", host);
|
|
688
|
+
/* an adopted BoundSocket owns the local end and the address family */
|
|
689
|
+
if (this._boundSource) {
|
|
690
|
+
if (localAddress !== undefined || localPort !== undefined) {
|
|
691
|
+
throw invalidArgValue("options", "localAddress and localPort cannot be used with an adopted bound socket", options);
|
|
692
|
+
}
|
|
693
|
+
selectFamily = false;
|
|
694
|
+
}
|
|
599
695
|
if (typeof port !== "undefined") {
|
|
600
696
|
if (typeof port !== "number" && typeof port !== "string") throw invalidArgType("options.port", "of type number or string", port);
|
|
601
697
|
port = validatePort(port);
|
|
602
698
|
}
|
|
603
699
|
port |= 0;
|
|
604
|
-
if (selectFamily
|
|
700
|
+
if (selectFamily != null && typeof selectFamily !== "boolean") throw invalidArgType("options.autoSelectFamily", "of type boolean", selectFamily);
|
|
605
701
|
selectFamily ??= autoSelectFamily;
|
|
606
|
-
if (attemptTimeout
|
|
607
|
-
|
|
702
|
+
if (attemptTimeout != null) {
|
|
703
|
+
validateInt32(attemptTimeout, "options.autoSelectFamilyAttemptTimeout", 1);
|
|
608
704
|
if (attemptTimeout < 10) attemptTimeout = 10;
|
|
609
705
|
} else {
|
|
610
706
|
attemptTimeout = autoSelectFamilyAttemptTimeout;
|
|
611
707
|
}
|
|
612
708
|
this._host = host;
|
|
613
709
|
const addressType = isIP(host);
|
|
710
|
+
/* an adopted BoundSocket connects in this tick, so its local address is known at once (a
|
|
711
|
+
* failure still comes as an 'error' event) */
|
|
712
|
+
if (addressType && this._boundSource) {
|
|
713
|
+
this._internalConnect(host, port, addressType, localAddress, localPort);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
614
716
|
if (addressType) {
|
|
615
717
|
process.nextTick(() => {
|
|
616
718
|
if (this.connecting) this._internalConnect(host, port, addressType, localAddress, localPort);
|
|
@@ -620,17 +722,19 @@ export class Socket extends Duplex {
|
|
|
620
722
|
let family = options.family ?? 0;
|
|
621
723
|
if (family === "IPv4") family = 4;
|
|
622
724
|
else if (family === "IPv6") family = 6;
|
|
623
|
-
const dnsopts = { family, all: selectFamily && family === 0 };
|
|
624
725
|
const lookupFn = options.lookup ?? lookup;
|
|
625
726
|
if (typeof lookupFn !== "function") throw invalidArgType("options.lookup", "of type function", lookupFn);
|
|
727
|
+
const dnsopts = { family, hints: options.hints || 0, all: false };
|
|
728
|
+
if (process.platform !== "win32" && family !== 4 && family !== 6 && dnsopts.hints === 0) dnsopts.hints = ADDRCONFIG;
|
|
729
|
+
dnsopts.all = family !== 4 && family !== 6 && !localAddress && selectFamily;
|
|
626
730
|
if (!dnsopts.all) {
|
|
627
731
|
lookupFn(host, dnsopts, (err, ip, addressType) => {
|
|
628
732
|
this.emit("lookup", err, ip, addressType, host);
|
|
629
733
|
if (!this.connecting) return;
|
|
630
734
|
if (err) process.nextTick(() => this.destroy(err));
|
|
631
|
-
else if (!isIP(ip)) process.nextTick(() => this.destroy(codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + ip)));
|
|
735
|
+
else if (typeof ip !== "string" || !isIP(ip)) process.nextTick(() => this.destroy(codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + ip)));
|
|
632
736
|
else if (addressType !== 4 && addressType !== 6) {
|
|
633
|
-
process.nextTick(() => this.destroy(
|
|
737
|
+
process.nextTick(() => this.destroy(addressFamilyError(addressType, host, options.port)));
|
|
634
738
|
} else {
|
|
635
739
|
this._unrefTimer();
|
|
636
740
|
this._internalConnect(ip, port, addressType, localAddress, localPort);
|
|
@@ -661,7 +765,7 @@ export class Socket extends Duplex {
|
|
|
661
765
|
const first = addresses[0];
|
|
662
766
|
const error = !first || !isIP(first.address)
|
|
663
767
|
? codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + first?.address)
|
|
664
|
-
:
|
|
768
|
+
: addressFamilyError(first.family, host, options.port);
|
|
665
769
|
process.nextTick(() => this.destroy(error));
|
|
666
770
|
return;
|
|
667
771
|
}
|
|
@@ -687,14 +791,23 @@ export class Socket extends Duplex {
|
|
|
687
791
|
/* a new handle bound to the local address, if any, and connecting: 0 or the failure as Node reports it */
|
|
688
792
|
_open(address, port, addressType, localAddress, localPort, onConnect) {
|
|
689
793
|
let id;
|
|
690
|
-
|
|
794
|
+
const cb = bindContext((event, value) => {
|
|
691
795
|
if (event === NET_CONNECT) {
|
|
692
796
|
if (this._handle === id) onConnect(value, id);
|
|
693
797
|
} else {
|
|
694
798
|
this._onEvent(event, value, id);
|
|
695
799
|
}
|
|
696
|
-
})
|
|
697
|
-
this._handle
|
|
800
|
+
});
|
|
801
|
+
if (this._boundSource && this._handle !== null) { /* an adopted BoundSocket's, already bound */
|
|
802
|
+
id = this._handle;
|
|
803
|
+
netCallback(id, cb);
|
|
804
|
+
} else {
|
|
805
|
+
id = netOpen(this._isPipe, cb);
|
|
806
|
+
this._handle = id;
|
|
807
|
+
}
|
|
808
|
+
if (!this._isPipe && this._perfConnect === null && features.perfObserving?.("net")) {
|
|
809
|
+
this._perfConnect = { start: features.perfNow(), host: address, port };
|
|
810
|
+
}
|
|
698
811
|
if (!this._isPipe && (localAddress || localPort)) {
|
|
699
812
|
localAddress ||= addressType === 4 ? "0.0.0.0" : "::";
|
|
700
813
|
const err = netBind(id, localAddress, localPort | 0, 0);
|
|
@@ -780,17 +893,27 @@ export class Socket extends Duplex {
|
|
|
780
893
|
}
|
|
781
894
|
this._attach(this._handle);
|
|
782
895
|
this._unrefTimer();
|
|
896
|
+
if (this._perfConnect !== null) {
|
|
897
|
+
const { start, host, port } = this._perfConnect;
|
|
898
|
+
this._perfConnect = null;
|
|
899
|
+
features.perfObserve(features.perfEntry("connect", "net", start, features.perfNow() - start, { host, port }));
|
|
900
|
+
}
|
|
901
|
+
if (this._tos !== undefined && !this._isPipe) features.applyTypeOfService(this);
|
|
783
902
|
this.emit("connect");
|
|
784
903
|
this.emit("ready");
|
|
785
904
|
if (this.readable && !this.isPaused()) this.read(0);
|
|
786
905
|
}
|
|
787
906
|
}
|
|
907
|
+
/* internal/net_tos.js's, linked when the program names them */
|
|
908
|
+
SocketClass.prototype.setTypeOfService = lazyFunction("setTypeOfService", 1);
|
|
909
|
+
SocketClass.prototype.getTypeOfService = lazyFunction("getTypeOfService");
|
|
910
|
+
export const Socket = callableClass(SocketClass, "Socket"); /* Socket(…) without new, as Node's */
|
|
788
911
|
|
|
789
912
|
export const Stream = Socket;
|
|
790
913
|
|
|
791
914
|
/* ---- Server -------------------------------------------------------------------------------- */
|
|
792
915
|
|
|
793
|
-
|
|
916
|
+
class ServerClass extends EventEmitter {
|
|
794
917
|
constructor(options, connectionListener) {
|
|
795
918
|
super();
|
|
796
919
|
if (typeof options === "function") {
|
|
@@ -803,6 +926,7 @@ export class Server extends EventEmitter {
|
|
|
803
926
|
}
|
|
804
927
|
if (typeof connectionListener === "function") this.on("connection", connectionListener);
|
|
805
928
|
this._connections = 0;
|
|
929
|
+
this._listeningId = 1; /* a listen or close since a lookup began makes the lookup's result stale */
|
|
806
930
|
this._handle = null;
|
|
807
931
|
this._isPipe = false;
|
|
808
932
|
this._pipeName = undefined;
|
|
@@ -827,7 +951,16 @@ export class Server extends EventEmitter {
|
|
|
827
951
|
if (typeof options.callback === "function") cb = options.callback;
|
|
828
952
|
if (this._handle !== null) throw codeError(Error, "ERR_SERVER_ALREADY_LISTEN", "Listen method has been called more than once without closing.");
|
|
829
953
|
if (cb !== null) this.once("listening", cb);
|
|
954
|
+
this._listeningId++;
|
|
830
955
|
const backlogFromArgs = toNumber(args.length > 1 && args[1]) || toNumber(args.length > 2 && args[2]);
|
|
956
|
+
/* listen(boundSocket) or listen({ handle: boundSocket }): the server takes its handle */
|
|
957
|
+
const bound = features.adoptBoundSocket?.(options) ?? features.adoptBoundSocket?.(options.handle);
|
|
958
|
+
if (bound) {
|
|
959
|
+
if (bound.path !== undefined) this._pipeName = bound.path;
|
|
960
|
+
this._isPipe = bound.path !== undefined;
|
|
961
|
+
this._setupListen(null, -1, -1, backlogFromArgs, undefined, 0, bound.id);
|
|
962
|
+
return this;
|
|
963
|
+
}
|
|
831
964
|
if (options._handle !== undefined && options._handle !== null) options = options._handle;
|
|
832
965
|
else if (options.handle !== undefined && options.handle !== null) options = options.handle;
|
|
833
966
|
const flags = (options.ipv6Only ? 1 : 0) | (options.reusePort ? 2 : 0);
|
|
@@ -880,7 +1013,9 @@ export class Server extends EventEmitter {
|
|
|
880
1013
|
}
|
|
881
1014
|
|
|
882
1015
|
_lookupAndListen(port, address, backlog, flags) {
|
|
1016
|
+
const listeningId = this._listeningId;
|
|
883
1017
|
lookup(address, (err, ip, addressType) => {
|
|
1018
|
+
if (listeningId !== this._listeningId) return;
|
|
884
1019
|
if (err) this.emit("error", err);
|
|
885
1020
|
else this._setupListen(ip, port, ip ? addressType : 4, backlog, undefined, flags);
|
|
886
1021
|
});
|
|
@@ -970,6 +1105,7 @@ export class Server extends EventEmitter {
|
|
|
970
1105
|
process.nextTick(() => this.emit("error", ex));
|
|
971
1106
|
return;
|
|
972
1107
|
}
|
|
1108
|
+
this._connectionKey = addressType + ":" + address + ":" + port;
|
|
973
1109
|
if (this._unref) netRef(id, false);
|
|
974
1110
|
process.nextTick(() => {
|
|
975
1111
|
if (this._handle !== null) this.emit("listening");
|
|
@@ -1024,6 +1160,7 @@ export class Server extends EventEmitter {
|
|
|
1024
1160
|
|
|
1025
1161
|
/* close([cb]): stop accepting; 'close' once the open connections end too */
|
|
1026
1162
|
close(cb) {
|
|
1163
|
+
this._listeningId++;
|
|
1027
1164
|
if (typeof cb === "function") {
|
|
1028
1165
|
if (this._handle === null) this.once("close", () => cb(codeError(Error, "ERR_SERVER_NOT_RUNNING", "Server is not running.")));
|
|
1029
1166
|
else this.once("close", cb);
|
|
@@ -1036,6 +1173,12 @@ export class Server extends EventEmitter {
|
|
|
1036
1173
|
return this;
|
|
1037
1174
|
}
|
|
1038
1175
|
|
|
1176
|
+
/* captureRejections: a 'connection' listener's rejection destroys the socket, as Node's */
|
|
1177
|
+
[Symbol.for("nodejs.rejection")](err, event, sock) {
|
|
1178
|
+
if (event === "connection") sock.destroy(err);
|
|
1179
|
+
else this.emit("error", err);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1039
1182
|
_emitCloseIfDrained() {
|
|
1040
1183
|
if (this._handle !== null || this._connections) return;
|
|
1041
1184
|
process.nextTick(() => this.emit("close"));
|
|
@@ -1063,6 +1206,7 @@ export class Server extends EventEmitter {
|
|
|
1063
1206
|
return new Promise((resolve, reject) => this.close((err) => (err ? reject(err) : resolve())));
|
|
1064
1207
|
}
|
|
1065
1208
|
}
|
|
1209
|
+
export const Server = callableClass(ServerClass, "Server"); /* Server(…) without new, as Node's */
|
|
1066
1210
|
|
|
1067
1211
|
export function createServer(options, connectionListener) {
|
|
1068
1212
|
return new Server(options, connectionListener);
|
|
@@ -1079,7 +1223,10 @@ export function connect(...args) {
|
|
|
1079
1223
|
|
|
1080
1224
|
export const createConnection = connect;
|
|
1081
1225
|
|
|
1226
|
+
/* internal/net_bound.js's, linked when the program names it */
|
|
1227
|
+
export const BoundSocket = lazyClass("BoundSocket");
|
|
1228
|
+
|
|
1082
1229
|
export default {
|
|
1083
|
-
BlockList, SocketAddress, isIP, isIPv4, isIPv6, Socket, Stream, Server, connect, createConnection, createServer,
|
|
1230
|
+
BlockList, BoundSocket, SocketAddress, isIP, isIPv4, isIPv6, Socket, Stream, Server, connect, createConnection, createServer,
|
|
1084
1231
|
getDefaultAutoSelectFamily, setDefaultAutoSelectFamily, getDefaultAutoSelectFamilyAttemptTimeout, setDefaultAutoSelectFamilyAttemptTimeout,
|
|
1085
1232
|
};
|
|
@@ -15,6 +15,10 @@ const now = () => hrMs() - origin;
|
|
|
15
15
|
|
|
16
16
|
const kConstruct = Symbol("construct");
|
|
17
17
|
|
|
18
|
+
/* for internal/perf_observe.js's users: the clock, and an entry of another module's (net's 'connect') */
|
|
19
|
+
features.perfNow = now;
|
|
20
|
+
features.perfEntry = (name, entryType, startTime, duration, detail) => new PerformanceEntry(kConstruct, name, entryType, startTime, duration, detail);
|
|
21
|
+
|
|
18
22
|
export class PerformanceEntry {
|
|
19
23
|
constructor(key, name, entryType, startTime, duration, detail) {
|
|
20
24
|
if (key !== kConstruct) throw codeError(TypeError, "ERR_ILLEGAL_CONSTRUCTOR", "Illegal constructor");
|