Skip to content

Commit 2a4c826

Browse files
committed
deps: update undici to 5.1.1
PR-URL: #42939 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
1 parent bf9240a commit 2a4c826

28 files changed

+4418
-5712
lines changed

deps/undici/src/README.md

+21-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![Node CI](https://github.com/nodejs/undici/actions/workflows/nodejs.yml/badge.svg)](https://github.com/nodejs/undici/actions/workflows/nodejs.yml) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![npm version](https://badge.fury.io/js/undici.svg)](https://badge.fury.io/js/undici) [![codecov](https://codecov.io/gh/nodejs/undici/branch/main/graph/badge.svg?token=yZL6LtXkOA)](https://codecov.io/gh/nodejs/undici)
44

5-
A HTTP/1.1 client, written from scratch for Node.js.
5+
An HTTP/1.1 client, written from scratch for Node.js.
66

77
> Undici means eleven in Italian. 1.1 -> 11 -> Eleven -> Undici.
88
It is also a Stranger Things reference.
@@ -65,7 +65,15 @@ for await (const data of body) {
6565
console.log('trailers', trailers)
6666
```
6767

68-
Using [the body mixin from the Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin).
68+
## Body Mixins
69+
70+
The `body` mixins are the most common way to format the request/response body. Mixins include:
71+
72+
- [`.formData()`](https://fetch.spec.whatwg.org/#dom-body-formdata)
73+
- [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
74+
- [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
75+
76+
Example usage:
6977

7078
```js
7179
import { request } from 'undici'
@@ -83,6 +91,12 @@ console.log('data', await body.json())
8391
console.log('trailers', trailers)
8492
```
8593

94+
_Note: Once a mixin has been called then the body cannot be reused, thus calling additional mixins on `.body`, e.g. `.body.json(); .body.text()` will result in an error `TypeError: unusable` being thrown and returned through the `Promise` rejection._
95+
96+
Should you need to access the `body` in plain-text after using a mixin, the best practice is to use the `.text()` mixin first and then manually parse the text to the desired format.
97+
98+
For more information about their behavior, please reference the body mixin from the [Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin).
99+
86100
## Common API Methods
87101

88102
This section documents our most commonly used API methods. Additional APIs are documented in their own files within the [docs](./docs/) folder and are accessible via the navigation list on the left side of the docs site.
@@ -213,7 +227,7 @@ const data = {
213227

214228
#### `response.body`
215229

216-
Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html) which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.
230+
Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html), which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.
217231

218232
```js
219233
import {fetch} from 'undici';
@@ -228,7 +242,7 @@ Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v1
228242

229243
#### Specification Compliance
230244

231-
This section documents parts of the [Fetch Standard](https://fetch.spec.whatwg.org) which Undici does
245+
This section documents parts of the [Fetch Standard](https://fetch.spec.whatwg.org) that Undici does
232246
not support or does not fully implement.
233247

234248
##### Garbage Collection
@@ -239,7 +253,7 @@ The [Fetch Standard](https://fetch.spec.whatwg.org) allows users to skip consumi
239253
[garbage collection](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#garbage_collection) to release connection resources. Undici does not do the same. Therefore, it is important to always either consume or cancel the response body.
240254

241255
Garbage collection in Node is less aggressive and deterministic
242-
(due to the lack of clear idle periods that browser have through the rendering refresh rate)
256+
(due to the lack of clear idle periods that browsers have through the rendering refresh rate)
243257
which means that leaving the release of connection resources to the garbage collector can lead
244258
to excessive connection usage, reduced performance (due to less connection re-use), and even
245259
stalls or deadlocks when running out of connections.
@@ -301,7 +315,7 @@ Returns: `Dispatcher`
301315

302316
## Specification Compliance
303317

304-
This section documents parts of the HTTP/1.1 specification which Undici does
318+
This section documents parts of the HTTP/1.1 specification that Undici does
305319
not support or does not fully implement.
306320

307321
### Expect
@@ -334,7 +348,7 @@ aborted.
334348

335349
### Manual Redirect
336350

337-
Since it is not possible to manually follow an HTTP redirect on server-side,
351+
Since it is not possible to manually follow an HTTP redirect on the server-side,
338352
Undici returns the actual response instead of an `opaqueredirect` filtered one
339353
when invoked with a `manual` redirect. This aligns `fetch()` with the other
340354
implementations in Deno and Cloudflare Workers.

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
193193
* **path** `string`
194194
* **method** `string`
195195
* **body** `string | Buffer | Uint8Array | stream.Readable | Iterable | AsyncIterable | null` (optional) - Default: `null`
196-
* **headers** `UndiciHeaders` (optional) - Default: `null`
196+
* **headers** `UndiciHeaders | string[]` (optional) - Default: `null`.
197197
* **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed.
198198
* **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
199199
* **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.

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

+76
Original file line numberDiff line numberDiff line change
@@ -445,3 +445,79 @@ mockAgent.disableNetConnect()
445445
await request('http://example.com')
446446
// Will throw
447447
```
448+
449+
### `MockAgent.pendingInterceptors()`
450+
451+
This method returns any pending interceptors registered on a mock agent. A pending interceptor meets one of the following criteria:
452+
453+
- Is registered with neither `.times(<number>)` nor `.persist()`, and has not been invoked;
454+
- Is persistent (i.e., registered with `.persist()`) and has not been invoked;
455+
- Is registered with `.times(<number>)` and has not been invoked `<number>` of times.
456+
457+
Returns: `PendingInterceptor[]` (where `PendingInterceptor` is a `MockDispatch` with an additional `origin: string`)
458+
459+
#### Example - List all pending inteceptors
460+
461+
```js
462+
const agent = new MockAgent()
463+
agent.disableNetConnect()
464+
465+
agent
466+
.get('https://example.com')
467+
.intercept({ method: 'GET', path: '/' })
468+
.reply(200, '')
469+
470+
const pendingInterceptors = agent.pendingInterceptors()
471+
// Returns [
472+
// {
473+
// timesInvoked: 0,
474+
// times: 1,
475+
// persist: false,
476+
// consumed: false,
477+
// pending: true,
478+
// path: '/',
479+
// method: 'GET',
480+
// body: undefined,
481+
// headers: undefined,
482+
// data: {
483+
// error: null,
484+
// statusCode: 200,
485+
// data: '',
486+
// headers: {},
487+
// trailers: {}
488+
// },
489+
// origin: 'https://example.com'
490+
// }
491+
// ]
492+
```
493+
494+
### `MockAgent.assertNoPendingInterceptors([options])`
495+
496+
This method throws if the mock agent has any pending interceptors. A pending interceptor meets one of the following criteria:
497+
498+
- Is registered with neither `.times(<number>)` nor `.persist()`, and has not been invoked;
499+
- Is persistent (i.e., registered with `.persist()`) and has not been invoked;
500+
- Is registered with `.times(<number>)` and has not been invoked `<number>` of times.
501+
502+
#### Example - Check that there are no pending interceptors
503+
504+
```js
505+
const agent = new MockAgent()
506+
agent.disableNetConnect()
507+
508+
agent
509+
.get('https://example.com')
510+
.intercept({ method: 'GET', path: '/' })
511+
.reply(200, '')
512+
513+
agent.assertNoPendingInterceptors()
514+
// Throws an UndiciError with the following message:
515+
//
516+
// 1 interceptor is pending:
517+
//
518+
// ┌─────────┬────────┬───────────────────────┬──────┬─────────────┬────────────┬─────────────┬───────────┐
519+
// │ (index) │ Method │ Origin │ Path │ Status code │ Persistent │ Invocations │ Remaining │
520+
// ├─────────┼────────┼───────────────────────┼──────┼─────────────┼────────────┼─────────────┼───────────┤
521+
// │ 0 │ 'GET' │ 'https://example.com' │ '/' │ 200 │ '❌' │ 0 │ 1 │
522+
// └─────────┴────────┴───────────────────────┴──────┴─────────────┴────────────┴─────────────┴───────────┘
523+
```

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

+10-7
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ Undici have its own mocking [utility](../api/MockAgent.md). It allow us to inter
55
Example:
66

77
```js
8-
// index.mjs
8+
// bank.mjs
99
import { request } from 'undici'
1010

11-
export async function bankTransfer(recepient, ammount) {
12-
const { body } = await request('http://localhost:3000/bank-transfer',
11+
export async function bankTransfer(recepient, amount) {
12+
const { body } = await request('http://localhost:3000/bank-transfer',
1313
{
1414
method: 'POST',
1515
headers: {
1616
'X-TOKEN-SECRET': 'SuperSecretToken',
1717
},
18-
body: JSON.stringify({ recepient })
18+
body: JSON.stringify({
19+
recepient,
20+
amount
21+
})
1922
}
2023
)
2124
return await body.json()
@@ -28,7 +31,7 @@ And this is what the test file looks like:
2831
// index.test.mjs
2932
import { strict as assert } from 'assert'
3033
import { MockAgent, setGlobalDispatcher, } from 'undici'
31-
import { bankTransfer } from './undici.mjs'
34+
import { bankTransfer } from './bank.mjs'
3235

3336
const mockAgent = new MockAgent();
3437

@@ -46,7 +49,7 @@ mockPool.intercept({
4649
},
4750
body: JSON.stringify({
4851
recepient: '1234567890',
49-
ammount: '100'
52+
amount: '100'
5053
})
5154
}).reply(200, {
5255
message: 'transaction processed'
@@ -94,7 +97,7 @@ mockPool.intercept({
9497

9598
const badRequest = await bankTransfer('1234567890', '100')
9699
// Will throw an error
97-
// MockNotMatchedError: Mock dispatch not matched for path '/bank-transfer':
100+
// MockNotMatchedError: Mock dispatch not matched for path '/bank-transfer':
98101
// subsequent request to origin http://localhost:3000 was not allowed (net.connect disabled)
99102
```
100103

deps/undici/src/index-fetch.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict'
2+
3+
const Agent = require('./lib/agent')
4+
5+
const globalDispatcher = new Agent()
6+
7+
const fetchImpl = require('./lib/fetch')
8+
module.exports.fetch = async function fetch (resource) {
9+
return fetchImpl.apply(globalDispatcher, arguments)
10+
}
11+
module.exports.FormData = require('./lib/fetch/formdata').FormData
12+
module.exports.Headers = require('./lib/fetch/headers').Headers
13+
module.exports.Response = require('./lib/fetch/response').Response
14+
module.exports.Request = require('./lib/fetch/request').Request

deps/undici/src/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { request, pipeline, stream, connect, upgrade } from './types/api'
1616
export * from './types/fetch'
1717
export * from './types/file'
1818
export * from './types/formdata'
19+
export { Interceptable } from './types/mock-interceptor'
1920

2021
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent }
2122
export default Undici

deps/undici/src/lib/api/api-request.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,16 @@ class RequestHandler extends AsyncResource {
8888
this.res = body
8989
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
9090

91-
this.runInAsyncScope(callback, null, null, {
92-
statusCode,
93-
headers,
94-
trailers: this.trailers,
95-
opaque,
96-
body,
97-
context
98-
})
91+
if (callback !== null) {
92+
this.runInAsyncScope(callback, null, null, {
93+
statusCode,
94+
headers,
95+
trailers: this.trailers,
96+
opaque,
97+
body,
98+
context
99+
})
100+
}
99101
}
100102

101103
onData (chunk) {

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

+23-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ const kHandler = Symbol('handler')
1111

1212
const channels = {}
1313

14+
let extractBody
15+
16+
const nodeVersion = process.versions.node.split('.')
17+
const nodeMajor = Number(nodeVersion[0])
18+
const nodeMinor = Number(nodeVersion[1])
19+
1420
try {
1521
const diagnosticsChannel = require('diagnostics_channel')
1622
channels.create = diagnosticsChannel.channel('undici:request:create')
@@ -79,7 +85,7 @@ class Request {
7985
this.body = body.byteLength ? body : null
8086
} else if (typeof body === 'string') {
8187
this.body = body.length ? Buffer.from(body) : null
82-
} else if (util.isIterable(body) || util.isBlobLike(body)) {
88+
} else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) {
8389
this.body = body
8490
} else {
8591
throw new InvalidArgumentError('body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable')
@@ -126,7 +132,22 @@ class Request {
126132
throw new InvalidArgumentError('headers must be an object or an array')
127133
}
128134

129-
if (util.isBlobLike(body) && this.contentType == null && body.type) {
135+
if (util.isFormDataLike(this.body)) {
136+
if (nodeMajor < 16 || (nodeMajor === 16 && nodeMinor < 5)) {
137+
throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.5 and newer.')
138+
}
139+
140+
if (!extractBody) {
141+
extractBody = require('../fetch/body.js').extractBody
142+
}
143+
144+
const [bodyStream, contentType] = extractBody(body)
145+
if (this.contentType == null) {
146+
this.contentType = contentType
147+
this.headers += `content-type: ${contentType}\r\n`
148+
}
149+
this.body = bodyStream.stream
150+
} else if (util.isBlobLike(body) && this.contentType == null && body.type) {
130151
this.contentType = body.type
131152
this.headers += `content-type: ${body.type}\r\n`
132153
}

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,10 @@ function ReadableStreamFrom (iterable) {
324324
)
325325
}
326326

327+
function isFormDataLike (chunk) {
328+
return chunk && chunk.constructor && chunk.constructor.name === 'FormData'
329+
}
330+
327331
const kEnumerableProperty = Object.create(null)
328332
kEnumerableProperty.enumerable = true
329333

@@ -352,5 +356,6 @@ module.exports = {
352356
ReadableStreamFrom,
353357
isBuffer,
354358
validateHandler,
355-
getSocketInfo
359+
getSocketInfo,
360+
isFormDataLike
356361
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ function extractBody (object, keepalive = false) {
7171

7272
// Set source to a copy of the bytes held by object.
7373
source = new Uint8Array(object)
74-
} else if (object instanceof FormData) {
74+
} else if (util.isFormDataLike(object)) {
7575
const boundary = '----formdata-undici-' + Math.random()
7676
const prefix = `--${boundary}\r\nContent-Disposition: form-data`
7777

@@ -348,7 +348,7 @@ const properties = {
348348
bodyUsed: {
349349
enumerable: true,
350350
get () {
351-
return this[kState].body && util.isDisturbed(this[kState].body.stream)
351+
return !!this[kState].body && util.isDisturbed(this[kState].body.stream)
352352
}
353353
}
354354
}

0 commit comments

Comments
 (0)