Skip to content

Commit d09bc54

Browse files
nodejs-github-botruyadorno
authored andcommitted
deps: update undici to 5.8.1
PR-URL: #44158 Reviewed-By: Mohammed Keyvanzadeh <[email protected]> Reviewed-By: Feng Yu <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 873941a commit d09bc54

15 files changed

+195
-71
lines changed

deps/undici/src/docs/best-practices/mocking-request.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
# Mocking Request
22

3-
Undici have its own mocking [utility](../api/MockAgent.md). It allow us to intercept undici HTTP request and return mocked value instead. It can be useful for testing purposes.
3+
Undici has its own mocking [utility](../api/MockAgent.md). It allow us to intercept undici HTTP requests and return mocked values instead. It can be useful for testing purposes.
44

55
Example:
66

77
```js
88
// bank.mjs
99
import { request } from 'undici'
1010

11-
export async function bankTransfer(recepient, amount) {
11+
export async function bankTransfer(recipient, amount) {
1212
const { body } = await request('http://localhost:3000/bank-transfer',
1313
{
1414
method: 'POST',
1515
headers: {
1616
'X-TOKEN-SECRET': 'SuperSecretToken',
1717
},
1818
body: JSON.stringify({
19-
recepient,
19+
recipient,
2020
amount
2121
})
2222
}
@@ -48,7 +48,7 @@ mockPool.intercept({
4848
'X-TOKEN-SECRET': 'SuperSecretToken',
4949
},
5050
body: JSON.stringify({
51-
recepient: '1234567890',
51+
recipient: '1234567890',
5252
amount: '100'
5353
})
5454
}).reply(200, {
@@ -77,7 +77,7 @@ Explore other MockAgent functionality [here](../api/MockAgent.md)
7777

7878
## Debug Mock Value
7979

80-
When the interceptor we wrote are not the same undici will automatically call real HTTP request. To debug our mock value use `mockAgent.disableNetConnect()`
80+
When the interceptor and the request options are not the same, undici will automatically make a real HTTP request. To prevent real requests from being made, use `mockAgent.disableNetConnect()`:
8181

8282
```js
8383
const mockAgent = new MockAgent();
@@ -89,7 +89,7 @@ mockAgent.disableNetConnect()
8989
const mockPool = mockAgent.get('http://localhost:3000');
9090

9191
mockPool.intercept({
92-
path: '/bank-tanfer',
92+
path: '/bank-transfer',
9393
method: 'POST',
9494
}).reply(200, {
9595
message: 'transaction processed'
@@ -103,7 +103,7 @@ const badRequest = await bankTransfer('1234567890', '100')
103103

104104
## Reply with data based on request
105105

106-
If the mocked response needs to be dynamically derived from the request parameters, you can provide a function instead of an object to `reply`
106+
If the mocked response needs to be dynamically derived from the request parameters, you can provide a function instead of an object to `reply`:
107107

108108
```js
109109
mockPool.intercept({
@@ -113,7 +113,7 @@ mockPool.intercept({
113113
'X-TOKEN-SECRET': 'SuperSecretToken',
114114
},
115115
body: JSON.stringify({
116-
recepient: '1234567890',
116+
recipient: '1234567890',
117117
amount: '100'
118118
})
119119
}).reply(200, (opts) => {
@@ -129,7 +129,7 @@ in this case opts will be
129129
{
130130
method: 'POST',
131131
headers: { 'X-TOKEN-SECRET': 'SuperSecretToken' },
132-
body: '{"recepient":"1234567890","amount":"100"}',
132+
body: '{"recipient":"1234567890","amount":"100"}',
133133
origin: 'http://localhost:3000',
134134
path: '/bank-transfer'
135135
}

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

+28-5
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,12 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
7575
})
7676
}
7777

78-
const timeoutId = timeout
79-
? setTimeout(onConnectTimeout, timeout, socket)
80-
: null
78+
const cancelTimeout = setupTimeout(() => onConnectTimeout(socket), timeout)
8179

8280
socket
8381
.setNoDelay(true)
8482
.once(protocol === 'https:' ? 'secureConnect' : 'connect', function () {
85-
clearTimeout(timeoutId)
83+
cancelTimeout()
8684

8785
if (callback) {
8886
const cb = callback
@@ -91,7 +89,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
9189
}
9290
})
9391
.on('error', function (err) {
94-
clearTimeout(timeoutId)
92+
cancelTimeout()
9593

9694
if (callback) {
9795
const cb = callback
@@ -104,6 +102,31 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
104102
}
105103
}
106104

105+
function setupTimeout (onConnectTimeout, timeout) {
106+
if (!timeout) {
107+
return () => {}
108+
}
109+
110+
let s1 = null
111+
let s2 = null
112+
const timeoutId = setTimeout(() => {
113+
// setImmediate is added to make sure that we priotorise socket error events over timeouts
114+
s1 = setImmediate(() => {
115+
if (process.platform === 'win32') {
116+
// Windows needs an extra setImmediate probably due to implementation differences in the socket logic
117+
s2 = setImmediate(() => onConnectTimeout())
118+
} else {
119+
onConnectTimeout()
120+
}
121+
})
122+
}, timeout)
123+
return () => {
124+
clearTimeout(timeoutId)
125+
clearImmediate(s1)
126+
clearImmediate(s2)
127+
}
128+
}
129+
107130
function onConnectTimeout (socket) {
108131
util.destroy(socket, new ConnectTimeoutError())
109132
}

deps/undici/src/lib/fetch/body.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,18 @@ function bodyMixinMethods (instance) {
404404
// 1. Let entries be the result of parsing bytes.
405405
let entries
406406
try {
407-
entries = new URLSearchParams(await this.text())
407+
let text = ''
408+
// application/x-www-form-urlencoded parser will keep the BOM.
409+
// https://url.spec.whatwg.org/#concept-urlencoded-parser
410+
const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true })
411+
for await (const chunk of consumeBody(this[kState].body)) {
412+
if (!isUint8Array(chunk)) {
413+
throw new TypeError('Expected Uint8Array chunk')
414+
}
415+
text += textDecoder.decode(chunk, { stream: true })
416+
}
417+
text += textDecoder.decode()
418+
entries = new URLSearchParams(text)
408419
} catch (err) {
409420
// istanbul ignore next: Unclear when new URLSearchParams can fail on a string.
410421
// 2. If entries is failure, then throw a TypeError.

deps/undici/src/lib/fetch/dataURL.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ function percentDecode (input) {
255255
}
256256

257257
// 3. Return output.
258-
return Uint8Array.of(...output)
258+
return Uint8Array.from(output)
259259
}
260260

261261
// https://mimesniff.spec.whatwg.org/#parse-a-mime-type

deps/undici/src/lib/fetch/index.js

+11-7
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ const {
3333
isBlobLike,
3434
sameOrigin,
3535
isCancelled,
36-
isAborted
36+
isAborted,
37+
isErrorLike
3738
} = require('./util')
3839
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
3940
const assert = require('assert')
@@ -1854,7 +1855,7 @@ async function httpNetworkFetch (
18541855
timingInfo.decodedBodySize += bytes?.byteLength ?? 0
18551856

18561857
// 6. If bytes is failure, then terminate fetchParams’s controller.
1857-
if (bytes instanceof Error) {
1858+
if (isErrorLike(bytes)) {
18581859
fetchParams.controller.terminate(bytes)
18591860
return
18601861
}
@@ -1894,7 +1895,7 @@ async function httpNetworkFetch (
18941895
// 3. Otherwise, if stream is readable, error stream with a TypeError.
18951896
if (isReadable(stream)) {
18961897
fetchParams.controller.controller.error(new TypeError('terminated', {
1897-
cause: reason instanceof Error ? reason : undefined
1898+
cause: isErrorLike(reason) ? reason : undefined
18981899
}))
18991900
}
19001901
}
@@ -1942,14 +1943,17 @@ async function httpNetworkFetch (
19421943
}
19431944

19441945
let codings = []
1946+
let location = ''
19451947

19461948
const headers = new Headers()
19471949
for (let n = 0; n < headersList.length; n += 2) {
1948-
const key = headersList[n + 0].toString()
1949-
const val = headersList[n + 1].toString()
1950+
const key = headersList[n + 0].toString('latin1')
1951+
const val = headersList[n + 1].toString('latin1')
19501952

19511953
if (key.toLowerCase() === 'content-encoding') {
19521954
codings = val.split(',').map((x) => x.trim())
1955+
} else if (key.toLowerCase() === 'location') {
1956+
location = val
19531957
}
19541958

19551959
headers.append(key, val)
@@ -1960,7 +1964,7 @@ async function httpNetworkFetch (
19601964
const decoders = []
19611965

19621966
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
1963-
if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status)) {
1967+
if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !(request.redirect === 'follow' && location)) {
19641968
for (const coding of codings) {
19651969
if (/(x-)?gzip/.test(coding)) {
19661970
decoders.push(zlib.createGunzip())
@@ -1980,7 +1984,7 @@ async function httpNetworkFetch (
19801984
statusText,
19811985
headersList: headers[kHeadersList],
19821986
body: decoders.length
1983-
? pipeline(this.body, ...decoders, () => {})
1987+
? pipeline(this.body, ...decoders, () => { })
19841988
: this.body.on('error', () => {})
19851989
})
19861990

deps/undici/src/lib/fetch/request.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,9 @@ class Request {
367367
}
368368

369369
if (signal.aborted) {
370-
ac.abort()
370+
ac.abort(signal.reason)
371371
} else {
372-
const abort = () => ac.abort()
372+
const abort = () => ac.abort(signal.reason)
373373
signal.addEventListener('abort', abort, { once: true })
374374
requestFinalizer.register(this, { signal, abort })
375375
}
@@ -726,12 +726,12 @@ class Request {
726726
// 4. Make clonedRequestObject’s signal follow this’s signal.
727727
const ac = new AbortController()
728728
if (this.signal.aborted) {
729-
ac.abort()
729+
ac.abort(this.signal.reason)
730730
} else {
731731
this.signal.addEventListener(
732732
'abort',
733-
function () {
734-
ac.abort()
733+
() => {
734+
ac.abort(this.signal.reason)
735735
},
736736
{ once: true }
737737
)

deps/undici/src/lib/fetch/response.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const {
1010
isCancelled,
1111
isAborted,
1212
isBlobLike,
13-
serializeJavascriptValueToJSONString
13+
serializeJavascriptValueToJSONString,
14+
isErrorLike
1415
} = require('./util')
1516
const {
1617
redirectStatus,
@@ -347,15 +348,15 @@ function makeResponse (init) {
347348
}
348349

349350
function makeNetworkError (reason) {
351+
const isError = isErrorLike(reason)
350352
return makeResponse({
351353
type: 'error',
352354
status: 0,
353-
error:
354-
reason instanceof Error
355-
? reason
356-
: new Error(reason ? String(reason) : reason, {
357-
cause: reason instanceof Error ? reason : undefined
358-
}),
355+
error: isError
356+
? reason
357+
: new Error(reason ? String(reason) : reason, {
358+
cause: isError ? reason : undefined
359+
}),
359360
aborted: reason && reason.name === 'AbortError'
360361
})
361362
}

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ function isFileLike (object) {
8282
)
8383
}
8484

85+
function isErrorLike (object) {
86+
return object instanceof Error || (
87+
object?.constructor?.name === 'Error' ||
88+
object?.constructor?.name === 'DOMException'
89+
)
90+
}
91+
8592
// Check whether |statusText| is a ByteString and
8693
// matches the Reason-Phrase token production.
8794
// RFC 2616: https://tools.ietf.org/html/rfc2616
@@ -469,5 +476,6 @@ module.exports = {
469476
makeIterator,
470477
isValidHeaderName,
471478
isValidHeaderValue,
472-
hasOwn
479+
hasOwn,
480+
isErrorLike
473481
}

deps/undici/src/lib/fetch/webidl.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,9 @@ webidl.converters.DOMString = function (V, opts = {}) {
388388
return String(V)
389389
}
390390

391+
// Check for 0 or more characters outside of the latin1 range.
391392
// eslint-disable-next-line no-control-regex
392-
const isNotLatin1 = /[^\u0000-\u00ff]/
393+
const isLatin1 = /^[\u0000-\u00ff]{0,}$/
393394

394395
// https://webidl.spec.whatwg.org/#es-ByteString
395396
webidl.converters.ByteString = function (V) {
@@ -399,7 +400,7 @@ webidl.converters.ByteString = function (V) {
399400

400401
// 2. If the value of any element of x is greater than
401402
// 255, then throw a TypeError.
402-
if (isNotLatin1.test(x)) {
403+
if (!isLatin1.test(x)) {
403404
throw new TypeError('Argument is not a ByteString')
404405
}
405406

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

+20-9
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function lowerCaseEntries (headers) {
3838
function getHeaderByName (headers, key) {
3939
if (Array.isArray(headers)) {
4040
for (let i = 0; i < headers.length; i += 2) {
41-
if (headers[i] === key) {
41+
if (headers[i].toLocaleLowerCase() === key.toLocaleLowerCase()) {
4242
return headers[i + 1]
4343
}
4444
}
@@ -47,19 +47,24 @@ function getHeaderByName (headers, key) {
4747
} else if (typeof headers.get === 'function') {
4848
return headers.get(key)
4949
} else {
50-
return headers[key]
50+
return lowerCaseEntries(headers)[key.toLocaleLowerCase()]
5151
}
5252
}
5353

54+
/** @param {string[]} headers */
55+
function buildHeadersFromArray (headers) { // fetch HeadersList
56+
const clone = headers.slice()
57+
const entries = []
58+
for (let index = 0; index < clone.length; index += 2) {
59+
entries.push([clone[index], clone[index + 1]])
60+
}
61+
return Object.fromEntries(entries)
62+
}
63+
5464
function matchHeaders (mockDispatch, headers) {
5565
if (typeof mockDispatch.headers === 'function') {
5666
if (Array.isArray(headers)) { // fetch HeadersList
57-
const clone = headers.slice()
58-
const entries = []
59-
for (let index = 0; index < clone.length; index += 2) {
60-
entries.push([clone[index], clone[index + 1]])
61-
}
62-
headers = Object.fromEntries(entries)
67+
headers = buildHeadersFromArray(headers)
6368
}
6469
return mockDispatch.headers(headers ? lowerCaseEntries(headers) : {})
6570
}
@@ -284,7 +289,13 @@ function mockDispatch (opts, handler) {
284289
}
285290

286291
function handleReply (mockDispatches) {
287-
const responseData = getResponseData(typeof data === 'function' ? data(opts) : data)
292+
// fetch's HeadersList is a 1D string array
293+
const optsHeaders = Array.isArray(opts.headers)
294+
? buildHeadersFromArray(opts.headers)
295+
: opts.headers
296+
const responseData = getResponseData(
297+
typeof data === 'function' ? data({ ...opts, headers: optsHeaders }) : data
298+
)
288299
const responseHeaders = generateKeyValues(headers)
289300
const responseTrailers = generateKeyValues(trailers)
290301

0 commit comments

Comments
 (0)