visaljs 0.0.8 → 0.0.10
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 +2782 -770
- package/node/include/js_node.h +1 -0
- 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 +693 -285
- 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_chars.ts +22 -0
- 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 +212 -61
- 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/node/src/host_sys.c +9 -0
- 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_buffer.h +185 -0
- package/runtime/include/js_builtins.h +53 -0
- package/runtime/include/js_exception.h +3 -0
- package/runtime/include/js_function.h +1 -1
- package/runtime/include/js_object.h +21 -3
- package/runtime/include/js_ops.h +27 -0
- package/runtime/include/js_runtime.h +1 -0
- package/runtime/include/js_web.h +3 -3
- package/runtime/include/jscontext.h +15 -0
- package/runtime/src/atoms.c +37 -3
- package/runtime/src/builtins.c +17 -4
- package/runtime/src/events.c +19 -0
- package/runtime/src/function.c +37 -10
- package/runtime/src/gc.c +7 -5
- package/runtime/src/internal.h +4 -4
- package/runtime/src/iterator.c +5 -1
- package/runtime/src/object.c +42 -6
- package/runtime/src/regexp.c +569 -12
- package/runtime/src/shape.c +68 -1
- package/runtime/src/typedarray.c +23 -0
- 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
|
+
loopNow, 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
|
|
|
@@ -27,12 +30,6 @@ const UV_EOF = -4095;
|
|
|
27
30
|
const UV_EINVAL = -22;
|
|
28
31
|
const TIMEOUT_MAX = 2 ** 31 - 1;
|
|
29
32
|
|
|
30
|
-
/* a monotonic clock in ms */
|
|
31
|
-
const now = () => {
|
|
32
|
-
const [seconds, nanoseconds] = process.hrtime();
|
|
33
|
-
return seconds * 1e3 + nanoseconds / 1e6;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
33
|
/* ---- Node's error shapes ------------------------------------------------------------------- */
|
|
37
34
|
|
|
38
35
|
/** `read ECONNRESET` */
|
|
@@ -81,6 +78,9 @@ function validatePort(port, name = "Port", allowZero = true) {
|
|
|
81
78
|
return +port;
|
|
82
79
|
}
|
|
83
80
|
|
|
81
|
+
/* for internal/net_bound.js (BoundSocket) */
|
|
82
|
+
features.net = { errnoException, exceptionWithHostPort, validatePort };
|
|
83
|
+
|
|
84
84
|
const toNumber = (x) => ((x = Number(x)) >= 0 ? x : false);
|
|
85
85
|
const isPipeName = (s) => typeof s === "string" && toNumber(s) === false;
|
|
86
86
|
|
|
@@ -120,6 +120,7 @@ export function setDefaultAutoSelectFamilyAttemptTimeout(value) {
|
|
|
120
120
|
|
|
121
121
|
/* a descriptor as a handle: Node's createHandle */
|
|
122
122
|
function handleForFd(fd, cb) {
|
|
123
|
+
validateInt32(fd, "fd", 0);
|
|
123
124
|
const type = netGuessHandle(fd);
|
|
124
125
|
if (type !== "PIPE" && type !== "TCP") throw codeError(TypeError, "ERR_INVALID_FD_TYPE", "Unsupported fd type: " + type);
|
|
125
126
|
return netOpen(type === "PIPE", bindContext(cb));
|
|
@@ -129,6 +130,14 @@ function validateBlockList(value, name) {
|
|
|
129
130
|
if (value !== undefined && !BlockList.isBlockList(value)) throw invalidArgType(name, "an net.BlockList", value);
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
/* Node's ERR_INVALID_ADDRESS_FAMILY, with the host and port */
|
|
134
|
+
function addressFamilyError(addressType, host, port) {
|
|
135
|
+
const err = codeError(RangeError, "ERR_INVALID_ADDRESS_FAMILY", "Invalid address family: " + addressType + " " + host + ":" + port);
|
|
136
|
+
err.host = host;
|
|
137
|
+
err.port = port;
|
|
138
|
+
return err;
|
|
139
|
+
}
|
|
140
|
+
|
|
132
141
|
const ipBlocked = (ip) => codeError(Error, "ERR_IP_BLOCKED", "IP(" + ip + ") is blocked by net.BlockList");
|
|
133
142
|
|
|
134
143
|
/* ---- Socket -------------------------------------------------------------------------------- */
|
|
@@ -148,13 +157,30 @@ function writeAfterFIN(chunk, encoding, cb) {
|
|
|
148
157
|
return false;
|
|
149
158
|
}
|
|
150
159
|
|
|
151
|
-
|
|
160
|
+
class SocketClass extends Duplex {
|
|
152
161
|
constructor(options) {
|
|
153
162
|
if (typeof options === "number") options = { fd: options };
|
|
154
163
|
else options = { ...options };
|
|
164
|
+
if (options.objectMode) throw invalidArgValue("options.objectMode", "is not supported", options.objectMode);
|
|
165
|
+
if (options.readableObjectMode || options.writableObjectMode) {
|
|
166
|
+
const name = options.readableObjectMode ? "readableObjectMode" : "writableObjectMode";
|
|
167
|
+
throw invalidArgValue("options." + name, "is not supported", options[name]);
|
|
168
|
+
}
|
|
155
169
|
/* Node's: not half-open by default, 'close' emitted by the socket itself, strings written as given */
|
|
156
170
|
const allowHalfOpen = Boolean(options.allowHalfOpen);
|
|
157
|
-
super({ ...options, allowHalfOpen, emitClose: false, autoDestroy: true, decodeStrings: false });
|
|
171
|
+
super({ ...options, signal: undefined, allowHalfOpen, emitClose: false, autoDestroy: true, decodeStrings: false });
|
|
172
|
+
/* the signal option, as Node's socket has it: aborted, the socket is destroyed with an AbortError
|
|
173
|
+
* (one already aborted: on the next tick, once connect() has begun) */
|
|
174
|
+
if (options.signal !== undefined) {
|
|
175
|
+
const signal = options.signal;
|
|
176
|
+
if (signal === null || typeof signal !== "object" || !("aborted" in signal)) throw invalidArgType("options.signal", "an instance of AbortSignal", signal);
|
|
177
|
+
const onAbort = () => this.destroy(new AbortError(undefined, { cause: signal.reason }));
|
|
178
|
+
if (signal.aborted) process.nextTick(onAbort);
|
|
179
|
+
else {
|
|
180
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
181
|
+
this.once("close", () => signal.removeEventListener("abort", onAbort));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
158
184
|
this.connecting = false;
|
|
159
185
|
this._hadError = false;
|
|
160
186
|
this._handle = null; /* the host handle's id while open */
|
|
@@ -178,9 +204,16 @@ export class Socket extends Duplex {
|
|
|
178
204
|
this._keepAlive = options.keepAlive === true ? [true, ~~((options.keepAliveInitialDelay ?? 0) / 1000)] : null;
|
|
179
205
|
this._unref = false;
|
|
180
206
|
this._timer = null; /* the timeout: an unref'd timer, re-armed for the idle time left when it fires */
|
|
181
|
-
this.
|
|
207
|
+
this._timerDue = 0; /* when _timer fires (loopNow's clock) */
|
|
208
|
+
this._timeoutMs = 0; /* 0: off; a pending _timer then finds nothing to do */
|
|
182
209
|
this._lastActive = 0;
|
|
183
210
|
this._onread = null;
|
|
211
|
+
if (options.typeOfService !== undefined) feature("validateTypeOfService")(options.typeOfService);
|
|
212
|
+
this._tos = options.typeOfService; /* internal/net_tos.js's: the type of service, set at connect when given before it */
|
|
213
|
+
this._boundSource = false; /* the handle is an adopted BoundSocket's */
|
|
214
|
+
this._boundPath = undefined; /* an adopted pipe BoundSocket's path, its local address */
|
|
215
|
+
this._perfConnect = null; /* a TCP connect's start, while a PerformanceObserver takes 'net' entries */ /* set at connect when given before it */
|
|
216
|
+
this._onreadRest = null; /* internal/net_onread.js's: the bytes read but not yet given when reading stopped */
|
|
184
217
|
this.timeout = undefined;
|
|
185
218
|
this.autoSelectFamilyAttemptedAddresses = undefined;
|
|
186
219
|
validateBlockList(options.blockList, "options.blockList");
|
|
@@ -188,10 +221,28 @@ export class Socket extends Duplex {
|
|
|
188
221
|
const onread = options.onread;
|
|
189
222
|
if (onread !== null && typeof onread === "object" && (typeof onread.buffer === "function" || onread.buffer instanceof Uint8Array) &&
|
|
190
223
|
typeof onread.callback === "function") {
|
|
224
|
+
feature("onread"); /* internal/net_onread.js's */
|
|
191
225
|
this._onread = onread;
|
|
192
226
|
}
|
|
193
227
|
this.on("end", onReadableStreamEnd);
|
|
194
|
-
|
|
228
|
+
/* an adopted BoundSocket's handle: bound, not yet connected, so not read until it is */
|
|
229
|
+
const bound = options.handle !== undefined ? features.adoptBoundSocket?.(options.handle) : null;
|
|
230
|
+
if (bound) {
|
|
231
|
+
const { id, path } = bound;
|
|
232
|
+
this._handle = id;
|
|
233
|
+
this._boundSource = true;
|
|
234
|
+
this._isPipe = path !== undefined;
|
|
235
|
+
netCallback(id, bindContext((event, value) => this._onEvent(event, value, id)));
|
|
236
|
+
if (path !== undefined) {
|
|
237
|
+
this._boundPath = path;
|
|
238
|
+
/* a pipe's bind leaves its stream unopened: opened on its own descriptor, as Node's */
|
|
239
|
+
const fd = netFd(id);
|
|
240
|
+
if (fd >= 0) {
|
|
241
|
+
const err = netOpenFd(id, fd);
|
|
242
|
+
if (err) throw errnoException(err, "open");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else if (options.handle !== undefined) {
|
|
195
246
|
const id = options.handle;
|
|
196
247
|
this._isPipe = !!options._isPipe;
|
|
197
248
|
this._attach(id);
|
|
@@ -209,7 +260,7 @@ export class Socket extends Duplex {
|
|
|
209
260
|
}
|
|
210
261
|
this._attach(id);
|
|
211
262
|
}
|
|
212
|
-
if (this._handle !== null && options.readable !== false) {
|
|
263
|
+
if (this._handle !== null && !bound && options.readable !== false) {
|
|
213
264
|
if (options.pauseOnCreate) this._readableState.flowing = false;
|
|
214
265
|
else if (!options.manualStart) this.read(0);
|
|
215
266
|
}
|
|
@@ -257,7 +308,7 @@ export class Socket extends Duplex {
|
|
|
257
308
|
this._bytesRead += value.byteLength;
|
|
258
309
|
this._unrefTimer();
|
|
259
310
|
let more;
|
|
260
|
-
if (this._onread) more =
|
|
311
|
+
if (this._onread) more = features.onread.deliver(this, value);
|
|
261
312
|
else more = this.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength));
|
|
262
313
|
if (!more) {
|
|
263
314
|
this._reading = false;
|
|
@@ -273,22 +324,31 @@ export class Socket extends Duplex {
|
|
|
273
324
|
this.destroy(errnoException(value, "read"));
|
|
274
325
|
return;
|
|
275
326
|
}
|
|
327
|
+
if (this._onread) features.onread.eof(this);
|
|
276
328
|
this.push(null);
|
|
277
329
|
this.read(0);
|
|
278
330
|
}
|
|
279
331
|
|
|
280
|
-
/* onread
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
332
|
+
/* with onread, pausing stops reading from the handle and resuming starts it, as Node's */
|
|
333
|
+
pause() {
|
|
334
|
+
if (this._onread) features.onread.pause(this);
|
|
335
|
+
return super.pause();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
resume() {
|
|
339
|
+
if (this._onread) features.onread.resume(this);
|
|
340
|
+
return super.resume();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* reading from the handle again (with onread, the bytes left from the last read first) */
|
|
344
|
+
_startReading() {
|
|
345
|
+
this._reading = true;
|
|
346
|
+
if (this._onreadRest) {
|
|
347
|
+
features.onread.rest(this);
|
|
348
|
+
return;
|
|
290
349
|
}
|
|
291
|
-
|
|
350
|
+
const err = netReadStart(this._handle);
|
|
351
|
+
if (err) this.destroy(errnoException(err, "read"));
|
|
292
352
|
}
|
|
293
353
|
|
|
294
354
|
get pending() {
|
|
@@ -304,25 +364,36 @@ export class Socket extends Duplex {
|
|
|
304
364
|
}
|
|
305
365
|
|
|
306
366
|
get bufferSize() {
|
|
307
|
-
return this._handle !== null ? this.writableLength : undefined;
|
|
367
|
+
return this._handle !== null || this.connecting ? this.writableLength : undefined;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
get _connecting() {
|
|
371
|
+
return this.connecting;
|
|
308
372
|
}
|
|
309
373
|
|
|
310
374
|
get bytesRead() {
|
|
311
375
|
return this._bytesRead;
|
|
312
376
|
}
|
|
313
377
|
|
|
378
|
+
/* Node's: what was sent, and what's still buffered (undefined on the prototype) */
|
|
314
379
|
get bytesWritten() {
|
|
380
|
+
const writableBuffer = this.writableBuffer;
|
|
381
|
+
if (!writableBuffer) return undefined;
|
|
315
382
|
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;
|
|
383
|
+
for (const el of writableBuffer) bytes += typeof el.chunk === "string" ? Buffer.byteLength(el.chunk, el.encoding) : el.chunk.length;
|
|
318
384
|
const data = this._pendingData;
|
|
319
|
-
if (
|
|
385
|
+
if (Array.isArray(data)) {
|
|
386
|
+
for (const el of data) bytes += typeof el.chunk === "string" ? Buffer.byteLength(el.chunk, el.encoding) : el.chunk.length;
|
|
387
|
+
} else if (data) {
|
|
388
|
+
bytes += typeof data === "string" ? Buffer.byteLength(data, this._pendingEncoding) : data.length;
|
|
389
|
+
}
|
|
320
390
|
return bytes;
|
|
321
391
|
}
|
|
322
392
|
|
|
323
|
-
/* a pipe's ends have no address object: Node's Pipe has no getpeername/getsockname
|
|
393
|
+
/* a pipe's ends have no address object: Node's Pipe has no getpeername/getsockname;
|
|
394
|
+
* once closed, the address read while connected */
|
|
324
395
|
_getpeername() {
|
|
325
|
-
if (this._handle === null || this._isPipe) return {};
|
|
396
|
+
if (this._handle === null || this._isPipe || this.connecting) return this._peername || {};
|
|
326
397
|
if (!this._peername) {
|
|
327
398
|
const a = netAddress(this._handle, true);
|
|
328
399
|
if (typeof a === "number") return {};
|
|
@@ -332,7 +403,8 @@ export class Socket extends Duplex {
|
|
|
332
403
|
}
|
|
333
404
|
|
|
334
405
|
_getsockname() {
|
|
335
|
-
if (this.
|
|
406
|
+
if (this._boundPath !== undefined) return { address: this._boundPath };
|
|
407
|
+
if (this._handle === null || this._isPipe) return this._sockname || {};
|
|
336
408
|
if (!this._sockname) {
|
|
337
409
|
const a = netAddress(this._handle, false);
|
|
338
410
|
if (typeof a === "number") return {};
|
|
@@ -391,16 +463,19 @@ export class Socket extends Duplex {
|
|
|
391
463
|
if (typeof msecs !== "number") throw invalidArgType("msecs", "of type number", msecs);
|
|
392
464
|
if (!(msecs >= 0 && msecs !== Infinity)) throw outOfRange("msecs", "a non-negative finite number", msecs);
|
|
393
465
|
if (msecs > TIMEOUT_MAX) msecs = TIMEOUT_MAX;
|
|
394
|
-
|
|
466
|
+
/* the pending timer stays when it fires no later than the new deadline: _onTimer re-arms it
|
|
467
|
+
* for the rest, so a keep-alive socket, off for each request and on after it, sets no timer */
|
|
395
468
|
if (msecs === 0) {
|
|
469
|
+
this._timeoutMs = 0;
|
|
396
470
|
if (callback !== undefined) {
|
|
397
471
|
validateFunction(callback, "callback");
|
|
398
472
|
this.removeListener("timeout", callback);
|
|
399
473
|
}
|
|
400
474
|
} else {
|
|
401
475
|
this._timeoutMs = msecs;
|
|
402
|
-
this._lastActive =
|
|
403
|
-
this.
|
|
476
|
+
this._lastActive = loopNow();
|
|
477
|
+
if (this._timer !== null && this._timerDue > this._lastActive + msecs) this._stopTimer();
|
|
478
|
+
if (this._timer === null) this._armTimeout(msecs);
|
|
404
479
|
if (callback !== undefined) {
|
|
405
480
|
validateFunction(callback, "callback");
|
|
406
481
|
this.once("timeout", callback);
|
|
@@ -410,26 +485,32 @@ export class Socket extends Duplex {
|
|
|
410
485
|
}
|
|
411
486
|
|
|
412
487
|
_armTimeout(ms) {
|
|
488
|
+
this._timerDue = loopNow() + ms;
|
|
413
489
|
this._timer = timerUnref(() => this._onTimer(), ms);
|
|
414
490
|
}
|
|
415
491
|
|
|
416
492
|
_onTimer() {
|
|
417
493
|
this._timer = null;
|
|
418
|
-
|
|
494
|
+
if (this._timeoutMs === 0) return;
|
|
495
|
+
const idle = loopNow() - this._lastActive;
|
|
419
496
|
if (idle < this._timeoutMs - 1) this._armTimeout(this._timeoutMs - idle);
|
|
420
497
|
else this.emit("timeout");
|
|
421
498
|
}
|
|
422
499
|
|
|
423
|
-
|
|
500
|
+
_stopTimer() {
|
|
424
501
|
if (this._timer !== null) clearTimeout(this._timer);
|
|
425
502
|
this._timer = null;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
_clearTimeout() {
|
|
506
|
+
this._stopTimer();
|
|
426
507
|
this._timeoutMs = 0;
|
|
427
508
|
}
|
|
428
509
|
|
|
429
510
|
/* activity: the timeout counts from now (Node's timer.refresh(), which re-arms a fired one) */
|
|
430
511
|
_unrefTimer() {
|
|
431
512
|
if (this._timeoutMs) {
|
|
432
|
-
this._lastActive =
|
|
513
|
+
this._lastActive = loopNow();
|
|
433
514
|
if (this._timer === null) this._armTimeout(this._timeoutMs);
|
|
434
515
|
}
|
|
435
516
|
}
|
|
@@ -448,11 +529,7 @@ export class Socket extends Duplex {
|
|
|
448
529
|
|
|
449
530
|
_read(n) {
|
|
450
531
|
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
|
-
}
|
|
532
|
+
else if (!this._reading) this._startReading();
|
|
456
533
|
}
|
|
457
534
|
|
|
458
535
|
/* Node's _writeGeneric: a write before the connection waits for it; a write the kernel takes at
|
|
@@ -498,7 +575,7 @@ export class Socket extends Duplex {
|
|
|
498
575
|
|
|
499
576
|
/* end(): shut the write side down once the writes are out */
|
|
500
577
|
_final(cb) {
|
|
501
|
-
if (this.
|
|
578
|
+
if (this.connecting) {
|
|
502
579
|
this.once("connect", () => this._final(cb));
|
|
503
580
|
return;
|
|
504
581
|
}
|
|
@@ -551,7 +628,7 @@ export class Socket extends Duplex {
|
|
|
551
628
|
|
|
552
629
|
/* close with a TCP RST instead of a FIN */
|
|
553
630
|
resetAndDestroy() {
|
|
554
|
-
if (this._handle !== null) {
|
|
631
|
+
if (this._handle !== null || this.connecting) {
|
|
555
632
|
if (this._isPipe) throw codeError(TypeError, "ERR_INVALID_HANDLE_TYPE", "This handle type cannot be sent");
|
|
556
633
|
if (this.connecting) this.once("connect", () => this._reset());
|
|
557
634
|
else this._reset();
|
|
@@ -574,12 +651,27 @@ export class Socket extends Duplex {
|
|
|
574
651
|
connect(...args) {
|
|
575
652
|
const [options, cb] = args[0] && args[0][normalizedArgs] ? args[0] : normalizeArgs(args);
|
|
576
653
|
if (this.write !== Socket.prototype.write) this.write = Socket.prototype.write;
|
|
577
|
-
if (
|
|
654
|
+
if (options.port === undefined && options.path == null) {
|
|
655
|
+
throw codeError(TypeError, "ERR_MISSING_ARGS", 'The "options" or "port" or "path" argument must be specified');
|
|
656
|
+
}
|
|
657
|
+
/* a closed socket connects again, as Node's: its streams undestroyed, its counts the new handle's */
|
|
658
|
+
if (this.destroyed) {
|
|
659
|
+
this._handle = null;
|
|
660
|
+
this._peername = null;
|
|
661
|
+
this._sockname = null;
|
|
662
|
+
this._bytesRead = 0;
|
|
663
|
+
this._bytesDispatched = 0;
|
|
664
|
+
this._hadError = false;
|
|
665
|
+
this._perfConnect = null;
|
|
666
|
+
this._undestroy();
|
|
667
|
+
}
|
|
578
668
|
const { path } = options;
|
|
579
669
|
if (cb !== null) this.once("connect", cb);
|
|
580
670
|
this._unrefTimer();
|
|
581
671
|
this.connecting = true;
|
|
582
|
-
|
|
672
|
+
/* an adopted handle is a pipe or not whatever the options say; once it's gone, the path does */
|
|
673
|
+
const pipe = this._boundSource && this._handle !== null ? this._isPipe : !!path;
|
|
674
|
+
if (pipe) {
|
|
583
675
|
validateString(path, "options.path");
|
|
584
676
|
this._isPipe = true;
|
|
585
677
|
this._internalConnect(path);
|
|
@@ -594,23 +686,37 @@ export class Socket extends Duplex {
|
|
|
594
686
|
const host = options.host || "localhost";
|
|
595
687
|
let { port, autoSelectFamilyAttemptTimeout: attemptTimeout, autoSelectFamily: selectFamily } = options;
|
|
596
688
|
if (localAddress && !isIP(localAddress)) throw codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + localAddress);
|
|
597
|
-
if (localPort
|
|
689
|
+
if (localPort && typeof localPort !== "number") throw invalidArgType("options.localPort", "of type number", localPort);
|
|
598
690
|
if (typeof host !== "string") throw invalidArgType("options.host", "of type string", host);
|
|
691
|
+
if (host.includes("\0")) throw invalidArgValue("options.host", "must be a string without null bytes", host);
|
|
692
|
+
/* an adopted BoundSocket owns the local end and the address family */
|
|
693
|
+
if (this._boundSource) {
|
|
694
|
+
if (localAddress !== undefined || localPort !== undefined) {
|
|
695
|
+
throw invalidArgValue("options", "localAddress and localPort cannot be used with an adopted bound socket", options);
|
|
696
|
+
}
|
|
697
|
+
selectFamily = false;
|
|
698
|
+
}
|
|
599
699
|
if (typeof port !== "undefined") {
|
|
600
700
|
if (typeof port !== "number" && typeof port !== "string") throw invalidArgType("options.port", "of type number or string", port);
|
|
601
701
|
port = validatePort(port);
|
|
602
702
|
}
|
|
603
703
|
port |= 0;
|
|
604
|
-
if (selectFamily
|
|
704
|
+
if (selectFamily != null && typeof selectFamily !== "boolean") throw invalidArgType("options.autoSelectFamily", "of type boolean", selectFamily);
|
|
605
705
|
selectFamily ??= autoSelectFamily;
|
|
606
|
-
if (attemptTimeout
|
|
607
|
-
|
|
706
|
+
if (attemptTimeout != null) {
|
|
707
|
+
validateInt32(attemptTimeout, "options.autoSelectFamilyAttemptTimeout", 1);
|
|
608
708
|
if (attemptTimeout < 10) attemptTimeout = 10;
|
|
609
709
|
} else {
|
|
610
710
|
attemptTimeout = autoSelectFamilyAttemptTimeout;
|
|
611
711
|
}
|
|
612
712
|
this._host = host;
|
|
613
713
|
const addressType = isIP(host);
|
|
714
|
+
/* an adopted BoundSocket connects in this tick, so its local address is known at once (a
|
|
715
|
+
* failure still comes as an 'error' event) */
|
|
716
|
+
if (addressType && this._boundSource) {
|
|
717
|
+
this._internalConnect(host, port, addressType, localAddress, localPort);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
614
720
|
if (addressType) {
|
|
615
721
|
process.nextTick(() => {
|
|
616
722
|
if (this.connecting) this._internalConnect(host, port, addressType, localAddress, localPort);
|
|
@@ -620,17 +726,19 @@ export class Socket extends Duplex {
|
|
|
620
726
|
let family = options.family ?? 0;
|
|
621
727
|
if (family === "IPv4") family = 4;
|
|
622
728
|
else if (family === "IPv6") family = 6;
|
|
623
|
-
const dnsopts = { family, all: selectFamily && family === 0 };
|
|
624
729
|
const lookupFn = options.lookup ?? lookup;
|
|
625
730
|
if (typeof lookupFn !== "function") throw invalidArgType("options.lookup", "of type function", lookupFn);
|
|
731
|
+
const dnsopts = { family, hints: options.hints || 0, all: false };
|
|
732
|
+
if (process.platform !== "win32" && family !== 4 && family !== 6 && dnsopts.hints === 0) dnsopts.hints = ADDRCONFIG;
|
|
733
|
+
dnsopts.all = family !== 4 && family !== 6 && !localAddress && selectFamily;
|
|
626
734
|
if (!dnsopts.all) {
|
|
627
735
|
lookupFn(host, dnsopts, (err, ip, addressType) => {
|
|
628
736
|
this.emit("lookup", err, ip, addressType, host);
|
|
629
737
|
if (!this.connecting) return;
|
|
630
738
|
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)));
|
|
739
|
+
else if (typeof ip !== "string" || !isIP(ip)) process.nextTick(() => this.destroy(codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + ip)));
|
|
632
740
|
else if (addressType !== 4 && addressType !== 6) {
|
|
633
|
-
process.nextTick(() => this.destroy(
|
|
741
|
+
process.nextTick(() => this.destroy(addressFamilyError(addressType, host, options.port)));
|
|
634
742
|
} else {
|
|
635
743
|
this._unrefTimer();
|
|
636
744
|
this._internalConnect(ip, port, addressType, localAddress, localPort);
|
|
@@ -661,7 +769,7 @@ export class Socket extends Duplex {
|
|
|
661
769
|
const first = addresses[0];
|
|
662
770
|
const error = !first || !isIP(first.address)
|
|
663
771
|
? codeError(TypeError, "ERR_INVALID_IP_ADDRESS", "Invalid IP address: " + first?.address)
|
|
664
|
-
:
|
|
772
|
+
: addressFamilyError(first.family, host, options.port);
|
|
665
773
|
process.nextTick(() => this.destroy(error));
|
|
666
774
|
return;
|
|
667
775
|
}
|
|
@@ -687,14 +795,23 @@ export class Socket extends Duplex {
|
|
|
687
795
|
/* a new handle bound to the local address, if any, and connecting: 0 or the failure as Node reports it */
|
|
688
796
|
_open(address, port, addressType, localAddress, localPort, onConnect) {
|
|
689
797
|
let id;
|
|
690
|
-
|
|
798
|
+
const cb = bindContext((event, value) => {
|
|
691
799
|
if (event === NET_CONNECT) {
|
|
692
800
|
if (this._handle === id) onConnect(value, id);
|
|
693
801
|
} else {
|
|
694
802
|
this._onEvent(event, value, id);
|
|
695
803
|
}
|
|
696
|
-
})
|
|
697
|
-
this._handle
|
|
804
|
+
});
|
|
805
|
+
if (this._boundSource && this._handle !== null) { /* an adopted BoundSocket's, already bound */
|
|
806
|
+
id = this._handle;
|
|
807
|
+
netCallback(id, cb);
|
|
808
|
+
} else {
|
|
809
|
+
id = netOpen(this._isPipe, cb);
|
|
810
|
+
this._handle = id;
|
|
811
|
+
}
|
|
812
|
+
if (!this._isPipe && this._perfConnect === null && features.perfObserving?.("net")) {
|
|
813
|
+
this._perfConnect = { start: features.perfNow(), host: address, port };
|
|
814
|
+
}
|
|
698
815
|
if (!this._isPipe && (localAddress || localPort)) {
|
|
699
816
|
localAddress ||= addressType === 4 ? "0.0.0.0" : "::";
|
|
700
817
|
const err = netBind(id, localAddress, localPort | 0, 0);
|
|
@@ -780,17 +897,27 @@ export class Socket extends Duplex {
|
|
|
780
897
|
}
|
|
781
898
|
this._attach(this._handle);
|
|
782
899
|
this._unrefTimer();
|
|
900
|
+
if (this._perfConnect !== null) {
|
|
901
|
+
const { start, host, port } = this._perfConnect;
|
|
902
|
+
this._perfConnect = null;
|
|
903
|
+
features.perfObserve(features.perfEntry("connect", "net", start, features.perfNow() - start, { host, port }));
|
|
904
|
+
}
|
|
905
|
+
if (this._tos !== undefined && !this._isPipe) features.applyTypeOfService(this);
|
|
783
906
|
this.emit("connect");
|
|
784
907
|
this.emit("ready");
|
|
785
908
|
if (this.readable && !this.isPaused()) this.read(0);
|
|
786
909
|
}
|
|
787
910
|
}
|
|
911
|
+
/* internal/net_tos.js's, linked when the program names them */
|
|
912
|
+
SocketClass.prototype.setTypeOfService = lazyFunction("setTypeOfService", 1);
|
|
913
|
+
SocketClass.prototype.getTypeOfService = lazyFunction("getTypeOfService");
|
|
914
|
+
export const Socket = callableClass(SocketClass, "Socket"); /* Socket(…) without new, as Node's */
|
|
788
915
|
|
|
789
916
|
export const Stream = Socket;
|
|
790
917
|
|
|
791
918
|
/* ---- Server -------------------------------------------------------------------------------- */
|
|
792
919
|
|
|
793
|
-
|
|
920
|
+
class ServerClass extends EventEmitter {
|
|
794
921
|
constructor(options, connectionListener) {
|
|
795
922
|
super();
|
|
796
923
|
if (typeof options === "function") {
|
|
@@ -803,6 +930,7 @@ export class Server extends EventEmitter {
|
|
|
803
930
|
}
|
|
804
931
|
if (typeof connectionListener === "function") this.on("connection", connectionListener);
|
|
805
932
|
this._connections = 0;
|
|
933
|
+
this._listeningId = 1; /* a listen or close since a lookup began makes the lookup's result stale */
|
|
806
934
|
this._handle = null;
|
|
807
935
|
this._isPipe = false;
|
|
808
936
|
this._pipeName = undefined;
|
|
@@ -827,7 +955,16 @@ export class Server extends EventEmitter {
|
|
|
827
955
|
if (typeof options.callback === "function") cb = options.callback;
|
|
828
956
|
if (this._handle !== null) throw codeError(Error, "ERR_SERVER_ALREADY_LISTEN", "Listen method has been called more than once without closing.");
|
|
829
957
|
if (cb !== null) this.once("listening", cb);
|
|
958
|
+
this._listeningId++;
|
|
830
959
|
const backlogFromArgs = toNumber(args.length > 1 && args[1]) || toNumber(args.length > 2 && args[2]);
|
|
960
|
+
/* listen(boundSocket) or listen({ handle: boundSocket }): the server takes its handle */
|
|
961
|
+
const bound = features.adoptBoundSocket?.(options) ?? features.adoptBoundSocket?.(options.handle);
|
|
962
|
+
if (bound) {
|
|
963
|
+
if (bound.path !== undefined) this._pipeName = bound.path;
|
|
964
|
+
this._isPipe = bound.path !== undefined;
|
|
965
|
+
this._setupListen(null, -1, -1, backlogFromArgs, undefined, 0, bound.id);
|
|
966
|
+
return this;
|
|
967
|
+
}
|
|
831
968
|
if (options._handle !== undefined && options._handle !== null) options = options._handle;
|
|
832
969
|
else if (options.handle !== undefined && options.handle !== null) options = options.handle;
|
|
833
970
|
const flags = (options.ipv6Only ? 1 : 0) | (options.reusePort ? 2 : 0);
|
|
@@ -880,7 +1017,9 @@ export class Server extends EventEmitter {
|
|
|
880
1017
|
}
|
|
881
1018
|
|
|
882
1019
|
_lookupAndListen(port, address, backlog, flags) {
|
|
1020
|
+
const listeningId = this._listeningId;
|
|
883
1021
|
lookup(address, (err, ip, addressType) => {
|
|
1022
|
+
if (listeningId !== this._listeningId) return;
|
|
884
1023
|
if (err) this.emit("error", err);
|
|
885
1024
|
else this._setupListen(ip, port, ip ? addressType : 4, backlog, undefined, flags);
|
|
886
1025
|
});
|
|
@@ -970,6 +1109,7 @@ export class Server extends EventEmitter {
|
|
|
970
1109
|
process.nextTick(() => this.emit("error", ex));
|
|
971
1110
|
return;
|
|
972
1111
|
}
|
|
1112
|
+
this._connectionKey = addressType + ":" + address + ":" + port;
|
|
973
1113
|
if (this._unref) netRef(id, false);
|
|
974
1114
|
process.nextTick(() => {
|
|
975
1115
|
if (this._handle !== null) this.emit("listening");
|
|
@@ -1024,6 +1164,7 @@ export class Server extends EventEmitter {
|
|
|
1024
1164
|
|
|
1025
1165
|
/* close([cb]): stop accepting; 'close' once the open connections end too */
|
|
1026
1166
|
close(cb) {
|
|
1167
|
+
this._listeningId++;
|
|
1027
1168
|
if (typeof cb === "function") {
|
|
1028
1169
|
if (this._handle === null) this.once("close", () => cb(codeError(Error, "ERR_SERVER_NOT_RUNNING", "Server is not running.")));
|
|
1029
1170
|
else this.once("close", cb);
|
|
@@ -1036,6 +1177,12 @@ export class Server extends EventEmitter {
|
|
|
1036
1177
|
return this;
|
|
1037
1178
|
}
|
|
1038
1179
|
|
|
1180
|
+
/* captureRejections: a 'connection' listener's rejection destroys the socket, as Node's */
|
|
1181
|
+
[Symbol.for("nodejs.rejection")](err, event, sock) {
|
|
1182
|
+
if (event === "connection") sock.destroy(err);
|
|
1183
|
+
else this.emit("error", err);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1039
1186
|
_emitCloseIfDrained() {
|
|
1040
1187
|
if (this._handle !== null || this._connections) return;
|
|
1041
1188
|
process.nextTick(() => this.emit("close"));
|
|
@@ -1063,6 +1210,7 @@ export class Server extends EventEmitter {
|
|
|
1063
1210
|
return new Promise((resolve, reject) => this.close((err) => (err ? reject(err) : resolve())));
|
|
1064
1211
|
}
|
|
1065
1212
|
}
|
|
1213
|
+
export const Server = callableClass(ServerClass, "Server"); /* Server(…) without new, as Node's */
|
|
1066
1214
|
|
|
1067
1215
|
export function createServer(options, connectionListener) {
|
|
1068
1216
|
return new Server(options, connectionListener);
|
|
@@ -1079,7 +1227,10 @@ export function connect(...args) {
|
|
|
1079
1227
|
|
|
1080
1228
|
export const createConnection = connect;
|
|
1081
1229
|
|
|
1230
|
+
/* internal/net_bound.js's, linked when the program names it */
|
|
1231
|
+
export const BoundSocket = lazyClass("BoundSocket");
|
|
1232
|
+
|
|
1082
1233
|
export default {
|
|
1083
|
-
BlockList, SocketAddress, isIP, isIPv4, isIPv6, Socket, Stream, Server, connect, createConnection, createServer,
|
|
1234
|
+
BlockList, BoundSocket, SocketAddress, isIP, isIPv4, isIPv6, Socket, Stream, Server, connect, createConnection, createServer,
|
|
1084
1235
|
getDefaultAutoSelectFamily, setDefaultAutoSelectFamily, getDefaultAutoSelectFamilyAttemptTimeout, setDefaultAutoSelectFamilyAttemptTimeout,
|
|
1085
1236
|
};
|
|
@@ -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");
|