undici 4.15.1 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -7
- package/docs/api/Dispatcher.md +3 -1
- package/docs/api/MockAgent.md +85 -45
- package/docs/api/MockClient.md +1 -4
- package/docs/api/MockPool.md +1 -5
- package/docs/best-practices/mocking-request.md +10 -7
- package/index.d.ts +1 -0
- package/lib/agent.js +30 -98
- package/lib/api/api-request.js +10 -8
- package/lib/balanced-pool.js +1 -1
- package/lib/client.js +60 -160
- package/lib/core/connect.js +1 -1
- package/lib/core/request.js +23 -2
- package/lib/core/symbols.js +3 -0
- package/lib/core/util.js +9 -4
- package/lib/dispatcher-base.js +159 -0
- package/lib/fetch/body.js +2 -2
- package/lib/fetch/constants.js +3 -0
- package/lib/fetch/dataURL.js +7 -11
- package/lib/fetch/file.js +2 -0
- package/lib/fetch/headers.js +75 -72
- package/lib/fetch/index.js +557 -507
- package/lib/fetch/request.js +22 -21
- package/lib/fetch/response.js +83 -33
- package/lib/fetch/util.js +58 -12
- package/lib/llhttp/llhttp.wasm.js +1 -1
- package/lib/llhttp/llhttp_simd.wasm.js +1 -1
- package/lib/mock/mock-agent.js +38 -3
- package/lib/mock/mock-interceptor.js +26 -16
- package/lib/mock/mock-utils.js +106 -18
- package/lib/mock/pending-interceptors-formatter.js +40 -0
- package/lib/mock/pluralizer.js +29 -0
- package/lib/pool-base.js +23 -101
- package/lib/pool-stats.js +7 -7
- package/lib/proxy-agent.js +32 -7
- package/package.json +16 -14
- package/types/dispatcher.d.ts +2 -1
- package/types/fetch.d.ts +4 -10
- package/types/mock-agent.d.ts +14 -1
- package/types/mock-interceptor.d.ts +5 -5
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/nodejs/undici/actions/workflows/nodejs.yml) [](http://standardjs.com/) [](https://badge.fury.io/js/undici) [](https://codecov.io/gh/nodejs/undici)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
An HTTP/1.1 client, written from scratch for Node.js.
|
|
6
6
|
|
|
7
7
|
> Undici means eleven in Italian. 1.1 -> 11 -> Eleven -> Undici.
|
|
8
8
|
It is also a Stranger Things reference.
|
|
@@ -65,7 +65,15 @@ for await (const data of body) {
|
|
|
65
65
|
console.log('trailers', trailers)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
|
|
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:
|
|
69
77
|
|
|
70
78
|
```js
|
|
71
79
|
import { request } from 'undici'
|
|
@@ -83,6 +91,12 @@ console.log('data', await body.json())
|
|
|
83
91
|
console.log('trailers', trailers)
|
|
84
92
|
```
|
|
85
93
|
|
|
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
|
+
|
|
86
100
|
## Common API Methods
|
|
87
101
|
|
|
88
102
|
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 = {
|
|
|
213
227
|
|
|
214
228
|
#### `response.body`
|
|
215
229
|
|
|
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()`.
|
|
217
231
|
|
|
218
232
|
```js
|
|
219
233
|
import {fetch} from 'undici';
|
|
@@ -228,7 +242,7 @@ Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v1
|
|
|
228
242
|
|
|
229
243
|
#### Specification Compliance
|
|
230
244
|
|
|
231
|
-
This section documents parts of the [Fetch Standard](https://fetch.spec.whatwg.org)
|
|
245
|
+
This section documents parts of the [Fetch Standard](https://fetch.spec.whatwg.org) that Undici does
|
|
232
246
|
not support or does not fully implement.
|
|
233
247
|
|
|
234
248
|
##### Garbage Collection
|
|
@@ -239,7 +253,7 @@ The [Fetch Standard](https://fetch.spec.whatwg.org) allows users to skip consumi
|
|
|
239
253
|
[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.
|
|
240
254
|
|
|
241
255
|
Garbage collection in Node is less aggressive and deterministic
|
|
242
|
-
(due to the lack of clear idle periods that
|
|
256
|
+
(due to the lack of clear idle periods that browsers have through the rendering refresh rate)
|
|
243
257
|
which means that leaving the release of connection resources to the garbage collector can lead
|
|
244
258
|
to excessive connection usage, reduced performance (due to less connection re-use), and even
|
|
245
259
|
stalls or deadlocks when running out of connections.
|
|
@@ -301,7 +315,7 @@ Returns: `Dispatcher`
|
|
|
301
315
|
|
|
302
316
|
## Specification Compliance
|
|
303
317
|
|
|
304
|
-
This section documents parts of the HTTP/1.1 specification
|
|
318
|
+
This section documents parts of the HTTP/1.1 specification that Undici does
|
|
305
319
|
not support or does not fully implement.
|
|
306
320
|
|
|
307
321
|
### Expect
|
|
@@ -334,7 +348,7 @@ aborted.
|
|
|
334
348
|
|
|
335
349
|
### Manual Redirect
|
|
336
350
|
|
|
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,
|
|
338
352
|
Undici returns the actual response instead of an `opaqueredirect` filtered one
|
|
339
353
|
when invoked with a `manual` redirect. This aligns `fetch()` with the other
|
|
340
354
|
implementations in Deno and Cloudflare Workers.
|
package/docs/api/Dispatcher.md
CHANGED
|
@@ -193,7 +193,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
|
|
|
193
193
|
* **path** `string`
|
|
194
194
|
* **method** `string`
|
|
195
195
|
* **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`.
|
|
197
197
|
* **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.
|
|
198
198
|
* **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.
|
|
199
199
|
* **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
|
|
@@ -489,6 +489,8 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
|
|
|
489
489
|
- `body`
|
|
490
490
|
- `bodyUsed`
|
|
491
491
|
|
|
492
|
+
`body` can not be consumed twice. For example, calling `text()` after `json()` throws `TypeError`.
|
|
493
|
+
|
|
492
494
|
`body` contains the following additional extensions:
|
|
493
495
|
|
|
494
496
|
- `dump({ limit: Integer })`, dump the response by reading up to `limit` bytes without killing the socket (optional) - Default: 262144.
|
package/docs/api/MockAgent.md
CHANGED
|
@@ -72,11 +72,7 @@ const mockAgent = new MockAgent()
|
|
|
72
72
|
setGlobalDispatcher(mockAgent)
|
|
73
73
|
|
|
74
74
|
const mockPool = mockAgent.get('http://localhost:3000')
|
|
75
|
-
|
|
76
|
-
mockPool.intercept({
|
|
77
|
-
path: '/foo',
|
|
78
|
-
method: 'GET'
|
|
79
|
-
}).reply(200, 'foo')
|
|
75
|
+
mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
|
|
80
76
|
|
|
81
77
|
const { statusCode, body } = await request('http://localhost:3000/foo')
|
|
82
78
|
|
|
@@ -95,11 +91,7 @@ import { MockAgent, request } from 'undici'
|
|
|
95
91
|
const mockAgent = new MockAgent()
|
|
96
92
|
|
|
97
93
|
const mockPool = mockAgent.get('http://localhost:3000')
|
|
98
|
-
|
|
99
|
-
mockPool.intercept({
|
|
100
|
-
path: '/foo',
|
|
101
|
-
method: 'GET'
|
|
102
|
-
}).reply(200, 'foo')
|
|
94
|
+
mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
|
|
103
95
|
|
|
104
96
|
const {
|
|
105
97
|
statusCode,
|
|
@@ -121,11 +113,7 @@ import { MockAgent, request } from 'undici'
|
|
|
121
113
|
const mockAgent = new MockAgent()
|
|
122
114
|
|
|
123
115
|
const mockPool = mockAgent.get('http://localhost:3000')
|
|
124
|
-
|
|
125
|
-
mockPool.intercept({
|
|
126
|
-
path: '/foo',
|
|
127
|
-
method: 'GET'
|
|
128
|
-
}).reply(200, 'foo')
|
|
116
|
+
mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
|
|
129
117
|
|
|
130
118
|
const {
|
|
131
119
|
statusCode,
|
|
@@ -147,11 +135,7 @@ import { MockAgent, request } from 'undici'
|
|
|
147
135
|
const mockAgent = new MockAgent({ connections: 1 })
|
|
148
136
|
|
|
149
137
|
const mockClient = mockAgent.get('http://localhost:3000')
|
|
150
|
-
|
|
151
|
-
mockClient.intercept({
|
|
152
|
-
path: '/foo',
|
|
153
|
-
method: 'GET'
|
|
154
|
-
}).reply(200, 'foo')
|
|
138
|
+
mockClient.intercept({ path: '/foo' }).reply(200, 'foo')
|
|
155
139
|
|
|
156
140
|
const {
|
|
157
141
|
statusCode,
|
|
@@ -174,16 +158,8 @@ const mockAgent = new MockAgent()
|
|
|
174
158
|
setGlobalDispatcher(mockAgent)
|
|
175
159
|
|
|
176
160
|
const mockPool = mockAgent.get('http://localhost:3000')
|
|
177
|
-
|
|
178
|
-
mockPool.intercept({
|
|
179
|
-
path: '/foo',
|
|
180
|
-
method: 'GET'
|
|
181
|
-
}).reply(200, 'foo')
|
|
182
|
-
|
|
183
|
-
mockPool.intercept({
|
|
184
|
-
path: '/hello',
|
|
185
|
-
method: 'GET'
|
|
186
|
-
}).reply(200, 'hello')
|
|
161
|
+
mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
|
|
162
|
+
mockPool.intercept({ path: '/hello'}).reply(200, 'hello')
|
|
187
163
|
|
|
188
164
|
const result1 = await request('http://localhost:3000/foo')
|
|
189
165
|
|
|
@@ -250,11 +226,7 @@ const mockAgent = new MockAgent()
|
|
|
250
226
|
setGlobalDispatcher(mockAgent)
|
|
251
227
|
|
|
252
228
|
const mockPool = mockAgent.get(new RegExp('http://localhost:3000'))
|
|
253
|
-
|
|
254
|
-
mockPool.intercept({
|
|
255
|
-
path: '/foo',
|
|
256
|
-
method: 'GET',
|
|
257
|
-
}).reply(200, 'foo')
|
|
229
|
+
mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
|
|
258
230
|
|
|
259
231
|
const {
|
|
260
232
|
statusCode,
|
|
@@ -277,11 +249,7 @@ const mockAgent = new MockAgent()
|
|
|
277
249
|
setGlobalDispatcher(mockAgent)
|
|
278
250
|
|
|
279
251
|
const mockPool = mockAgent.get((origin) => origin === 'http://localhost:3000')
|
|
280
|
-
|
|
281
|
-
mockPool.intercept({
|
|
282
|
-
path: '/foo',
|
|
283
|
-
method: 'GET'
|
|
284
|
-
}).reply(200, 'foo')
|
|
252
|
+
mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
|
|
285
253
|
|
|
286
254
|
const {
|
|
287
255
|
statusCode,
|
|
@@ -328,11 +296,7 @@ import { MockAgent } from 'undici'
|
|
|
328
296
|
const mockAgent = new MockAgent()
|
|
329
297
|
|
|
330
298
|
const mockPool = mockAgent.get('http://localhost:3000')
|
|
331
|
-
|
|
332
|
-
mockPool.intercept({
|
|
333
|
-
path: '/foo',
|
|
334
|
-
method: 'GET'
|
|
335
|
-
}).reply(200, 'foo')
|
|
299
|
+
mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
|
|
336
300
|
|
|
337
301
|
const {
|
|
338
302
|
statusCode,
|
|
@@ -481,3 +445,79 @@ mockAgent.disableNetConnect()
|
|
|
481
445
|
await request('http://example.com')
|
|
482
446
|
// Will throw
|
|
483
447
|
```
|
|
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
|
+
```
|
package/docs/api/MockClient.md
CHANGED
|
@@ -58,10 +58,7 @@ import { MockAgent } from 'undici'
|
|
|
58
58
|
const mockAgent = new MockAgent({ connections: 1 })
|
|
59
59
|
|
|
60
60
|
const mockClient = mockAgent.get('http://localhost:3000')
|
|
61
|
-
mockClient.intercept({
|
|
62
|
-
path: '/foo',
|
|
63
|
-
method: 'GET',
|
|
64
|
-
}).reply(200, 'foo')
|
|
61
|
+
mockClient.intercept({ path: '/foo' }).reply(200, 'foo')
|
|
65
62
|
|
|
66
63
|
const {
|
|
67
64
|
statusCode,
|
package/docs/api/MockPool.md
CHANGED
|
@@ -95,11 +95,7 @@ setGlobalDispatcher(mockAgent)
|
|
|
95
95
|
|
|
96
96
|
// MockPool
|
|
97
97
|
const mockPool = mockAgent.get('http://localhost:3000')
|
|
98
|
-
|
|
99
|
-
mockPool.intercept({
|
|
100
|
-
path: '/foo',
|
|
101
|
-
method: 'GET',
|
|
102
|
-
}).reply(200, 'foo')
|
|
98
|
+
mockPool.intercept({ path: '/foo' }).reply(200, 'foo')
|
|
103
99
|
|
|
104
100
|
const {
|
|
105
101
|
statusCode,
|
|
@@ -5,17 +5,20 @@ Undici have its own mocking [utility](../api/MockAgent.md). It allow us to inter
|
|
|
5
5
|
Example:
|
|
6
6
|
|
|
7
7
|
```js
|
|
8
|
-
//
|
|
8
|
+
// bank.mjs
|
|
9
9
|
import { request } from 'undici'
|
|
10
10
|
|
|
11
|
-
export async function bankTransfer(recepient,
|
|
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',
|
|
13
13
|
{
|
|
14
14
|
method: 'POST',
|
|
15
15
|
headers: {
|
|
16
16
|
'X-TOKEN-SECRET': 'SuperSecretToken',
|
|
17
17
|
},
|
|
18
|
-
body: JSON.stringify({
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
recepient,
|
|
20
|
+
amount
|
|
21
|
+
})
|
|
19
22
|
}
|
|
20
23
|
)
|
|
21
24
|
return await body.json()
|
|
@@ -28,7 +31,7 @@ And this is what the test file looks like:
|
|
|
28
31
|
// index.test.mjs
|
|
29
32
|
import { strict as assert } from 'assert'
|
|
30
33
|
import { MockAgent, setGlobalDispatcher, } from 'undici'
|
|
31
|
-
import { bankTransfer } from './
|
|
34
|
+
import { bankTransfer } from './bank.mjs'
|
|
32
35
|
|
|
33
36
|
const mockAgent = new MockAgent();
|
|
34
37
|
|
|
@@ -46,7 +49,7 @@ mockPool.intercept({
|
|
|
46
49
|
},
|
|
47
50
|
body: JSON.stringify({
|
|
48
51
|
recepient: '1234567890',
|
|
49
|
-
|
|
52
|
+
amount: '100'
|
|
50
53
|
})
|
|
51
54
|
}).reply(200, {
|
|
52
55
|
message: 'transaction processed'
|
|
@@ -94,7 +97,7 @@ mockPool.intercept({
|
|
|
94
97
|
|
|
95
98
|
const badRequest = await bankTransfer('1234567890', '100')
|
|
96
99
|
// Will throw an error
|
|
97
|
-
// MockNotMatchedError: Mock dispatch not matched for path '/bank-transfer':
|
|
100
|
+
// MockNotMatchedError: Mock dispatch not matched for path '/bank-transfer':
|
|
98
101
|
// subsequent request to origin http://localhost:3000 was not allowed (net.connect disabled)
|
|
99
102
|
```
|
|
100
103
|
|
package/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { request, pipeline, stream, connect, upgrade } from './types/api'
|
|
|
16
16
|
export * from './types/fetch'
|
|
17
17
|
export * from './types/file'
|
|
18
18
|
export * from './types/formdata'
|
|
19
|
+
export { Interceptable } from './types/mock-interceptor'
|
|
19
20
|
|
|
20
21
|
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent }
|
|
21
22
|
export default Undici
|
package/lib/agent.js
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
ClientDestroyedError
|
|
7
|
-
} = require('./core/errors')
|
|
8
|
-
const { kClients, kRunning } = require('./core/symbols')
|
|
9
|
-
const Dispatcher = require('./dispatcher')
|
|
3
|
+
const { InvalidArgumentError } = require('./core/errors')
|
|
4
|
+
const { kClients, kRunning, kClose, kDestroy, kDispatch } = require('./core/symbols')
|
|
5
|
+
const DispatcherBase = require('./dispatcher-base')
|
|
10
6
|
const Pool = require('./pool')
|
|
11
7
|
const Client = require('./client')
|
|
12
8
|
const util = require('./core/util')
|
|
13
9
|
const RedirectHandler = require('./handler/redirect')
|
|
14
10
|
const { WeakRef, FinalizationRegistry } = require('./compat/dispatcher-weakref')()
|
|
15
11
|
|
|
16
|
-
const kDestroyed = Symbol('destroyed')
|
|
17
|
-
const kClosed = Symbol('closed')
|
|
18
12
|
const kOnConnect = Symbol('onConnect')
|
|
19
13
|
const kOnDisconnect = Symbol('onDisconnect')
|
|
20
14
|
const kOnConnectionError = Symbol('onConnectionError')
|
|
@@ -30,7 +24,7 @@ function defaultFactory (origin, opts) {
|
|
|
30
24
|
: new Pool(origin, opts)
|
|
31
25
|
}
|
|
32
26
|
|
|
33
|
-
class Agent extends
|
|
27
|
+
class Agent extends DispatcherBase {
|
|
34
28
|
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
|
35
29
|
super()
|
|
36
30
|
|
|
@@ -60,8 +54,6 @@ class Agent extends Dispatcher {
|
|
|
60
54
|
this[kClients].delete(key)
|
|
61
55
|
}
|
|
62
56
|
})
|
|
63
|
-
this[kClosed] = false
|
|
64
|
-
this[kDestroyed] = false
|
|
65
57
|
|
|
66
58
|
const agent = this
|
|
67
59
|
|
|
@@ -94,76 +86,38 @@ class Agent extends Dispatcher {
|
|
|
94
86
|
return ret
|
|
95
87
|
}
|
|
96
88
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
89
|
+
[kDispatch] (opts, handler) {
|
|
90
|
+
let key
|
|
91
|
+
if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) {
|
|
92
|
+
key = String(opts.origin)
|
|
93
|
+
} else {
|
|
94
|
+
throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
|
|
100
95
|
}
|
|
101
96
|
|
|
102
|
-
|
|
103
|
-
if (!opts || typeof opts !== 'object') {
|
|
104
|
-
throw new InvalidArgumentError('opts must be an object.')
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
let key
|
|
108
|
-
if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) {
|
|
109
|
-
key = String(opts.origin)
|
|
110
|
-
} else {
|
|
111
|
-
throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (this[kDestroyed]) {
|
|
115
|
-
throw new ClientDestroyedError()
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (this[kClosed]) {
|
|
119
|
-
throw new ClientClosedError()
|
|
120
|
-
}
|
|
97
|
+
const ref = this[kClients].get(key)
|
|
121
98
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
.on('disconnect', this[kOnDisconnect])
|
|
130
|
-
.on('connectionError', this[kOnConnectionError])
|
|
131
|
-
|
|
132
|
-
this[kClients].set(key, new WeakRef(dispatcher))
|
|
133
|
-
this[kFinalizer].register(dispatcher, key)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const { maxRedirections = this[kMaxRedirections] } = opts
|
|
137
|
-
if (maxRedirections != null && maxRedirections !== 0) {
|
|
138
|
-
opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
|
|
139
|
-
handler = new RedirectHandler(this, maxRedirections, opts, handler)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return dispatcher.dispatch(opts, handler)
|
|
143
|
-
} catch (err) {
|
|
144
|
-
if (typeof handler.onError !== 'function') {
|
|
145
|
-
throw new InvalidArgumentError('invalid onError method')
|
|
146
|
-
}
|
|
99
|
+
let dispatcher = ref ? ref.deref() : null
|
|
100
|
+
if (!dispatcher) {
|
|
101
|
+
dispatcher = this[kFactory](opts.origin, this[kOptions])
|
|
102
|
+
.on('drain', this[kOnDrain])
|
|
103
|
+
.on('connect', this[kOnConnect])
|
|
104
|
+
.on('disconnect', this[kOnDisconnect])
|
|
105
|
+
.on('connectionError', this[kOnConnectionError])
|
|
147
106
|
|
|
148
|
-
|
|
107
|
+
this[kClients].set(key, new WeakRef(dispatcher))
|
|
108
|
+
this[kFinalizer].register(dispatcher, key)
|
|
149
109
|
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
get closed () {
|
|
153
|
-
return this[kClosed]
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
get destroyed () {
|
|
157
|
-
return this[kDestroyed]
|
|
158
|
-
}
|
|
159
110
|
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
111
|
+
const { maxRedirections = this[kMaxRedirections] } = opts
|
|
112
|
+
if (maxRedirections != null && maxRedirections !== 0) {
|
|
113
|
+
opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
|
|
114
|
+
handler = new RedirectHandler(this, maxRedirections, opts, handler)
|
|
163
115
|
}
|
|
164
116
|
|
|
165
|
-
|
|
117
|
+
return dispatcher.dispatch(opts, handler)
|
|
118
|
+
}
|
|
166
119
|
|
|
120
|
+
async [kClose] () {
|
|
167
121
|
const closePromises = []
|
|
168
122
|
for (const ref of this[kClients].values()) {
|
|
169
123
|
const client = ref.deref()
|
|
@@ -173,27 +127,10 @@ class Agent extends Dispatcher {
|
|
|
173
127
|
}
|
|
174
128
|
}
|
|
175
129
|
|
|
176
|
-
|
|
177
|
-
return Promise.all(closePromises)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Should never error.
|
|
181
|
-
Promise.all(closePromises).then(() => process.nextTick(callback))
|
|
130
|
+
await Promise.all(closePromises)
|
|
182
131
|
}
|
|
183
132
|
|
|
184
|
-
|
|
185
|
-
if (typeof err === 'function') {
|
|
186
|
-
callback = err
|
|
187
|
-
err = null
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (callback != null && typeof callback !== 'function') {
|
|
191
|
-
throw new InvalidArgumentError('callback must be a function')
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
this[kClosed] = true
|
|
195
|
-
this[kDestroyed] = true
|
|
196
|
-
|
|
133
|
+
async [kDestroy] (err) {
|
|
197
134
|
const destroyPromises = []
|
|
198
135
|
for (const ref of this[kClients].values()) {
|
|
199
136
|
const client = ref.deref()
|
|
@@ -203,12 +140,7 @@ class Agent extends Dispatcher {
|
|
|
203
140
|
}
|
|
204
141
|
}
|
|
205
142
|
|
|
206
|
-
|
|
207
|
-
return Promise.all(destroyPromises)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Should never error.
|
|
211
|
-
Promise.all(destroyPromises).then(() => process.nextTick(callback))
|
|
143
|
+
await Promise.all(destroyPromises)
|
|
212
144
|
}
|
|
213
145
|
}
|
|
214
146
|
|
package/lib/api/api-request.js
CHANGED
|
@@ -88,14 +88,16 @@ class RequestHandler extends AsyncResource {
|
|
|
88
88
|
this.res = body
|
|
89
89
|
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
+
}
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
onData (chunk) {
|
package/lib/balanced-pool.js
CHANGED