Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions deps/undici/src/docs/docs/api/Client.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ added: v1.0.0
WebSocket messages. Applied to uncompressed messages, compressed frame
payloads, and decompressed (`permessage-deflate`) messages. Set to `0` to
disable the limit. **Default:** `134217728`.
* `eventSource` {Object} (optional) EventSource-specific configuration.
* `maxEventSize` {number} The maximum allowed event size, in bytes, for
EventSource messages. Set to `0` to disable the limit.
**Default:** `buffer.kStringMaxLength`.
* Returns: {Client}

Instantiating a `Client` does not open a connection; the connection is
Expand Down
5 changes: 3 additions & 2 deletions deps/undici/src/docs/docs/api/EnvHttpProxyAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ it is used only for HTTPS requests.
proxied. Each entry may include a leading dot or `*.` wildcard (for example
`.example.com`) to match subdomains, and an optional `:port` suffix to restrict
the match to a specific port. A request bypasses the proxy when its host equals
an entry or is a subdomain of one. Setting `no_proxy` to `*` bypasses the proxy
for every request.
an entry or is a subdomain of one. A trailing dot is ignored on both sides, so
`example.com.` and `example.com` match each other. Setting `no_proxy` to `*`
bypasses the proxy for every request.

The uppercase variants `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` are also
honored. When both the lowercase and uppercase forms of a variable are set, the
Expand Down
4 changes: 4 additions & 0 deletions deps/undici/src/docs/docs/api/EventSource.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ added: v6.5.0
wait before re-establishing a dropped connection. The server may override
this value with a `retry` field. **Default:** `3000`.

EventSource-specific limits can be configured on the dispatcher using the
`eventSource` option. See [`Client`][] for details.

Creates a new `EventSource` and immediately begins connecting to `url`. The
request is sent with the `Accept: text/event-stream` header, a cache mode of
`no-store`, and an initiator type of `other`.
Expand Down Expand Up @@ -349,6 +352,7 @@ eventSource.onerror = () => {
```

[WHATWG-conformant]: https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
[`Client`]: Client.md#new-clienturl-options
[`Dispatcher`]: Dispatcher.md#class-dispatcher
[`addEventListener()`]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
[server-sent events]: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
10 changes: 7 additions & 3 deletions deps/undici/src/docs/docs/api/Interceptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ body yourself.

* `opts` {Object} (optional)
* `maxSize` {number} Maximum number of bytes to read and discard. Responses
whose `Content-Length` exceeds this value are aborted. **Default:**
`1_048_576` (1 MiB).
whose declared or received body size exceeds this value are aborted.
**Default:** `1_048_576` (1 MiB).

Per-request override: set `dumpMaxSize` on the dispatch options to override
the global `maxSize` for a specific request.
Expand Down Expand Up @@ -210,6 +210,9 @@ Automatically decompresses response bodies encoded with `gzip`, `x-gzip`,
skipped. **Default:** `[204, 304]`.
* `skipErrorResponses` {boolean} When `true`, responses with a status code
>= 400 are not decompressed. **Default:** `true`.
* `maxSize` {number} Maximum decompressed response size in bytes. The request
fails with a `ResponseExceededMaxSizeError` if the decoded body exceeds
this limit. **Default:** `67108864` (64 MiB).

**Returns:** {Dispatcher.DispatcherComposeInterceptor}

Expand All @@ -221,7 +224,8 @@ import { Agent, interceptors } from 'undici'
const agent = new Agent().compose(
interceptors.decompress({
skipStatusCodes: [204, 304],
skipErrorResponses: false // decompress error bodies too
skipErrorResponses: false, // decompress error bodies too
maxSize: 16 * 1024 * 1024 // limit decoded bodies to 16 MiB
})
)
```
Expand Down
8 changes: 7 additions & 1 deletion deps/undici/src/docs/docs/api/Socks5ProxyAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ added: v7.23.0
a password embedded in `proxyUrl`. **Default:** the URL password, if any.
* `connect` {Function} Custom connector used to open the socket to the proxy.
**Default:** a connector built from `proxyTls`.
* `connectTimeout` {number} Maximum time in milliseconds for each proxy
connection, SOCKS5 negotiation, and target TLS negotiation stage. A value of
`0` disables these stage timeouts. `proxyTls.timeout` and
`requestTls.timeout` override it for their respective TLS stages.
**Default:** `5000`.
* `proxyTls` {BuildOptions} TLS options for the connection to the proxy itself
(SOCKS5 over TLS). When set, the proxy connection is established over TLS and
`servername` defaults to the proxy host name.
Expand All @@ -69,7 +74,8 @@ added: v7.23.0
host name.

Throws an `InvalidArgumentError` if `proxyUrl` is missing or does not use the
`socks5:` or `socks:` protocol.
`socks5:` or `socks:` protocol, or if `connectTimeout`, `proxyTls.timeout`, or
`requestTls.timeout` is not a finite, non-negative number.

```mjs
import { Socks5ProxyAgent } from 'undici'
Expand Down
4 changes: 2 additions & 2 deletions deps/undici/src/lib/cache/memory-cache-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class MemoryCacheStore extends EventEmitter {
}

/**
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
* @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined}
*/
get (key) {
Expand Down Expand Up @@ -179,7 +179,7 @@ class MemoryCacheStore extends EventEmitter {

// Perform eviction
for (const [key, entries] of store.#entries) {
for (const entry of entries.splice(0, entries.length / 2)) {
for (const entry of entries.splice(0, Math.ceil(entries.length / 2))) {
store.#size -= entry.size
store.#count -= 1
}
Expand Down
2 changes: 2 additions & 0 deletions deps/undici/src/lib/core/symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module.exports = {
kDestroy: Symbol('destroy'),
kDispatch: Symbol('dispatch'),
kUrl: Symbol('url'),
kRequestOrigin: Symbol('request origin'),
kOriginless: Symbol('originless'),
kWriting: Symbol('writing'),
kResuming: Symbol('resuming'),
kQueue: Symbol('queue'),
Expand Down
9 changes: 7 additions & 2 deletions deps/undici/src/lib/dispatcher/agent.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

const { InvalidArgumentError, MaxOriginsReachedError } = require('../core/errors')
const { kBusy, kClients, kConnected, kRunning, kClose, kDestroy, kDispatch, kUrl } = require('../core/symbols')
const { kBusy, kClients, kConnected, kRunning, kPending, kClose, kDestroy, kDispatch, kUrl } = require('../core/symbols')
const DispatcherBase = require('./dispatcher-base')
const Pool = require('./pool')
const Client = require('./client')
Expand Down Expand Up @@ -97,7 +97,12 @@ class Agent extends DispatcherBase {
return
}

if (dispatcher[kConnected] > 0 || dispatcher[kBusy]) {
// A GOAWAY detaches the HTTP/2 session before requeued requests are
// dispatched on a replacement connection. At that point the pool has
// no connected clients and is not busy, but it still has pending work.
// Closing it here lets the replacement Client finish those requests
// and then destroys that new connection with ClientDestroyedError.
if (dispatcher[kConnected] > 0 || dispatcher[kBusy] || dispatcher[kPending] > 0) {
return
}

Expand Down
11 changes: 7 additions & 4 deletions deps/undici/src/lib/dispatcher/balanced-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const {
kGetDispatcher
} = require('./pool-base')
const Pool = require('./pool')
const { kUrl } = require('../core/symbols')
const { kOriginless, kUrl } = require('../core/symbols')
const util = require('../core/util')
const kFactory = Symbol('factory')

Expand Down Expand Up @@ -49,14 +49,17 @@ function defaultFactory (origin, opts) {
}

class BalancedPool extends PoolBase {
constructor (upstreams = [], { factory = defaultFactory, ...opts } = {}) {
constructor (upstreams = [], { factory = defaultFactory, connect, tls, ...opts } = {}) {
if (typeof factory !== 'function') {
throw new InvalidArgumentError('factory must be a function.')
}

super()
super(opts)

this[kOptions] = { ...util.deepClone(opts) }
this[kOriginless] = true
if (connect && typeof connect !== 'function') connect = { ...connect }
if (tls && typeof tls !== 'function') tls = { ...tls }
this[kOptions] = { ...util.deepClone(opts), connect, tls }
this[kIndex] = -1
this[kCurrentWeight] = 0

Expand Down
16 changes: 12 additions & 4 deletions deps/undici/src/lib/dispatcher/client-h1.js
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ function onSocketClose () {

function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearTimeout(socket[kIdleSocketValidationTimeout])
clearImmediate(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}

Expand All @@ -1061,15 +1061,23 @@ function clearIdleSocketValidation (socket) {

function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
// Yield to the check phase (after poll) so unsolicited bytes / FIN / RST
// already pending on this idle keep-alive socket are processed before the
// next request is written (GHSA-35p6-xmwp-9g52).
//
// setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse
// (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll
// block for ~500ms when the event loop is otherwise idle (#5600 / #5606).
// A ref'd Immediate both keeps the pending request alive and makes poll
// return immediately — the hybrid those issues asked for.
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2

if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
})
}

/**
Expand Down
46 changes: 43 additions & 3 deletions deps/undici/src/lib/dispatcher/client-h2.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const {
InformationalError,
InvalidArgumentError,
HeadersTimeoutError,
BodyTimeoutError
BodyTimeoutError,
ResponseExceededMaxSizeError
} = require('../core/errors.js')
const {
kUrl,
Expand Down Expand Up @@ -39,7 +40,8 @@ const {
kRemoteSettings,
kHTTP2Stream,
kHTTP2SessionState,
kHTTP2Options
kHTTP2Options,
kMaxResponseSize
} = require('../core/symbols.js')
const { channels } = require('../core/diagnostics.js')

Expand Down Expand Up @@ -1022,9 +1024,11 @@ function writeH2 (client, request) {
const state = {
abort: null,
body: request.body,
bytesRead: 0,
client,
contentLength: null,
expectsPayload: false,
maxResponseSize: client[kMaxResponseSize],
request,
headersTimeout,
bodyTimeout,
Expand Down Expand Up @@ -1260,6 +1264,7 @@ function writeH2 (client, request) {
// become unreachable once the stream closes, so plain `on` avoids the
// per-listener `once` wrapper allocation.
stream.on('response', onResponse)
stream.on('headers', onInterimResponse)
stream.on('end', onEnd)
stream.on('error', onError)
stream.on('frameError', onFrameError)
Expand All @@ -1280,6 +1285,7 @@ function removeRequestStreamListeners (stream) {
stream.off('error', noop)
stream.off('continue', writeBodyH2)
stream.off('response', onResponse)
stream.off('headers', onInterimResponse)
stream.off('end', onEnd)
stream.off('error', onError)
stream.off('frameError', onFrameError)
Expand Down Expand Up @@ -1322,17 +1328,51 @@ function onData (chunk) {
return
}

const { request } = state
const { request, maxResponseSize } = state

if (request.aborted || request.completed) {
return
}

if (maxResponseSize > -1 && state.bytesRead + chunk.length > maxResponseSize) {
// Unlike HTTP/1.1, which destroys the socket because it cannot abandon one
// response without losing framing, resetting the offending stream leaves
// the session usable for its siblings.
state.abort(new ResponseExceededMaxSizeError())
return
}

state.bytesRead += chunk.length

if (request.onResponseData(chunk) === false) {
stream.pause()
}
}

function onInterimResponse (headers) {
const stream = this
const state = stream[kRequestStreamState]

if (state == null) {
return
}

const { request } = state

if (request.aborted || request.completed) {
return
}

// node http2 emits 'headers' for interim (1xx) informational responses,
// while the final response arrives via 'response'. Forward these to the
// handler so that onInfo is invoked, matching the HTTP/1 behaviour and the
// documented onInfo contract.
const statusCode = headers[HTTP2_HEADER_STATUS]
delete headers[HTTP2_HEADER_STATUS]

request.onResponseStart(Number(statusCode), headers, noop, '')
}

function onResponse (headers) {
const stream = this
const state = stream[kRequestStreamState]
Expand Down
5 changes: 3 additions & 2 deletions deps/undici/src/lib/dispatcher/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ class Client extends DispatcherBase {
connectionWindowSize,
pingInterval,
webSocket,
h2Options
h2Options,
eventSource
} = {}) {
if (keepAlive !== undefined) {
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
Expand Down Expand Up @@ -276,7 +277,7 @@ class Client extends DispatcherBase {
}
}

super({ webSocket })
super({ webSocket, eventSource })

if (typeof connect !== 'function') {
connect = buildConnector({
Expand Down
14 changes: 13 additions & 1 deletion deps/undici/src/lib/dispatcher/dispatcher-base.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const buffer = require('node:buffer')
const Dispatcher = require('./dispatcher')
const {
ClientDestroyedError,
Expand All @@ -11,6 +12,7 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch } = require('../core/sy
const kOnDestroyed = Symbol('onDestroyed')
const kOnClosed = Symbol('onClosed')
const kWebSocketOptions = Symbol('webSocketOptions')
const kEventSourceOptions = Symbol('eventSourceOptions')

class DispatcherBase extends Dispatcher {
/** @type {boolean} */
Expand All @@ -31,10 +33,11 @@ class DispatcherBase extends Dispatcher {
constructor (opts) {
super()
this[kWebSocketOptions] = opts?.webSocket ?? {}
this[kEventSourceOptions] = opts?.eventSource ?? {}
}

/**
* @returns {import('../../types/dispatcher').WebSocketOptions}
* @returns {import('../../types/client').Client.WebSocketOptions}
*/
get webSocketOptions () {
return {
Expand All @@ -43,6 +46,15 @@ class DispatcherBase extends Dispatcher {
}
}

/**
* @returns {import('../../types/client').Client.EventSourceOptions}
*/
get eventSourceOptions () {
return {
maxEventSize: this[kEventSourceOptions].maxEventSize ?? buffer.kStringMaxLength
}
}

/** @returns {boolean} */
get destroyed () {
return this[kDestroyed]
Expand Down
Loading
Loading