Skip to content

Commit 51ded81

Browse files
nodejs-github-botrichardlau
authored andcommitted
deps: update undici to 7.22.0
PR-URL: #62035 Reviewed-By: Matthew Aitken <maitken033380023@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Richard Lau <richard.lau@ibm.com>
1 parent 4e54c10 commit 51ded81

File tree

13 files changed

+723
-525
lines changed

13 files changed

+723
-525
lines changed

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,12 @@ Parameters:
6666
Returns: `GetResult | Promise<GetResult | undefined> | undefined` - If the request is cached, the cached response is returned. If the request's method is anything other than HEAD, the response is also returned.
6767
If the request isn't cached, `undefined` is returned.
6868

69+
The `get` method may return a `Promise` for async cache stores (e.g. Redis-backed or remote stores). The cache interceptor handles both synchronous and asynchronous return values, including in revalidation paths (304 Not Modified handling and stale-while-revalidate background revalidation).
70+
6971
Response properties:
7072

7173
* **response** `CacheValue` - The cached response data.
72-
* **body** `Readable | undefined` - The response's body.
74+
* **body** `Readable | Iterable<Buffer> | undefined` - The response's body. This can be an array of `Buffer` chunks (with a `.values()` method) or a `Readable` stream. Both formats are supported in all code paths, including 304 revalidation.
7375

7476
### Function: `createWriteStream`
7577

@@ -98,8 +100,11 @@ This is an interface containing the majority of a response's data (minus the bod
98100

99101
### Property `vary`
100102

101-
`Record<string, string | string[]> | undefined` - The headers defined by the response's `Vary` header
102-
and their respective values for later comparison
103+
`Record<string, string | string[] | null> | undefined` - The headers defined by the response's `Vary` header
104+
and their respective values for later comparison. Values are `null` when the
105+
header specified in `Vary` was not present in the original request. These `null`
106+
values are automatically filtered out during revalidation so they are not sent
107+
as request headers.
103108

104109
For example, for a response like
105110
```
@@ -116,6 +121,14 @@ This would be
116121
}
117122
```
118123

124+
If the original request did not include the `accepts` header:
125+
```js
126+
{
127+
'content-encoding': 'utf8',
128+
accepts: null
129+
}
130+
```
131+
119132
### Property `cachedAt`
120133

121134
`number` - Time in millis that this value was cached.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Arguments:
1515

1616
This example will not work in browsers or other platforms that don't allow passing an object.
1717

18-
```mjs
18+
```js
1919
import { WebSocket, ProxyAgent } from 'undici'
2020

2121
const proxyAgent = new ProxyAgent('my.proxy.server')
@@ -28,7 +28,7 @@ const ws = new WebSocket('wss://echo.websocket.events', {
2828

2929
If you do not need a custom Dispatcher, it's recommended to use the following pattern:
3030

31-
```mjs
31+
```js
3232
import { WebSocket } from 'undici'
3333

3434
const ws = new WebSocket('wss://echo.websocket.events', ['echo', 'chat'])
@@ -44,7 +44,7 @@ const ws = new WebSocket('wss://echo.websocket.events', ['echo', 'chat'])
4444
4545
This example will not work in browsers or other platforms that don't allow passing an object.
4646

47-
```mjs
47+
```js
4848
import { Agent } from 'undici'
4949

5050
const agent = new Agent({ allowH2: true })

deps/undici/src/lib/dispatcher/env-http-proxy-agent.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,14 @@ class EnvHttpProxyAgent extends DispatcherBase {
9595
if (entry.port && entry.port !== port) {
9696
continue // Skip if ports don't match.
9797
}
98-
if (!/^[.*]/.test(entry.hostname)) {
99-
// No wildcards, so don't proxy only if there is not an exact match.
100-
if (hostname === entry.hostname) {
101-
return false
102-
}
103-
} else {
104-
// Don't proxy if the hostname ends with the no_proxy host.
105-
if (hostname.endsWith(entry.hostname.replace(/^\*/, ''))) {
106-
return false
107-
}
98+
// Don't proxy if the hostname is equal with the no_proxy host.
99+
if (hostname === entry.hostname) {
100+
return false
101+
}
102+
// Don't proxy if the hostname is the subdomain of the no_proxy host.
103+
// Reference - https://github.com/denoland/deno/blob/6fbce91e40cc07fc6da74068e5cc56fdd40f7b4c/ext/fetch/proxy.rs#L485
104+
if (hostname.slice(-(entry.hostname.length + 1)) === `.${entry.hostname}`) {
105+
return false
108106
}
109107
}
110108

@@ -123,7 +121,8 @@ class EnvHttpProxyAgent extends DispatcherBase {
123121
}
124122
const parsed = entry.match(/^(.+):(\d+)$/)
125123
noProxyEntries.push({
126-
hostname: (parsed ? parsed[1] : entry).toLowerCase(),
124+
// strip leading dot or asterisk with dot
125+
hostname: (parsed ? parsed[1] : entry).replace(/^\*?\./, '').toLowerCase(),
127126
port: parsed ? Number.parseInt(parsed[2], 10) : 0
128127
})
129128
}

deps/undici/src/lib/handler/cache-handler.js

Lines changed: 77 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -193,57 +193,92 @@ class CacheHandler {
193193
// Not modified, re-use the cached value
194194
// https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-304-not-modified
195195
if (statusCode === 304) {
196-
/**
197-
* @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
198-
*/
199-
const cachedValue = this.#store.get(this.#cacheKey)
200-
if (!cachedValue) {
201-
// Do not create a new cache entry, as a 304 won't have a body - so cannot be cached.
202-
return downstreamOnHeaders()
203-
}
204-
205-
// Re-use the cached value: statuscode, statusmessage, headers and body
206-
value.statusCode = cachedValue.statusCode
207-
value.statusMessage = cachedValue.statusMessage
208-
value.etag = cachedValue.etag
209-
value.headers = { ...cachedValue.headers, ...strippedHeaders }
196+
const handle304 = (cachedValue) => {
197+
if (!cachedValue) {
198+
// Do not create a new cache entry, as a 304 won't have a body - so cannot be cached.
199+
return downstreamOnHeaders()
200+
}
210201

211-
downstreamOnHeaders()
202+
// Re-use the cached value: statuscode, statusmessage, headers and body
203+
value.statusCode = cachedValue.statusCode
204+
value.statusMessage = cachedValue.statusMessage
205+
value.etag = cachedValue.etag
206+
value.headers = { ...cachedValue.headers, ...strippedHeaders }
212207

213-
this.#writeStream = this.#store.createWriteStream(this.#cacheKey, value)
208+
downstreamOnHeaders()
214209

215-
if (!this.#writeStream || !cachedValue?.body) {
216-
return
217-
}
210+
this.#writeStream = this.#store.createWriteStream(this.#cacheKey, value)
218211

219-
const bodyIterator = cachedValue.body.values()
212+
if (!this.#writeStream || !cachedValue?.body) {
213+
return
214+
}
220215

221-
const streamCachedBody = () => {
222-
for (const chunk of bodyIterator) {
223-
const full = this.#writeStream.write(chunk) === false
224-
this.#handler.onResponseData?.(controller, chunk)
225-
// when stream is full stop writing until we get a 'drain' event
226-
if (full) {
227-
break
216+
if (typeof cachedValue.body.values === 'function') {
217+
const bodyIterator = cachedValue.body.values()
218+
219+
const streamCachedBody = () => {
220+
for (const chunk of bodyIterator) {
221+
const full = this.#writeStream.write(chunk) === false
222+
this.#handler.onResponseData?.(controller, chunk)
223+
// when stream is full stop writing until we get a 'drain' event
224+
if (full) {
225+
break
226+
}
227+
}
228228
}
229-
}
230-
}
231229

232-
this.#writeStream
233-
.on('error', function () {
234-
handler.#writeStream = undefined
235-
handler.#store.delete(handler.#cacheKey)
236-
})
237-
.on('drain', () => {
230+
this.#writeStream
231+
.on('error', function () {
232+
handler.#writeStream = undefined
233+
handler.#store.delete(handler.#cacheKey)
234+
})
235+
.on('drain', () => {
236+
streamCachedBody()
237+
})
238+
.on('close', function () {
239+
if (handler.#writeStream === this) {
240+
handler.#writeStream = undefined
241+
}
242+
})
243+
238244
streamCachedBody()
239-
})
240-
.on('close', function () {
241-
if (handler.#writeStream === this) {
242-
handler.#writeStream = undefined
243-
}
244-
})
245+
} else if (typeof cachedValue.body.on === 'function') {
246+
// Readable stream body (e.g. from async/remote cache stores)
247+
cachedValue.body
248+
.on('data', (chunk) => {
249+
this.#writeStream.write(chunk)
250+
this.#handler.onResponseData?.(controller, chunk)
251+
})
252+
.on('end', () => {
253+
this.#writeStream.end()
254+
})
255+
.on('error', () => {
256+
this.#writeStream = undefined
257+
this.#store.delete(this.#cacheKey)
258+
})
259+
260+
this.#writeStream
261+
.on('error', function () {
262+
handler.#writeStream = undefined
263+
handler.#store.delete(handler.#cacheKey)
264+
})
265+
.on('close', function () {
266+
if (handler.#writeStream === this) {
267+
handler.#writeStream = undefined
268+
}
269+
})
270+
}
271+
}
245272

246-
streamCachedBody()
273+
/**
274+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
275+
*/
276+
const result = this.#store.get(this.#cacheKey)
277+
if (result && typeof result.then === 'function') {
278+
result.then(handle304)
279+
} else {
280+
handle304(result)
281+
}
247282
} else {
248283
if (typeof resHeaders.etag === 'string' && isEtagUsable(resHeaders.etag)) {
249284
value.etag = resHeaders.etag

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ function handleResult (
292292

293293
// Start background revalidation (fire-and-forget)
294294
queueMicrotask(() => {
295-
let headers = {
295+
const headers = {
296296
...opts.headers,
297297
'if-modified-since': new Date(result.cachedAt).toUTCString()
298298
}
@@ -302,9 +302,10 @@ function handleResult (
302302
}
303303

304304
if (result.vary) {
305-
headers = {
306-
...headers,
307-
...result.vary
305+
for (const key in result.vary) {
306+
if (result.vary[key] != null) {
307+
headers[key] = result.vary[key]
308+
}
308309
}
309310
}
310311

@@ -335,7 +336,7 @@ function handleResult (
335336
withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000))
336337
}
337338

338-
let headers = {
339+
const headers = {
339340
...opts.headers,
340341
'if-modified-since': new Date(result.cachedAt).toUTCString()
341342
}
@@ -345,9 +346,10 @@ function handleResult (
345346
}
346347

347348
if (result.vary) {
348-
headers = {
349-
...headers,
350-
...result.vary
349+
for (const key in result.vary) {
350+
if (result.vary[key] != null) {
351+
headers[key] = result.vary[key]
352+
}
351353
}
352354
}
353355

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ module.exports = (opts = {}) => {
4646
// Convert to lowercase Set for case-insensitive header exclusion from deduplication key
4747
const excludeHeaderNamesSet = new Set(excludeHeaderNames.map(name => name.toLowerCase()))
4848

49-
const safeMethodsToNotDeduplicate = util.safeHTTPMethods.filter(method => methods.includes(method) === false)
50-
5149
/**
5250
* Map of pending requests for deduplication
5351
* @type {Map<string, DeduplicationHandler>}
@@ -56,7 +54,7 @@ module.exports = (opts = {}) => {
5654

5755
return dispatch => {
5856
return (opts, handler) => {
59-
if (!opts.origin || safeMethodsToNotDeduplicate.includes(opts.method)) {
57+
if (!opts.origin || methods.includes(opts.method) === false) {
6058
return dispatch(opts, handler)
6159
}
6260

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.21.0 build:wasm
2+
> undici@7.22.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/web/fetch/index.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,6 +2302,41 @@ async function httpNetworkFetch (
23022302
reject(error)
23032303
},
23042304

2305+
onRequestUpgrade (_controller, status, headers, socket) {
2306+
// We need to support 200 for websocket over h2 as per RFC-8441
2307+
// Absence of session means H1
2308+
if ((socket.session != null && status !== 200) || (socket.session == null && status !== 101)) {
2309+
return false
2310+
}
2311+
2312+
const headersList = new HeadersList()
2313+
2314+
for (const [name, value] of Object.entries(headers)) {
2315+
if (value == null) {
2316+
continue
2317+
}
2318+
2319+
const headerName = name.toLowerCase()
2320+
2321+
if (Array.isArray(value)) {
2322+
for (const entry of value) {
2323+
headersList.append(headerName, String(entry), true)
2324+
}
2325+
} else {
2326+
headersList.append(headerName, String(value), true)
2327+
}
2328+
}
2329+
2330+
resolve({
2331+
status,
2332+
statusText: STATUS_CODES[status],
2333+
headersList,
2334+
socket
2335+
})
2336+
2337+
return true
2338+
},
2339+
23052340
onUpgrade (status, rawHeaders, socket) {
23062341
// We need to support 200 for websocket over h2 as per RFC-8441
23072342
// Absence of session means H1

deps/undici/src/lib/web/fetch/util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,7 @@ function hasAuthenticationEntry (request) {
14391439
*/
14401440
function includesCredentials (url) {
14411441
// A URL includes credentials if its username or password is not the empty string.
1442-
return !!(url.username && url.password)
1442+
return !!(url.username || url.password)
14431443
}
14441444

14451445
/**

0 commit comments

Comments
 (0)