| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent db95655 commit b6e7935
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -452,6 +452,48 @@ changes: | |||
| 452 | 452 | Calls [`server.close()`][] and returns a promise that fulfills when the | |
| 453 | 453 | server has closed. | |
| 454 | 454 | ||
| 455 | + ### `server[Symbol.asyncIterator]()` | ||
| 456 | + | ||
| 457 | + <!-- YAML | ||
| 458 | + added: REPLACEME | ||
| 459 | + --> | ||
| 460 | + | ||
| 461 | + > Stability: 1 - Experimental | ||
| 462 | + | ||
| 463 | + * Returns: {AsyncIterator} An async iterator that yields each incoming | ||
| 464 | + [`net.Socket`][]. | ||
| 465 | + | ||
| 466 | + Returns an async iterator over the server's incoming connections, allowing them | ||
| 467 | + to be consumed with `for await...of` as an alternative to the [`'connection'`][] | ||
| 468 | + event. Iteration ends when the server emits [`'close'`][], and rejects if the | ||
| 469 | + server emits [`'error'`][]. | ||
| 470 | + | ||
| 471 | + The loop only advances to the next connection once the current iteration's body | ||
| 472 | + has finished awaiting, so connection handling should be dispatched to a separate | ||
| 473 | + async task rather than awaited inline. Otherwise connections are serialized: | ||
| 474 | + each one waits for the previous to be fully handled. | ||
| 475 | + | ||
| 476 | + ```mjs | ||
| 477 | + import { createServer } from 'node:net'; | ||
| 478 | + | ||
| 479 | + const server = createServer().listen(8124); | ||
| 480 | + | ||
| 481 | + async function handleConnection(socket) { | ||
| 482 | + // ...handle the connection, awaiting as needed. | ||
| 483 | + socket.end('hello world!'); | ||
| 484 | + } | ||
| 485 | + | ||
| 486 | + for await (const socket of server) { | ||
| 487 | + // Dispatch handling to a separate task so the loop keeps accepting | ||
| 488 | + // connections instead of serializing them. | ||
| 489 | + handleConnection(socket); | ||
| 490 | + } | ||
| 491 | + ``` | ||
| 492 | + | ||
| 493 | + The server does not stop accepting connections while the loop body runs, so a | ||
| 494 | + consumer slower than the connection rate can buffer them without bound. Use | ||
| 495 | + [`server.maxConnections`][] to bound concurrency. | ||
| 496 | + | ||
| 455 | 497 | ### `server.getConnections(callback)` | |
| 456 | 498 | ||
| 457 | 499 | <!-- YAML | |
@@ -2276,6 +2318,82 @@ net.isIPv6('::1'); // returns true | |||
| 2276 | 2318 | net.isIPv6('fhqwhgads'); // returns false | |
| 2277 | 2319 | ``` | |
| 2278 | 2320 | ||
| 2321 | + ## `net/promises` API | ||
| 2322 | + | ||
| 2323 | + <!-- YAML | ||
| 2324 | + added: REPLACEME | ||
| 2325 | + --> | ||
| 2326 | + | ||
| 2327 | + > Stability: 1 - Experimental | ||
| 2328 | + | ||
| 2329 | + The `net/promises` API provides a set of `net` functions that return `Promise` | ||
| 2330 | + objects rather than relying on events. The API is accessible via | ||
| 2331 | + `require('node:net').promises` or `require('node:net/promises')`. | ||
| 2332 | + | ||
| 2333 | + ### `netPromises.connect(options)` | ||
| 2334 | + | ||
| 2335 | + ### `netPromises.connect(path)` | ||
| 2336 | + | ||
| 2337 | + ### `netPromises.connect(port[, host])` | ||
| 2338 | + | ||
| 2339 | + <!-- YAML | ||
| 2340 | + added: REPLACEME | ||
| 2341 | + --> | ||
| 2342 | + | ||
| 2343 | + * `options` {Object} Accepts the same arguments as [`net.connect()`][]. May | ||
| 2344 | + include a `signal` {AbortSignal} that can be used to abort an in-progress | ||
| 2345 | + connection attempt. | ||
| 2346 | + * Returns: {Promise} Fulfills with a connected [`net.Socket`][]. | ||
| 2347 | + | ||
| 2348 | + A promise-based alternative to [`net.connect()`][]. The returned promise is | ||
| 2349 | + fulfilled with the socket once its [`'connect'`][] event fires, and is rejected | ||
| 2350 | + if the connection fails or the `signal` is aborted. When the promise rejects, | ||
| 2351 | + the underlying socket is destroyed. | ||
| 2352 | + | ||
| 2353 | + This API is named for the action it performs and awaits — connecting — to | ||
| 2354 | + parallel [`netPromises.listen()`][]. It is not named `createConnection()`, | ||
| 2355 | + because that name belongs to the socket-factory taxonomy of the callback API, | ||
| 2356 | + which has no counterpart here. | ||
| 2357 | + | ||
| 2358 | + ```mjs | ||
| 2359 | + import { connect } from 'node:net/promises'; | ||
| 2360 | + | ||
| 2361 | + const socket = await connect({ port: 8124 }); | ||
| 2362 | + socket.write('hello world!'); | ||
| 2363 | + socket.end(); | ||
| 2364 | + ``` | ||
| 2365 | + | ||
| 2366 | + ### `netPromises.listen([options])` | ||
| 2367 | + | ||
| 2368 | + <!-- YAML | ||
| 2369 | + added: REPLACEME | ||
| 2370 | + --> | ||
| 2371 | + | ||
| 2372 | + * `options` {Object} Accepts the same options as [`net.createServer()`][] and | ||
| 2373 | + [`server.listen()`][], plus: | ||
| 2374 | + * `connectionListener` {Function} Automatically set as a listener for the | ||
| 2375 | + [`'connection'`][] event. | ||
| 2376 | + * `signal` {AbortSignal} An `AbortSignal` that may be used to abort the | ||
| 2377 | + server. Aborting before the server is listening rejects the returned | ||
| 2378 | + promise with an `AbortError`; aborting at any later point closes the | ||
| 2379 | + server, matching the `signal` option of [`server.listen()`][]. | ||
| 2380 | + * Returns: {Promise} Fulfills with a listening [`net.Server`][]. | ||
| 2381 | + | ||
| 2382 | + Creates a [`net.Server`][] and begins listening. The returned promise is | ||
| 2383 | + fulfilled with the server once its [`'listening'`][] event fires, and is | ||
| 2384 | + rejected if the server fails to bind or the `signal` is aborted before it is | ||
| 2385 | + listening. When the promise rejects, the server is closed. | ||
| 2386 | + | ||
| 2387 | + The resolved server is async iterable, so incoming connections can be consumed | ||
| 2388 | + with `for await...of` (see `server[Symbol.asyncIterator]()`). | ||
| 2389 | + | ||
| 2390 | + ```mjs | ||
| 2391 | + import { listen } from 'node:net/promises'; | ||
| 2392 | + | ||
| 2393 | + const server = await listen({ port: 8124 }); | ||
| 2394 | + console.log('listening on', server.address().port); | ||
| 2395 | + ``` | ||
| 2396 | + | ||
| 2279 | 2397 | [IPC]: #ipc-support | |
| 2280 | 2398 | [Identifying paths for IPC connections]: #identifying-paths-for-ipc-connections | |
| 2281 | 2399 | [RFC 8305]: https://www.rfc-editor.org/rfc/rfc8305.txt | |
@@ -2310,6 +2428,7 @@ net.isIPv6('fhqwhgads'); // returns false | |||
| 2310 | 2428 | [`net.createServer()`]: #netcreateserveroptions-connectionlistener | |
| 2311 | 2429 | [`net.getDefaultAutoSelectFamily()`]: #netgetdefaultautoselectfamily | |
| 2312 | 2430 | [`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: #netgetdefaultautoselectfamilyattempttimeout | |
| 2431 | + [`netPromises.listen()`]: #netpromiseslistenoptions | ||
| 2313 | 2432 | [`new net.Socket(options)`]: #new-netsocketoptions | |
| 2314 | 2433 | [`readable.setEncoding()`]: stream.md#readablesetencodingencoding | |
| 2315 | 2434 | [`server.address()`]: #serveraddress | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,88 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const { once } = require('events'); | ||
| 4 | + const { | ||
| 5 | + validateAbortSignal, | ||
| 6 | + validateObject, | ||
| 7 | + } = require('internal/validators'); | ||
| 8 | + const { kEmptyObject } = require('internal/util'); | ||
| 9 | + | ||
| 10 | + // Lazily loaded to avoid a require cycle with the `net` module, which exposes | ||
| 11 | + // this namespace through its `promises` getter. | ||
| 12 | + let net; | ||
| 13 | + function lazyNet() { | ||
| 14 | + net ??= require('net'); | ||
| 15 | + return net; | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + // Resolves with a connected `net.Socket` once the `'connect'` event fires, and | ||
| 19 | + // rejects if the connection fails or the optional `signal` is aborted. | ||
| 20 | + async function connect(...args) { | ||
| 21 | + const lazy = lazyNet(); | ||
| 22 | + const options = lazy._normalizeArgs(args)[0]; | ||
| 23 | + const { signal } = options; | ||
| 24 | + if (signal !== undefined) { | ||
| 25 | + validateAbortSignal(signal, 'options.signal'); | ||
| 26 | + signal.throwIfAborted(); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + // Strip the signal so the socket does not also install its own abort | ||
| 30 | + // handling; rejecting and destroying below fully tears the socket down. | ||
| 31 | + const socket = lazy.connect({ ...options, signal: undefined }); | ||
| 32 | + | ||
| 33 | + try { | ||
| 34 | + await once(socket, 'connect', signal !== undefined ? { signal } : kEmptyObject); | ||
| 35 | + } catch (err) { | ||
| 36 | + socket.destroy(); | ||
| 37 | + throw err; | ||
| 38 | + } | ||
| 39 | + return socket; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + // Creates a server and resolves with it once it is listening, rejecting if it | ||
| 43 | + // fails to bind or the optional `signal` is aborted. | ||
| 44 | + async function listen(options = kEmptyObject) { | ||
| 45 | + validateObject(options, 'options'); | ||
| 46 | + const { signal, connectionListener } = options; | ||
| 47 | + if (signal !== undefined) { | ||
| 48 | + validateAbortSignal(signal, 'options.signal'); | ||
| 49 | + signal.throwIfAborted(); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + const lazy = lazyNet(); | ||
| 53 | + const server = lazy.createServer(options, connectionListener); | ||
| 54 | + | ||
| 55 | + try { | ||
| 56 | + // Default to an ephemeral port when no listen target is supplied, matching | ||
| 57 | + // `server.listen()` with no arguments; passing a target-less options object | ||
| 58 | + // (the `{}` default, or e.g. `{ signal }`) straight to listen() would throw | ||
| 59 | + // ERR_INVALID_ARG_VALUE. `signal` is passed through so net installs its own | ||
| 60 | + // close-on-abort handler: the signal aborts the server for its entire | ||
| 61 | + // lifetime, not just the pending listen. | ||
| 62 | + const hasListenTarget = options.port !== undefined || | ||
| 63 | + options.path !== undefined || | ||
| 64 | + options.fd !== undefined || | ||
| 65 | + options.handle !== undefined; | ||
| 66 | + server.listen(hasListenTarget ? options : { ...options, port: 0 }); | ||
| 67 | + await once(server, 'listening', signal !== undefined ? { signal } : kEmptyObject); | ||
| 68 | + } catch (err) { | ||
| 69 | + // On abort, net's signal handler already closes the server, so closing | ||
| 70 | + // again would be redundant; on other failures (e.g. a bind error) there | ||
| 71 | + // is no such handler, so close it here. | ||
| 72 | + if (!signal?.aborted) { | ||
| 73 | + server.close(); | ||
| 74 | + } | ||
| 75 | + throw err; | ||
| 76 | + } | ||
| 77 | + return server; | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + module.exports = { | ||
| 81 | + connect, | ||
| 82 | + listen, | ||
| 83 | + get isIP() { return lazyNet().isIP; }, | ||
| 84 | + get isIPv4() { return lazyNet().isIPv4; }, | ||
| 85 | + get isIPv6() { return lazyNet().isIPv6; }, | ||
| 86 | + get BlockList() { return lazyNet().BlockList; }, | ||
| 87 | + get SocketAddress() { return lazyNet().SocketAddress; }, | ||
| 88 | + }; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -37,6 +37,7 @@ const { | |||
| 37 | 37 | ObjectSetPrototypeOf, | |
| 38 | 38 | Symbol, | |
| 39 | 39 | SymbolAsyncDispose, | |
| 40 | + SymbolAsyncIterator, | ||
| 40 | 41 | SymbolDispose, | |
| 41 | 42 | } = primordials; | |
| 42 | 43 | ||
@@ -156,6 +157,8 @@ let cluster; | |||
| 156 | 157 | let dns; | |
| 157 | 158 | let BlockList; | |
| 158 | 159 | let SocketAddress; | |
| 160 | + let netPromises; | ||
| 161 | + let kFirstEventParam; | ||
| 159 | 162 | let autoSelectFamilyDefault = getOptionValue('--network-family-autoselection'); | |
| 160 | 163 | let autoSelectFamilyAttemptTimeoutDefault = getOptionValue('--network-family-autoselection-attempt-timeout'); | |
| 161 | 164 | ||
@@ -2854,6 +2857,14 @@ Server.prototype[SymbolAsyncDispose] = async function() { | |||
| 2854 | 2857 | await FunctionPrototypeCall(promisify(this.close), this); | |
| 2855 | 2858 | }; | |
| 2856 | 2859 | ||
| 2860 | + Server.prototype[SymbolAsyncIterator] = function() { | ||
| 2861 | + kFirstEventParam ??= require('internal/events/symbols').kFirstEventParam; | ||
| 2862 | + return EventEmitter.on(this, 'connection', { | ||
| 2863 | + close: ['close'], | ||
| 2864 | + [kFirstEventParam]: true, | ||
| 2865 | + }); | ||
| 2866 | + }; | ||
| 2867 | + | ||
| 2857 | 2868 | Server.prototype._emitCloseIfDrained = function() { | |
| 2858 | 2869 | debug('SERVER _emitCloseIfDrained'); | |
| 2859 | 2870 | ||
@@ -2945,6 +2956,10 @@ module.exports = { | |||
| 2945 | 2956 | connect, | |
| 2946 | 2957 | createConnection: connect, | |
| 2947 | 2958 | createServer, | |
| 2959 | + get promises() { | ||
| 2960 | + netPromises ??= require('internal/net/promises'); | ||
| 2961 | + return netPromises; | ||
| 2962 | + }, | ||
| 2948 | 2963 | isIP: isIP, | |
| 2949 | 2964 | isIPv4: isIPv4, | |
| 2950 | 2965 | isIPv6: isIPv6, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,3 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + module.exports = require('internal/net/promises'); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,62 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const assert = require('assert'); | ||
| 4 | + const net = require('net'); | ||
| 5 | + const { once } = require('events'); | ||
| 6 | + const { connect } = require('net/promises'); | ||
| 7 | + | ||
| 8 | + (async () => { | ||
| 9 | + // Resolves with a connected socket and round-trips data. | ||
| 10 | + { | ||
| 11 | + const server = net.createServer((socket) => { | ||
| 12 | + socket.end('hello'); | ||
| 13 | + }).listen(0); | ||
| 14 | + await once(server, 'listening'); | ||
| 15 | + const socket = await connect({ port: server.address().port }); | ||
| 16 | + assert.strictEqual(socket.connecting, false); | ||
| 17 | + const chunks = []; | ||
| 18 | + for await (const chunk of socket) { | ||
| 19 | + chunks.push(chunk); | ||
| 20 | + } | ||
| 21 | + assert.strictEqual(Buffer.concat(chunks).toString(), 'hello'); | ||
| 22 | + server.close(); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + // net.promises is the same object as require('net/promises'). | ||
| 26 | + assert.strictEqual(net.promises, require('net/promises')); | ||
| 27 | + | ||
| 28 | + // Rejects when the connection is refused. | ||
| 29 | + { | ||
| 30 | + const server = net.createServer().listen(0); | ||
| 31 | + await once(server, 'listening'); | ||
| 32 | + const { port } = server.address(); | ||
| 33 | + server.close(); | ||
| 34 | + await once(server, 'close'); | ||
| 35 | + await assert.rejects(connect({ port }), { code: 'ECONNREFUSED' }); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + // A pre-aborted signal rejects with an AbortError. | ||
| 39 | + { | ||
| 40 | + await assert.rejects( | ||
| 41 | + connect({ port: 0, signal: AbortSignal.abort() }), | ||
| 42 | + { name: 'AbortError' }); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + // Aborting while connecting rejects with an AbortError. | ||
| 46 | + { | ||
| 47 | + const server = net.createServer().listen(0); | ||
| 48 | + await once(server, 'listening'); | ||
| 49 | + const controller = new AbortController(); | ||
| 50 | + const promise = connect({ port: server.address().port, signal: controller.signal }); | ||
| 51 | + controller.abort(); | ||
| 52 | + await assert.rejects(promise, { name: 'AbortError' }); | ||
| 53 | + server.close(); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + // An invalid signal throws. | ||
| 57 | + { | ||
| 58 | + await assert.rejects( | ||
| 59 | + connect({ port: 0, signal: 'INVALID_SIGNAL' }), | ||
| 60 | + { code: 'ERR_INVALID_ARG_TYPE' }); | ||
| 61 | + } | ||
| 62 | + })().then(common.mustCall()); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments