From 69bd2ae2c558d9e89fd7bd435ab41315306a1c87 Mon Sep 17 00:00:00 2001 From: nodejs-github-bot <18269663+nodejs-github-bot@users.noreply.github.com> Date: Sun, 25 May 2025 00:40:44 +0000 Subject: [PATCH] deps: update undici to 7.10.0 --- deps/undici/src/.gitignore | 1 + deps/undici/src/docs/docs/api/CacheStore.md | 24 +++- deps/undici/src/docs/docs/api/Pool.md | 1 + deps/undici/src/docs/docs/api/ProxyAgent.md | 1 + .../src/lib/cache/memory-cache-store.js | 40 +++++- deps/undici/src/lib/dispatcher/agent.js | 40 ++++-- deps/undici/src/lib/dispatcher/pool.js | 20 ++- deps/undici/src/lib/dispatcher/proxy-agent.js | 90 +++++++++++- deps/undici/src/lib/llhttp/wasm_build_env.txt | 2 +- deps/undici/src/lib/mock/mock-agent.js | 16 +-- deps/undici/src/package-lock.json | 56 ++++---- deps/undici/src/package.json | 2 +- deps/undici/src/types/pool.d.ts | 2 + deps/undici/src/types/proxy-agent.d.ts | 1 + deps/undici/undici.js | 134 +++++++++++++++--- src/undici_version.h | 2 +- 16 files changed, 354 insertions(+), 78 deletions(-) diff --git a/deps/undici/src/.gitignore b/deps/undici/src/.gitignore index 7cba7df889f509..8a85634911ad27 100644 --- a/deps/undici/src/.gitignore +++ b/deps/undici/src/.gitignore @@ -63,6 +63,7 @@ typings/ # lock files package-lock.json yarn.lock +pnpm-lock.yaml # IDE files .idea diff --git a/deps/undici/src/docs/docs/api/CacheStore.md b/deps/undici/src/docs/docs/api/CacheStore.md index 7cd19e08786730..0f3b3eebc7f06f 100644 --- a/deps/undici/src/docs/docs/api/CacheStore.md +++ b/deps/undici/src/docs/docs/api/CacheStore.md @@ -13,8 +13,28 @@ The `MemoryCacheStore` stores the responses in-memory. **Options** +- `maxSize` - The maximum total size in bytes of all stored responses. Default `Infinity`. - `maxCount` - The maximum amount of responses to store. Default `Infinity`. -- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached. +- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached. Default `Infinity`. + +### Getters + +#### `MemoryCacheStore.size` + +Returns the current total size in bytes of all stored responses. + +### Methods + +#### `MemoryCacheStore.isFull()` + +Returns a boolean indicating whether the cache has reached its maximum size or count. + +### Events + +#### `'maxSizeExceeded'` + +Emitted when the cache exceeds its maximum size or count limits. The event payload contains `size`, `maxSize`, `count`, and `maxCount` properties. + ### `SqliteCacheStore` @@ -26,7 +46,7 @@ The `SqliteCacheStore` is only exposed if the `node:sqlite` api is present. - `location` - The location of the SQLite database to use. Default `:memory:`. - `maxCount` - The maximum number of entries to store in the database. Default `Infinity`. -- `maxEntrySize` - The maximum size in bytes that a resposne's body can be. If a response's body is greater than or equal to this, the response will not be cached. Default `Infinity`. +- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached. Default `Infinity`. ## Defining a Custom Cache Store diff --git a/deps/undici/src/docs/docs/api/Pool.md b/deps/undici/src/docs/docs/api/Pool.md index 9c4328240af292..ee0a0d3fe81aca 100644 --- a/deps/undici/src/docs/docs/api/Pool.md +++ b/deps/undici/src/docs/docs/api/Pool.md @@ -19,6 +19,7 @@ Extends: [`ClientOptions`](/docs/docs/api/Client.md#parameter-clientoptions) * **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Client(origin, opts)` * **connections** `number | null` (optional) - Default: `null` - The number of `Client` instances to create. When set to `null`, the `Pool` instance will create an unlimited amount of `Client` instances. +* **clientTtl** `number | null` (optional) - Default: `null` - The amount of time before a `Client` instance is removed from the `Pool` and closed. When set to `null`, `Client` instances will not be removed or closed based on age. ## Instance Properties diff --git a/deps/undici/src/docs/docs/api/ProxyAgent.md b/deps/undici/src/docs/docs/api/ProxyAgent.md index 932716ae7957b9..e0581a4de5164c 100644 --- a/deps/undici/src/docs/docs/api/ProxyAgent.md +++ b/deps/undici/src/docs/docs/api/ProxyAgent.md @@ -25,6 +25,7 @@ For detailed information on the parsing process and potential validation errors, * **clientFactory** `(origin: URL, opts: Object) => Dispatcher` (optional) - Default: `(origin, opts) => new Pool(origin, opts)` * **requestTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the request. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions). * **proxyTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the proxy server. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions). +* **proxyTunnel** `boolean` (optional) - By default, ProxyAgent will request that the Proxy facilitate a tunnel between the endpoint and the agent. Setting `proxyTunnel` to false avoids issuing a CONNECT extension, and includes the endpoint domain and path in each request. Examples: diff --git a/deps/undici/src/lib/cache/memory-cache-store.js b/deps/undici/src/lib/cache/memory-cache-store.js index 4640092b5f54e4..2fb3dfabd19eb8 100644 --- a/deps/undici/src/lib/cache/memory-cache-store.js +++ b/deps/undici/src/lib/cache/memory-cache-store.js @@ -1,6 +1,7 @@ 'use strict' const { Writable } = require('node:stream') +const { EventEmitter } = require('node:events') const { assertCacheKey, assertCacheValue } = require('../util/cache.js') /** @@ -12,8 +13,9 @@ const { assertCacheKey, assertCacheValue } = require('../util/cache.js') /** * @implements {CacheStore} + * @extends {EventEmitter} */ -class MemoryCacheStore { +class MemoryCacheStore extends EventEmitter { #maxCount = Infinity #maxSize = Infinity #maxEntrySize = Infinity @@ -21,11 +23,13 @@ class MemoryCacheStore { #size = 0 #count = 0 #entries = new Map() + #hasEmittedMaxSizeEvent = false /** * @param {import('../../types/cache-interceptor.d.ts').default.MemoryCacheStoreOpts | undefined} [opts] */ constructor (opts) { + super() if (opts) { if (typeof opts !== 'object') { throw new TypeError('MemoryCacheStore options must be an object') @@ -66,6 +70,22 @@ class MemoryCacheStore { } } + /** + * Get the current size of the cache in bytes + * @returns {number} The current size of the cache in bytes + */ + get size () { + return this.#size + } + + /** + * Check if the cache is full (either max size or max count reached) + * @returns {boolean} True if the cache is full, false otherwise + */ + isFull () { + return this.#size >= this.#maxSize || this.#count >= this.#maxCount + } + /** * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req * @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined} @@ -144,7 +164,20 @@ class MemoryCacheStore { store.#size += entry.size + // Check if cache is full and emit event if needed if (store.#size > store.#maxSize || store.#count > store.#maxCount) { + // Emit maxSizeExceeded event if we haven't already + if (!store.#hasEmittedMaxSizeEvent) { + store.emit('maxSizeExceeded', { + size: store.#size, + maxSize: store.#maxSize, + count: store.#count, + maxCount: store.#maxCount + }) + store.#hasEmittedMaxSizeEvent = true + } + + // Perform eviction for (const [key, entries] of store.#entries) { for (const entry of entries.splice(0, entries.length / 2)) { store.#size -= entry.size @@ -154,6 +187,11 @@ class MemoryCacheStore { store.#entries.delete(key) } } + + // Reset the event flag after eviction + if (store.#size < store.#maxSize && store.#count < store.#maxCount) { + store.#hasEmittedMaxSizeEvent = false + } } callback(null) diff --git a/deps/undici/src/lib/dispatcher/agent.js b/deps/undici/src/lib/dispatcher/agent.js index 938333b63a4f00..7c413701a6d683 100644 --- a/deps/undici/src/lib/dispatcher/agent.js +++ b/deps/undici/src/lib/dispatcher/agent.js @@ -45,22 +45,35 @@ class Agent extends DispatcherBase { } this[kOnConnect] = (origin, targets) => { + const result = this[kClients].get(origin) + if (result) { + result.count += 1 + } this.emit('connect', origin, [this, ...targets]) } this[kOnDisconnect] = (origin, targets, err) => { + const result = this[kClients].get(origin) + if (result) { + result.count -= 1 + if (result.count <= 0) { + this[kClients].delete(origin) + result.dispatcher.destroy() + } + } this.emit('disconnect', origin, [this, ...targets], err) } this[kOnConnectionError] = (origin, targets, err) => { + // TODO: should this decrement result.count here? this.emit('connectionError', origin, [this, ...targets], err) } } get [kRunning] () { let ret = 0 - for (const client of this[kClients].values()) { - ret += client[kRunning] + for (const { dispatcher } of this[kClients].values()) { + ret += dispatcher[kRunning] } return ret } @@ -73,8 +86,8 @@ class Agent extends DispatcherBase { throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.') } - let dispatcher = this[kClients].get(key) - + const result = this[kClients].get(key) + let dispatcher = result && result.dispatcher if (!dispatcher) { dispatcher = this[kFactory](opts.origin, this[kOptions]) .on('drain', this[kOnDrain]) @@ -82,10 +95,7 @@ class Agent extends DispatcherBase { .on('disconnect', this[kOnDisconnect]) .on('connectionError', this[kOnConnectionError]) - // This introduces a tiny memory leak, as dispatchers are never removed from the map. - // TODO(mcollina): remove te timer when the client/pool do not have any more - // active connections. - this[kClients].set(key, dispatcher) + this[kClients].set(key, { count: 0, dispatcher }) } return dispatcher.dispatch(opts, handler) @@ -93,8 +103,8 @@ class Agent extends DispatcherBase { async [kClose] () { const closePromises = [] - for (const client of this[kClients].values()) { - closePromises.push(client.close()) + for (const { dispatcher } of this[kClients].values()) { + closePromises.push(dispatcher.close()) } this[kClients].clear() @@ -103,8 +113,8 @@ class Agent extends DispatcherBase { async [kDestroy] (err) { const destroyPromises = [] - for (const client of this[kClients].values()) { - destroyPromises.push(client.destroy(err)) + for (const { dispatcher } of this[kClients].values()) { + destroyPromises.push(dispatcher.destroy(err)) } this[kClients].clear() @@ -113,9 +123,9 @@ class Agent extends DispatcherBase { get stats () { const allClientStats = {} - for (const client of this[kClients].values()) { - if (client.stats) { - allClientStats[client[kUrl].origin] = client.stats + for (const { dispatcher } of this[kClients].values()) { + if (dispatcher.stats) { + allClientStats[dispatcher[kUrl].origin] = dispatcher.stats } } return allClientStats diff --git a/deps/undici/src/lib/dispatcher/pool.js b/deps/undici/src/lib/dispatcher/pool.js index 14175cf4a184e2..00cf50c3012b29 100644 --- a/deps/undici/src/lib/dispatcher/pool.js +++ b/deps/undici/src/lib/dispatcher/pool.js @@ -5,7 +5,8 @@ const { kClients, kNeedDrain, kAddClient, - kGetDispatcher + kGetDispatcher, + kRemoveClient } = require('./pool-base') const Client = require('./client') const { @@ -35,6 +36,7 @@ class Pool extends PoolBase { autoSelectFamily, autoSelectFamilyAttemptTimeout, allowH2, + clientTtl, ...options } = {}) { if (connections != null && (!Number.isFinite(connections) || connections < 0)) { @@ -65,12 +67,20 @@ class Pool extends PoolBase { this[kConnections] = connections || null this[kUrl] = util.parseOrigin(origin) - this[kOptions] = { ...util.deepClone(options), connect, allowH2 } + this[kOptions] = { ...util.deepClone(options), connect, allowH2, clientTtl } this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : undefined this[kFactory] = factory + this.on('connect', (origin, targets) => { + if (clientTtl != null && clientTtl > 0) { + for (const target of targets) { + Object.assign(target, { ttl: Date.now() }) + } + } + }) + this.on('connectionError', (origin, targets, error) => { // If a connection error occurs, we remove the client from the pool, // and emit a connectionError event. They will not be re-used. @@ -87,8 +97,12 @@ class Pool extends PoolBase { } [kGetDispatcher] () { + const clientTtlOption = this[kOptions].clientTtl for (const client of this[kClients]) { - if (!client[kNeedDrain]) { + // check ttl of client and if it's stale, remove it from the pool + if (clientTtlOption != null && clientTtlOption > 0 && client.ttl && ((Date.now() - client.ttl) > clientTtlOption)) { + this[kRemoveClient](client) + } else if (!client[kNeedDrain]) { return client } } diff --git a/deps/undici/src/lib/dispatcher/proxy-agent.js b/deps/undici/src/lib/dispatcher/proxy-agent.js index c5b4d51babb449..3f656f7eb564fb 100644 --- a/deps/undici/src/lib/dispatcher/proxy-agent.js +++ b/deps/undici/src/lib/dispatcher/proxy-agent.js @@ -1,12 +1,13 @@ 'use strict' -const { kProxy, kClose, kDestroy } = require('../core/symbols') +const { kProxy, kClose, kDestroy, kDispatch, kConnector } = require('../core/symbols') const { URL } = require('node:url') const Agent = require('./agent') const Pool = require('./pool') const DispatcherBase = require('./dispatcher-base') const { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require('../core/errors') const buildConnector = require('../core/connect') +const Client = require('./client') const kAgent = Symbol('proxy agent') const kClient = Symbol('proxy client') @@ -14,6 +15,7 @@ const kProxyHeaders = Symbol('proxy headers') const kRequestTls = Symbol('request tls settings') const kProxyTls = Symbol('proxy tls settings') const kConnectEndpoint = Symbol('connect endpoint function') +const kTunnelProxy = Symbol('tunnel proxy') function defaultProtocolPort (protocol) { return protocol === 'https:' ? 443 : 80 @@ -25,6 +27,61 @@ function defaultFactory (origin, opts) { const noop = () => {} +class ProxyClient extends DispatcherBase { + #client = null + constructor (origin, opts) { + if (typeof origin === 'string') { + origin = new URL(origin) + } + + if (origin.protocol !== 'http:' && origin.protocol !== 'https:') { + throw new InvalidArgumentError('ProxyClient only supports http and https protocols') + } + + super() + + this.#client = new Client(origin, opts) + } + + async [kClose] () { + await this.#client.close() + } + + async [kDestroy] () { + await this.#client.destroy() + } + + async [kDispatch] (opts, handler) { + const { method, origin } = opts + if (method === 'CONNECT') { + this.#client[kConnector]({ + origin, + port: opts.port || defaultProtocolPort(opts.protocol), + path: opts.host, + signal: opts.signal, + headers: { + ...this[kProxyHeaders], + host: opts.host + }, + servername: this[kProxyTls]?.servername || opts.servername + }, + (err, socket) => { + if (err) { + handler.callback(err) + } else { + handler.callback(null, { socket, statusCode: 200 }) + } + } + ) + return + } + if (typeof origin === 'string') { + opts.origin = new URL(origin) + } + + return this.#client.dispatch(opts, handler) + } +} class ProxyAgent extends DispatcherBase { constructor (opts) { if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) { @@ -36,6 +93,8 @@ class ProxyAgent extends DispatcherBase { throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.') } + const { proxyTunnel = true } = opts + super() const url = this.#getUrl(opts) @@ -57,9 +116,19 @@ class ProxyAgent extends DispatcherBase { this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}` } + const factory = (!proxyTunnel && protocol === 'http:') + ? (origin, options) => { + if (origin.protocol === 'http:') { + return new ProxyClient(origin, options) + } + return new Client(origin, options) + } + : undefined + const connect = buildConnector({ ...opts.proxyTls }) this[kConnectEndpoint] = buildConnector({ ...opts.requestTls }) - this[kClient] = clientFactory(url, { connect }) + this[kClient] = clientFactory(url, { connect, factory }) + this[kTunnelProxy] = proxyTunnel this[kAgent] = new Agent({ ...opts, connect: async (opts, callback) => { @@ -115,6 +184,10 @@ class ProxyAgent extends DispatcherBase { headers.host = host } + if (!this.#shouldConnect(new URL(opts.origin))) { + opts.path = opts.origin + opts.path + } + return this[kAgent].dispatch( { ...opts, @@ -147,6 +220,19 @@ class ProxyAgent extends DispatcherBase { await this[kAgent].destroy() await this[kClient].destroy() } + + #shouldConnect (uri) { + if (typeof uri === 'string') { + uri = new URL(uri) + } + if (this[kTunnelProxy]) { + return true + } + if (uri.protocol !== 'http:' || this[kProxy].protocol !== 'http:') { + return true + } + return false + } } /** diff --git a/deps/undici/src/lib/llhttp/wasm_build_env.txt b/deps/undici/src/lib/llhttp/wasm_build_env.txt index 3bbfdb97d1549e..1b4a3a6f8bd0fa 100644 --- a/deps/undici/src/lib/llhttp/wasm_build_env.txt +++ b/deps/undici/src/lib/llhttp/wasm_build_env.txt @@ -1,5 +1,5 @@ -> undici@7.9.0 build:wasm +> undici@7.10.0 build:wasm > node build/wasm.js --docker > docker run --rm --platform=linux/x86_64 --user 1001:118 --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/lib/llhttp,target=/home/node/build/lib/llhttp --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/build,target=/home/node/build/build --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/deps,target=/home/node/build/deps -t ghcr.io/nodejs/wasm-builder@sha256:975f391d907e42a75b8c72eb77c782181e941608687d4d8694c3e9df415a0970 node build/wasm.js diff --git a/deps/undici/src/lib/mock/mock-agent.js b/deps/undici/src/lib/mock/mock-agent.js index 53c041002c8bc3..19bb01c266188f 100644 --- a/deps/undici/src/lib/mock/mock-agent.js +++ b/deps/undici/src/lib/mock/mock-agent.js @@ -159,7 +159,7 @@ class MockAgent extends Dispatcher { } [kMockAgentSet] (origin, dispatcher) { - this[kClients].set(origin, dispatcher) + this[kClients].set(origin, { count: 0, dispatcher }) } [kFactory] (origin) { @@ -171,9 +171,9 @@ class MockAgent extends Dispatcher { [kMockAgentGet] (origin) { // First check if we can immediately find it - const client = this[kClients].get(origin) - if (client) { - return client + const result = this[kClients].get(origin) + if (result?.dispatcher) { + return result.dispatcher } // If the origin is not a string create a dummy parent pool and return to user @@ -184,11 +184,11 @@ class MockAgent extends Dispatcher { } // If we match, create a pool and assign the same dispatches - for (const [keyMatcher, nonExplicitDispatcher] of Array.from(this[kClients])) { - if (nonExplicitDispatcher && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) { + for (const [keyMatcher, result] of Array.from(this[kClients])) { + if (result && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) { const dispatcher = this[kFactory](origin) this[kMockAgentSet](origin, dispatcher) - dispatcher[kDispatches] = nonExplicitDispatcher[kDispatches] + dispatcher[kDispatches] = result.dispatcher[kDispatches] return dispatcher } } @@ -202,7 +202,7 @@ class MockAgent extends Dispatcher { const mockAgentClients = this[kClients] return Array.from(mockAgentClients.entries()) - .flatMap(([origin, scope]) => scope[kDispatches].map(dispatch => ({ ...dispatch, origin }))) + .flatMap(([origin, result]) => result.dispatcher[kDispatches].map(dispatch => ({ ...dispatch, origin }))) .filter(({ pending }) => pending) } diff --git a/deps/undici/src/package-lock.json b/deps/undici/src/package-lock.json index cab6918acd08e4..5b2f2ef8a1cee7 100644 --- a/deps/undici/src/package-lock.json +++ b/deps/undici/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "undici", - "version": "7.9.0", + "version": "7.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "undici", - "version": "7.9.0", + "version": "7.10.0", "license": "MIT", "devDependencies": { "@fastify/busboy": "3.1.1", @@ -2159,9 +2159,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.100", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.100.tgz", - "integrity": "sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA==", + "version": "18.19.103", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.103.tgz", + "integrity": "sha512-hHTHp+sEz6SxFsp+SA+Tqrua3AbmlAw+Y//aEwdHrdZkYVRWdvWD3y5uPZ0flYOkgskaFWqZ/YGFm3FaFQ0pRw==", "dev": true, "license": "MIT", "dependencies": { @@ -4059,9 +4059,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.155", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", - "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", + "version": "1.5.157", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz", + "integrity": "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==", "dev": true, "license": "ISC" }, @@ -4110,9 +4110,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "version": "1.23.10", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.10.tgz", + "integrity": "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==", "dev": true, "license": "MIT", "dependencies": { @@ -4120,18 +4120,18 @@ "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -4147,13 +4147,13 @@ "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", + "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", + "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", @@ -4166,7 +4166,7 @@ "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -4585,9 +4585,9 @@ } }, "node_modules/eslint-plugin-import-x": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.11.1.tgz", - "integrity": "sha512-CiqREASJRnhwCB0NujkTdo4jU+cJAnhQrd4aCnWC1o+rYWIWakVbyuzVbnCriUUSLAnn5CoJ2ob36TEgNzejBQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.12.2.tgz", + "integrity": "sha512-0jVUgJQipbs0yUfLe7LwYD6p8rIGqCysWZdyJFgkPzDyJgiKpuCaXlywKUAWgJ6u1nLpfrdt21B60OUkupyBrQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5413,9 +5413,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9555,9 +9555,9 @@ } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", "dev": true, "license": "MIT", "engines": { diff --git a/deps/undici/src/package.json b/deps/undici/src/package.json index 5a548d4151fe04..ca21dfd50ccefb 100644 --- a/deps/undici/src/package.json +++ b/deps/undici/src/package.json @@ -1,6 +1,6 @@ { "name": "undici", - "version": "7.9.0", + "version": "7.10.0", "description": "An HTTP/1.1 client, written from scratch for Node.js", "homepage": "https://undici.nodejs.org", "bugs": { diff --git a/deps/undici/src/types/pool.d.ts b/deps/undici/src/types/pool.d.ts index 4814606a7027c5..5198476eb9c78f 100644 --- a/deps/undici/src/types/pool.d.ts +++ b/deps/undici/src/types/pool.d.ts @@ -33,6 +33,8 @@ declare namespace Pool { factory?(origin: URL, opts: object): Dispatcher; /** The max number of clients to create. `null` if no limit. Default `null`. */ connections?: number | null; + /** The amount of time before a client is removed from the pool and closed. `null` if no time limit. Default `null` */ + clientTtl?: number | null; interceptors?: { Pool?: readonly Dispatcher.DispatchInterceptor[] } & Client.Options['interceptors'] } diff --git a/deps/undici/src/types/proxy-agent.d.ts b/deps/undici/src/types/proxy-agent.d.ts index 7d39f971c903a6..41555422178b57 100644 --- a/deps/undici/src/types/proxy-agent.d.ts +++ b/deps/undici/src/types/proxy-agent.d.ts @@ -24,5 +24,6 @@ declare namespace ProxyAgent { requestTls?: buildConnector.BuildOptions; proxyTls?: buildConnector.BuildOptions; clientFactory?(origin: URL, opts: object): Dispatcher; + proxyTunnel?: boolean; } } diff --git a/deps/undici/undici.js b/deps/undici/undici.js index ca51462dce9156..8905ca01ae25ad 100644 --- a/deps/undici/undici.js +++ b/deps/undici/undici.js @@ -8570,7 +8570,8 @@ var require_pool = __commonJS({ kClients, kNeedDrain, kAddClient, - kGetDispatcher + kGetDispatcher, + kRemoveClient } = require_pool_base(); var Client = require_client(); var { @@ -8601,6 +8602,7 @@ var require_pool = __commonJS({ autoSelectFamily, autoSelectFamilyAttemptTimeout, allowH2, + clientTtl, ...options } = {}) { if (connections != null && (!Number.isFinite(connections) || connections < 0)) { @@ -8626,9 +8628,16 @@ var require_pool = __commonJS({ } this[kConnections] = connections || null; this[kUrl] = util.parseOrigin(origin); - this[kOptions] = { ...util.deepClone(options), connect, allowH2 }; + this[kOptions] = { ...util.deepClone(options), connect, allowH2, clientTtl }; this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0; this[kFactory] = factory; + this.on("connect", (origin2, targets) => { + if (clientTtl != null && clientTtl > 0) { + for (const target of targets) { + Object.assign(target, { ttl: Date.now() }); + } + } + }); this.on("connectionError", (origin2, targets, error) => { for (const target of targets) { const idx = this[kClients].indexOf(target); @@ -8639,8 +8648,11 @@ var require_pool = __commonJS({ }); } [kGetDispatcher]() { + const clientTtlOption = this[kOptions].clientTtl; for (const client of this[kClients]) { - if (!client[kNeedDrain]) { + if (clientTtlOption != null && clientTtlOption > 0 && client.ttl && Date.now() - client.ttl > clientTtlOption) { + this[kRemoveClient](client); + } else if (!client[kNeedDrain]) { return client; } } @@ -8697,9 +8709,21 @@ var require_agent = __commonJS({ this.emit("drain", origin, [this, ...targets]); }; this[kOnConnect] = (origin, targets) => { + const result = this[kClients].get(origin); + if (result) { + result.count += 1; + } this.emit("connect", origin, [this, ...targets]); }; this[kOnDisconnect] = (origin, targets, err) => { + const result = this[kClients].get(origin); + if (result) { + result.count -= 1; + if (result.count <= 0) { + this[kClients].delete(origin); + result.dispatcher.destroy(); + } + } this.emit("disconnect", origin, [this, ...targets], err); }; this[kOnConnectionError] = (origin, targets, err) => { @@ -8708,8 +8732,8 @@ var require_agent = __commonJS({ } get [kRunning]() { let ret = 0; - for (const client of this[kClients].values()) { - ret += client[kRunning]; + for (const { dispatcher } of this[kClients].values()) { + ret += dispatcher[kRunning]; } return ret; } @@ -8720,34 +8744,35 @@ var require_agent = __commonJS({ } else { throw new InvalidArgumentError("opts.origin must be a non-empty string or URL."); } - let dispatcher = this[kClients].get(key); + const result = this[kClients].get(key); + let dispatcher = result && result.dispatcher; if (!dispatcher) { dispatcher = this[kFactory](opts.origin, this[kOptions]).on("drain", this[kOnDrain]).on("connect", this[kOnConnect]).on("disconnect", this[kOnDisconnect]).on("connectionError", this[kOnConnectionError]); - this[kClients].set(key, dispatcher); + this[kClients].set(key, { count: 0, dispatcher }); } return dispatcher.dispatch(opts, handler); } async [kClose]() { const closePromises = []; - for (const client of this[kClients].values()) { - closePromises.push(client.close()); + for (const { dispatcher } of this[kClients].values()) { + closePromises.push(dispatcher.close()); } this[kClients].clear(); await Promise.all(closePromises); } async [kDestroy](err) { const destroyPromises = []; - for (const client of this[kClients].values()) { - destroyPromises.push(client.destroy(err)); + for (const { dispatcher } of this[kClients].values()) { + destroyPromises.push(dispatcher.destroy(err)); } this[kClients].clear(); await Promise.all(destroyPromises); } get stats() { const allClientStats = {}; - for (const client of this[kClients].values()) { - if (client.stats) { - allClientStats[client[kUrl].origin] = client.stats; + for (const { dispatcher } of this[kClients].values()) { + if (dispatcher.stats) { + allClientStats[dispatcher[kUrl].origin] = dispatcher.stats; } } return allClientStats; @@ -8794,19 +8819,21 @@ var require_global2 = __commonJS({ var require_proxy_agent = __commonJS({ "lib/dispatcher/proxy-agent.js"(exports2, module2) { "use strict"; - var { kProxy, kClose, kDestroy } = require_symbols(); + var { kProxy, kClose, kDestroy, kDispatch, kConnector } = require_symbols(); var { URL: URL2 } = require("node:url"); var Agent = require_agent(); var Pool = require_pool(); var DispatcherBase = require_dispatcher_base(); var { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require_errors(); var buildConnector = require_connect(); + var Client = require_client(); var kAgent = Symbol("proxy agent"); var kClient = Symbol("proxy client"); var kProxyHeaders = Symbol("proxy headers"); var kRequestTls = Symbol("request tls settings"); var kProxyTls = Symbol("proxy tls settings"); var kConnectEndpoint = Symbol("connect endpoint function"); + var kTunnelProxy = Symbol("tunnel proxy"); function defaultProtocolPort(protocol) { return protocol === "https:" ? 443 : 80; } @@ -8817,6 +8844,58 @@ var require_proxy_agent = __commonJS({ __name(defaultFactory, "defaultFactory"); var noop = /* @__PURE__ */ __name(() => { }, "noop"); + var ProxyClient = class extends DispatcherBase { + static { + __name(this, "ProxyClient"); + } + #client = null; + constructor(origin, opts) { + if (typeof origin === "string") { + origin = new URL2(origin); + } + if (origin.protocol !== "http:" && origin.protocol !== "https:") { + throw new InvalidArgumentError("ProxyClient only supports http and https protocols"); + } + super(); + this.#client = new Client(origin, opts); + } + async [kClose]() { + await this.#client.close(); + } + async [kDestroy]() { + await this.#client.destroy(); + } + async [kDispatch](opts, handler) { + const { method, origin } = opts; + if (method === "CONNECT") { + this.#client[kConnector]( + { + origin, + port: opts.port || defaultProtocolPort(opts.protocol), + path: opts.host, + signal: opts.signal, + headers: { + ...this[kProxyHeaders], + host: opts.host + }, + servername: this[kProxyTls]?.servername || opts.servername + }, + (err, socket) => { + if (err) { + handler.callback(err); + } else { + handler.callback(null, { socket, statusCode: 200 }); + } + } + ); + return; + } + if (typeof origin === "string") { + opts.origin = new URL2(origin); + } + return this.#client.dispatch(opts, handler); + } + }; var ProxyAgent = class extends DispatcherBase { static { __name(this, "ProxyAgent"); @@ -8829,6 +8908,7 @@ var require_proxy_agent = __commonJS({ if (typeof clientFactory !== "function") { throw new InvalidArgumentError("Proxy opts.clientFactory must be a function."); } + const { proxyTunnel = true } = opts; super(); const url = this.#getUrl(opts); const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url; @@ -8845,9 +8925,16 @@ var require_proxy_agent = __commonJS({ } else if (username && password) { this[kProxyHeaders]["proxy-authorization"] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString("base64")}`; } + const factory = !proxyTunnel && protocol === "http:" ? (origin2, options) => { + if (origin2.protocol === "http:") { + return new ProxyClient(origin2, options); + } + return new Client(origin2, options); + } : void 0; const connect = buildConnector({ ...opts.proxyTls }); this[kConnectEndpoint] = buildConnector({ ...opts.requestTls }); - this[kClient] = clientFactory(url, { connect }); + this[kClient] = clientFactory(url, { connect, factory }); + this[kTunnelProxy] = proxyTunnel; this[kAgent] = new Agent({ ...opts, connect: /* @__PURE__ */ __name(async (opts2, callback) => { @@ -8899,6 +8986,9 @@ var require_proxy_agent = __commonJS({ const { host } = new URL2(opts.origin); headers.host = host; } + if (!this.#shouldConnect(new URL2(opts.origin))) { + opts.path = opts.origin + opts.path; + } return this[kAgent].dispatch( { ...opts, @@ -8928,6 +9018,18 @@ var require_proxy_agent = __commonJS({ await this[kAgent].destroy(); await this[kClient].destroy(); } + #shouldConnect(uri) { + if (typeof uri === "string") { + uri = new URL2(uri); + } + if (this[kTunnelProxy]) { + return true; + } + if (uri.protocol !== "http:" || this[kProxy].protocol !== "http:") { + return true; + } + return false; + } }; function buildHeaders(headers) { if (Array.isArray(headers)) { diff --git a/src/undici_version.h b/src/undici_version.h index 1830066ea65c71..0cba4a2b7d7842 100644 --- a/src/undici_version.h +++ b/src/undici_version.h @@ -2,5 +2,5 @@ // Refer to tools/dep_updaters/update-undici.sh #ifndef SRC_UNDICI_VERSION_H_ #define SRC_UNDICI_VERSION_H_ -#define UNDICI_VERSION "7.9.0" +#define UNDICI_VERSION "7.10.0" #endif // SRC_UNDICI_VERSION_H_