Skip to content

Commit b7bf7fe

Browse files
deps: update undici to 7.19.0
PR-URL: #61470 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 7b483d3 commit b7bf7fe

File tree

22 files changed

+493
-197
lines changed

22 files changed

+493
-197
lines changed

deps/undici/src/docs/docs/api/Client.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Returns: `Client`
3232
* **allowH2**: `boolean` - Default: `false`. Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
3333
* **useH2c**: `boolean` - Default: `false`. Enforces h2c for non-https connections.
3434
* **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame.
35+
* **initialWindowSize**: `number` (optional) - Default: `262144` (256KB). Sets the HTTP/2 stream-level flow-control window size (SETTINGS_INITIAL_WINDOW_SIZE). Must be a positive integer greater than 0. This default is higher than Node.js core's default (65535 bytes) to improve throughput, Node's choice is very conservative for current high-bandwith networks. See [RFC 7540 Section 6.9.2](https://datatracker.ietf.org/doc/html/rfc7540#section-6.9.2) for more details.
36+
* **connectionWindowSize**: `number` (optional) - Default `524288` (512KB). Sets the HTTP/2 connection-level flow-control window size using `ClientHttp2Session.setLocalWindowSize()`. Must be a positive integer greater than 0. This provides better flow control for the entire connection across multiple streams. See [Node.js HTTP/2 documentation](https://nodejs.org/api/http2.html#clienthttp2sessionsetlocalwindowsize) for more details.
3537

3638
> **Notes about HTTP/2**
3739
> - It only works under TLS connections. h2c is not supported.

deps/undici/src/lib/core/symbols.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ module.exports = {
6262
kListeners: Symbol('listeners'),
6363
kHTTPContext: Symbol('http context'),
6464
kMaxConcurrentStreams: Symbol('max concurrent streams'),
65+
kHTTP2InitialWindowSize: Symbol('http2 initial window size'),
66+
kHTTP2ConnectionWindowSize: Symbol('http2 connection window size'),
6567
kEnableConnectProtocol: Symbol('http2session connect protocol'),
6668
kRemoteSettings: Symbol('http2session remote settings'),
6769
kHTTP2Stream: Symbol('http2session client stream'),

deps/undici/src/lib/core/util.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ function wrapRequestBody (body) {
5858
// to determine whether or not it has been disturbed. This is just
5959
// a workaround.
6060
return new BodyAsyncIterable(body)
61+
} else if (body && isFormDataLike(body)) {
62+
return body
6163
} else if (
6264
body &&
6365
typeof body !== 'string' &&

deps/undici/src/lib/dispatcher/client-h2.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const {
2525
kOnError,
2626
kMaxConcurrentStreams,
2727
kHTTP2Session,
28+
kHTTP2InitialWindowSize,
29+
kHTTP2ConnectionWindowSize,
2830
kResume,
2931
kSize,
3032
kHTTPContext,
@@ -87,12 +89,16 @@ function parseH2Headers (headers) {
8789
function connectH2 (client, socket) {
8890
client[kSocket] = socket
8991

92+
const http2InitialWindowSize = client[kHTTP2InitialWindowSize]
93+
const http2ConnectionWindowSize = client[kHTTP2ConnectionWindowSize]
94+
9095
const session = http2.connect(client[kUrl], {
9196
createConnection: () => socket,
9297
peerMaxConcurrentStreams: client[kMaxConcurrentStreams],
9398
settings: {
9499
// TODO(metcoder95): add support for PUSH
95-
enablePush: false
100+
enablePush: false,
101+
...(http2InitialWindowSize != null ? { initialWindowSize: http2InitialWindowSize } : null)
96102
}
97103
})
98104

@@ -107,6 +113,11 @@ function connectH2 (client, socket) {
107113
// States whether or not we have received the remote settings from the server
108114
session[kRemoteSettings] = false
109115

116+
// Apply connection-level flow control once connected (if supported).
117+
if (http2ConnectionWindowSize) {
118+
util.addListener(session, 'connect', applyConnectionWindowSize.bind(session, http2ConnectionWindowSize))
119+
}
120+
110121
util.addListener(session, 'error', onHttp2SessionError)
111122
util.addListener(session, 'frameError', onHttp2FrameError)
112123
util.addListener(session, 'end', onHttp2SessionEnd)
@@ -211,6 +222,16 @@ function resumeH2 (client) {
211222
}
212223
}
213224

225+
function applyConnectionWindowSize (connectionWindowSize) {
226+
try {
227+
if (typeof this.setLocalWindowSize === 'function') {
228+
this.setLocalWindowSize(connectionWindowSize)
229+
}
230+
} catch {
231+
// Best-effort only.
232+
}
233+
}
234+
214235
function onHttp2RemoteSettings (settings) {
215236
// Fallbacks are a safe bet, remote setting will always override
216237
this[kClient][kMaxConcurrentStreams] = settings.maxConcurrentStreams ?? this[kClient][kMaxConcurrentStreams]

deps/undici/src/lib/dispatcher/client.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ const {
5252
kOnError,
5353
kHTTPContext,
5454
kMaxConcurrentStreams,
55+
kHTTP2InitialWindowSize,
56+
kHTTP2ConnectionWindowSize,
5557
kResume
5658
} = require('../core/symbols.js')
5759
const connectH1 = require('./client-h1.js')
@@ -108,7 +110,9 @@ class Client extends DispatcherBase {
108110
// h2
109111
maxConcurrentStreams,
110112
allowH2,
111-
useH2c
113+
useH2c,
114+
initialWindowSize,
115+
connectionWindowSize
112116
} = {}) {
113117
if (keepAlive !== undefined) {
114118
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
@@ -204,6 +208,14 @@ class Client extends DispatcherBase {
204208
throw new InvalidArgumentError('useH2c must be a valid boolean value')
205209
}
206210

211+
if (initialWindowSize != null && (!Number.isInteger(initialWindowSize) || initialWindowSize < 1)) {
212+
throw new InvalidArgumentError('initialWindowSize must be a positive integer, greater than 0')
213+
}
214+
215+
if (connectionWindowSize != null && (!Number.isInteger(connectionWindowSize) || connectionWindowSize < 1)) {
216+
throw new InvalidArgumentError('connectionWindowSize must be a positive integer, greater than 0')
217+
}
218+
207219
super()
208220

209221
if (typeof connect !== 'function') {
@@ -239,6 +251,14 @@ class Client extends DispatcherBase {
239251
this[kClosedResolve] = null
240252
this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1
241253
this[kMaxConcurrentStreams] = maxConcurrentStreams != null ? maxConcurrentStreams : 100 // Max peerConcurrentStreams for a Node h2 server
254+
// HTTP/2 window sizes are set to higher defaults than Node.js core for better performance:
255+
// - initialWindowSize: 262144 (256KB) vs Node.js default 65535 (64KB - 1)
256+
// Allows more data to be sent before requiring acknowledgment, improving throughput
257+
// especially on high-latency networks. This matches common production HTTP/2 servers.
258+
// - connectionWindowSize: 524288 (512KB) vs Node.js default (none set)
259+
// Provides better flow control for the entire connection across multiple streams.
260+
this[kHTTP2InitialWindowSize] = initialWindowSize != null ? initialWindowSize : 262144
261+
this[kHTTP2ConnectionWindowSize] = connectionWindowSize != null ? connectionWindowSize : 524288
242262
this[kHTTPContext] = null
243263

244264
// kQueue is built up of 3 sections separated by

deps/undici/src/lib/interceptor/cache.js

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@ const CacheRevalidationHandler = require('../handler/cache-revalidation-handler'
99
const { assertCacheStore, assertCacheMethods, makeCacheKey, normalizeHeaders, parseCacheControlHeader } = require('../util/cache.js')
1010
const { AbortError } = require('../core/errors.js')
1111

12+
/**
13+
* @param {(string | RegExp)[] | undefined} origins
14+
* @param {string} name
15+
*/
16+
function assertCacheOrigins (origins, name) {
17+
if (origins === undefined) return
18+
if (!Array.isArray(origins)) {
19+
throw new TypeError(`expected ${name} to be an array or undefined, got ${typeof origins}`)
20+
}
21+
for (let i = 0; i < origins.length; i++) {
22+
const origin = origins[i]
23+
if (typeof origin !== 'string' && !(origin instanceof RegExp)) {
24+
throw new TypeError(`expected ${name}[${i}] to be a string or RegExp, got ${typeof origin}`)
25+
}
26+
}
27+
}
28+
1229
const nop = () => {}
1330

1431
/**
@@ -372,7 +389,8 @@ module.exports = (opts = {}) => {
372389
store = new MemoryCacheStore(),
373390
methods = ['GET'],
374391
cacheByDefault = undefined,
375-
type = 'shared'
392+
type = 'shared',
393+
origins = undefined
376394
} = opts
377395

378396
if (typeof opts !== 'object' || opts === null) {
@@ -381,6 +399,7 @@ module.exports = (opts = {}) => {
381399

382400
assertCacheStore(store, 'opts.store')
383401
assertCacheMethods(methods, 'opts.methods')
402+
assertCacheOrigins(origins, 'opts.origins')
384403

385404
if (typeof cacheByDefault !== 'undefined' && typeof cacheByDefault !== 'number') {
386405
throw new TypeError(`expected opts.cacheByDefault to be number or undefined, got ${typeof cacheByDefault}`)
@@ -406,6 +425,29 @@ module.exports = (opts = {}) => {
406425
return dispatch(opts, handler)
407426
}
408427

428+
// Check if origin is in whitelist
429+
if (origins !== undefined) {
430+
const requestOrigin = opts.origin.toString().toLowerCase()
431+
let isAllowed = false
432+
433+
for (let i = 0; i < origins.length; i++) {
434+
const allowed = origins[i]
435+
if (typeof allowed === 'string') {
436+
if (allowed.toLowerCase() === requestOrigin) {
437+
isAllowed = true
438+
break
439+
}
440+
} else if (allowed.test(requestOrigin)) {
441+
isAllowed = true
442+
break
443+
}
444+
}
445+
446+
if (!isAllowed) {
447+
return dispatch(opts, handler)
448+
}
449+
}
450+
409451
opts = {
410452
...opts,
411453
headers: normalizeHeaders(opts)

deps/undici/src/lib/llhttp/wasm_build_env.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
> undici@7.18.2 build:wasm
2+
> undici@7.19.0 build:wasm
33
> node build/wasm.js --docker
44

55
> docker run --rm --platform=linux/x86_64 --user 1001:1001 --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

deps/undici/src/lib/mock/mock-agent.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const {
2222
} = require('./mock-symbols')
2323
const MockClient = require('./mock-client')
2424
const MockPool = require('./mock-pool')
25-
const { matchValue, normalizeSearchParams, buildAndValidateMockOptions } = require('./mock-utils')
25+
const { matchValue, normalizeSearchParams, buildAndValidateMockOptions, normalizeOrigin } = require('./mock-utils')
2626
const { InvalidArgumentError, UndiciError } = require('../core/errors')
2727
const Dispatcher = require('../dispatcher/dispatcher')
2828
const PendingInterceptorsFormatter = require('./pending-interceptors-formatter')
@@ -56,9 +56,9 @@ class MockAgent extends Dispatcher {
5656
}
5757

5858
get (origin) {
59-
const originKey = this[kIgnoreTrailingSlash]
60-
? origin.replace(/\/$/, '')
61-
: origin
59+
// Normalize origin to handle URL objects and case-insensitive hostnames
60+
const normalizedOrigin = normalizeOrigin(origin)
61+
const originKey = this[kIgnoreTrailingSlash] ? normalizedOrigin.replace(/\/$/, '') : normalizedOrigin
6262

6363
let dispatcher = this[kMockAgentGet](originKey)
6464

@@ -70,6 +70,8 @@ class MockAgent extends Dispatcher {
7070
}
7171

7272
dispatch (opts, handler) {
73+
opts.origin = normalizeOrigin(opts.origin)
74+
7375
// Call MockAgent.get to perform additional setup before dispatching as normal
7476
this.get(opts.origin)
7577

deps/undici/src/lib/mock/mock-utils.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,18 @@ function checkNetConnect (netConnect, origin) {
396396
return false
397397
}
398398

399+
function normalizeOrigin (origin) {
400+
if (typeof origin !== 'string' && !(origin instanceof URL)) {
401+
return origin
402+
}
403+
404+
if (origin instanceof URL) {
405+
return origin.origin
406+
}
407+
408+
return origin.toLowerCase()
409+
}
410+
399411
function buildAndValidateMockOptions (opts) {
400412
const { agent, ...mockOptions } = opts
401413

@@ -430,5 +442,6 @@ module.exports = {
430442
buildAndValidateMockOptions,
431443
getHeaderByName,
432444
buildHeadersFromArray,
433-
normalizeSearchParams
445+
normalizeSearchParams,
446+
normalizeOrigin
434447
}

deps/undici/src/lib/web/cache/cache.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -794,9 +794,9 @@ class Cache {
794794
// 5.5.2
795795
for (const response of responses) {
796796
// 5.5.2.1
797-
const responseObject = fromInnerResponse(response, 'immutable')
797+
const responseObject = fromInnerResponse(cloneResponse(response), 'immutable')
798798

799-
responseList.push(responseObject.clone())
799+
responseList.push(responseObject)
800800

801801
if (responseList.length >= maxResponses) {
802802
break

0 commit comments

Comments
 (0)