undici 7.0.0-alpha.2 → 7.0.0-alpha.4
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 +3 -2
- package/docs/docs/api/BalancedPool.md +1 -1
- package/docs/docs/api/CacheStore.md +100 -0
- package/docs/docs/api/Dispatcher.md +32 -2
- package/docs/docs/api/MockClient.md +1 -1
- package/docs/docs/api/Pool.md +1 -1
- package/docs/docs/api/api-lifecycle.md +2 -2
- package/docs/docs/best-practices/mocking-request.md +2 -2
- package/docs/docs/best-practices/proxy.md +1 -1
- package/index.d.ts +1 -1
- package/index.js +8 -2
- package/lib/api/api-request.js +2 -2
- package/lib/api/readable.js +6 -6
- package/lib/cache/memory-cache-store.js +325 -0
- package/lib/core/connect.js +5 -0
- package/lib/core/constants.js +24 -1
- package/lib/core/request.js +2 -2
- package/lib/core/util.js +13 -1
- package/lib/dispatcher/client-h1.js +100 -87
- package/lib/dispatcher/client-h2.js +168 -96
- package/lib/dispatcher/pool-base.js +3 -3
- package/lib/handler/cache-handler.js +389 -0
- package/lib/handler/cache-revalidation-handler.js +151 -0
- package/lib/handler/redirect-handler.js +5 -3
- package/lib/handler/retry-handler.js +3 -3
- package/lib/interceptor/cache.js +192 -0
- package/lib/interceptor/dns.js +71 -48
- package/lib/util/cache.js +249 -0
- package/lib/web/cache/cache.js +1 -0
- package/lib/web/cache/cachestorage.js +2 -0
- package/lib/web/cookies/index.js +12 -1
- package/lib/web/cookies/parse.js +6 -1
- package/lib/web/eventsource/eventsource.js +2 -0
- package/lib/web/fetch/body.js +1 -5
- package/lib/web/fetch/constants.js +12 -5
- package/lib/web/fetch/data-url.js +2 -2
- package/lib/web/fetch/formdata-parser.js +70 -43
- package/lib/web/fetch/formdata.js +3 -1
- package/lib/web/fetch/headers.js +3 -1
- package/lib/web/fetch/index.js +4 -6
- package/lib/web/fetch/request.js +3 -1
- package/lib/web/fetch/response.js +3 -1
- package/lib/web/fetch/util.js +171 -47
- package/lib/web/fetch/webidl.js +28 -16
- package/lib/web/websocket/constants.js +67 -6
- package/lib/web/websocket/events.js +4 -0
- package/lib/web/websocket/stream/websocketerror.js +1 -1
- package/lib/web/websocket/websocket.js +2 -0
- package/package.json +8 -5
- package/types/cache-interceptor.d.ts +101 -0
- package/types/cookies.d.ts +2 -0
- package/types/dispatcher.d.ts +1 -1
- package/types/fetch.d.ts +9 -8
- package/types/index.d.ts +3 -1
- package/types/interceptors.d.ts +4 -1
- package/types/webidl.d.ts +7 -1
package/README.md
CHANGED
|
@@ -132,7 +132,7 @@ Returns a promise with the result of the `Dispatcher.request` method.
|
|
|
132
132
|
|
|
133
133
|
Calls `options.dispatcher.request(options)`.
|
|
134
134
|
|
|
135
|
-
See [Dispatcher.request](./docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback) for more details, and [request examples](./examples/README.md) for examples.
|
|
135
|
+
See [Dispatcher.request](./docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback) for more details, and [request examples](./docs/examples/README.md) for examples.
|
|
136
136
|
|
|
137
137
|
### `undici.stream([url, options, ]factory): Promise`
|
|
138
138
|
|
|
@@ -402,7 +402,8 @@ Refs: https://tools.ietf.org/html/rfc7231#section-5.1.1
|
|
|
402
402
|
### Pipelining
|
|
403
403
|
|
|
404
404
|
Undici will only use pipelining if configured with a `pipelining` factor
|
|
405
|
-
greater than `1`.
|
|
405
|
+
greater than `1`. Also it is important to pass `blocking: false` to the
|
|
406
|
+
request options to properly pipeline requests.
|
|
406
407
|
|
|
407
408
|
Undici always assumes that connections are persistent and will immediately
|
|
408
409
|
pipeline requests, without checking whether the connection is persistent.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Cache Store
|
|
2
|
+
|
|
3
|
+
A Cache Store is responsible for storing and retrieving cached responses.
|
|
4
|
+
It is also responsible for deciding which specific response to use based off of
|
|
5
|
+
a response's `Vary` header (if present). It is expected to be compliant with
|
|
6
|
+
[RFC-9111](https://www.rfc-editor.org/rfc/rfc9111.html).
|
|
7
|
+
|
|
8
|
+
## Pre-built Cache Stores
|
|
9
|
+
|
|
10
|
+
### `MemoryCacheStore`
|
|
11
|
+
|
|
12
|
+
The `MemoryCacheStore` stores the responses in-memory.
|
|
13
|
+
|
|
14
|
+
**Options**
|
|
15
|
+
|
|
16
|
+
- `maxEntries` - The maximum amount of responses to store. Default `Infinity`.
|
|
17
|
+
- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached.
|
|
18
|
+
|
|
19
|
+
## Defining a Custom Cache Store
|
|
20
|
+
|
|
21
|
+
The store must implement the following functions:
|
|
22
|
+
|
|
23
|
+
### Getter: `isFull`
|
|
24
|
+
|
|
25
|
+
Optional. This tells the cache interceptor if the store is full or not. If this is true,
|
|
26
|
+
the cache interceptor will not attempt to cache the response.
|
|
27
|
+
|
|
28
|
+
### Function: `get`
|
|
29
|
+
|
|
30
|
+
Parameters:
|
|
31
|
+
|
|
32
|
+
* **req** `Dispatcher.RequestOptions` - Incoming request
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
If the request isn't cached, `undefined` is returned.
|
|
36
|
+
|
|
37
|
+
Response properties:
|
|
38
|
+
|
|
39
|
+
* **response** `CachedResponse` - The cached response data.
|
|
40
|
+
* **body** `Readable | undefined` - The response's body.
|
|
41
|
+
|
|
42
|
+
### Function: `createWriteStream`
|
|
43
|
+
|
|
44
|
+
Parameters:
|
|
45
|
+
|
|
46
|
+
* **req** `Dispatcher.RequestOptions` - Incoming request
|
|
47
|
+
* **value** `CachedResponse` - Response to store
|
|
48
|
+
|
|
49
|
+
Returns: `Writable | undefined` - If the store is full, return `undefined`. Otherwise, return a writable so that the cache interceptor can stream the body and trailers to the store.
|
|
50
|
+
|
|
51
|
+
## `CachedResponse`
|
|
52
|
+
|
|
53
|
+
This is an interface containing the majority of a response's data (minus the body).
|
|
54
|
+
|
|
55
|
+
### Property `statusCode`
|
|
56
|
+
|
|
57
|
+
`number` - The response's HTTP status code.
|
|
58
|
+
|
|
59
|
+
### Property `statusMessage`
|
|
60
|
+
|
|
61
|
+
`string` - The response's HTTP status message.
|
|
62
|
+
|
|
63
|
+
### Property `rawHeaders`
|
|
64
|
+
|
|
65
|
+
`Buffer[]` - The response's headers.
|
|
66
|
+
|
|
67
|
+
### Property `vary`
|
|
68
|
+
|
|
69
|
+
`Record<string, string | string[]> | undefined` - The headers defined by the response's `Vary` header
|
|
70
|
+
and their respective values for later comparison
|
|
71
|
+
|
|
72
|
+
For example, for a response like
|
|
73
|
+
```
|
|
74
|
+
Vary: content-encoding, accepts
|
|
75
|
+
content-encoding: utf8
|
|
76
|
+
accepts: application/json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This would be
|
|
80
|
+
```js
|
|
81
|
+
{
|
|
82
|
+
'content-encoding': 'utf8',
|
|
83
|
+
accepts: 'application/json'
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Property `cachedAt`
|
|
88
|
+
|
|
89
|
+
`number` - Time in millis that this value was cached.
|
|
90
|
+
|
|
91
|
+
### Property `staleAt`
|
|
92
|
+
|
|
93
|
+
`number` - Time in millis that this value is considered stale.
|
|
94
|
+
|
|
95
|
+
### Property `deleteAt`
|
|
96
|
+
|
|
97
|
+
`number` - Time in millis that this value is to be deleted from the cache. This
|
|
98
|
+
is either the same sa staleAt or the `max-stale` caching directive.
|
|
99
|
+
|
|
100
|
+
The store must not return a response after the time defined in this property.
|
|
@@ -197,7 +197,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
|
|
|
197
197
|
* **headers** `UndiciHeaders | string[]` (optional) - Default: `null`.
|
|
198
198
|
* **query** `Record<string, any> | null` (optional) - Default: `null` - Query string params to be embedded in the request URL. Note that both keys and values of query are encoded using `encodeURIComponent`. If for some reason you need to send them unencoded, embed query params into path directly instead.
|
|
199
199
|
* **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.
|
|
200
|
-
* **blocking** `boolean` (optional) - Default: `
|
|
200
|
+
* **blocking** `boolean` (optional) - Default: `method !== 'HEAD'` - 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.
|
|
201
201
|
* **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
|
|
202
202
|
* **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds.
|
|
203
203
|
* **headersTimeout** `number | null` (optional) - The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds.
|
|
@@ -527,6 +527,7 @@ try {
|
|
|
527
527
|
console.log('headers', headers)
|
|
528
528
|
body.setEncoding('utf8')
|
|
529
529
|
body.on('data', console.log)
|
|
530
|
+
body.on('error', console.error)
|
|
530
531
|
body.on('end', () => {
|
|
531
532
|
console.log('trailers', trailers)
|
|
532
533
|
})
|
|
@@ -630,6 +631,25 @@ try {
|
|
|
630
631
|
}
|
|
631
632
|
```
|
|
632
633
|
|
|
634
|
+
#### Example 3 - Conditionally reading the body
|
|
635
|
+
|
|
636
|
+
Remember to fully consume the body even in the case when it is not read.
|
|
637
|
+
|
|
638
|
+
```js
|
|
639
|
+
const { body, statusCode } = await client.request({
|
|
640
|
+
path: '/',
|
|
641
|
+
method: 'GET'
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
if (statusCode === 200) {
|
|
645
|
+
return await body.arrayBuffer()
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
await body.dump()
|
|
649
|
+
|
|
650
|
+
return null
|
|
651
|
+
```
|
|
652
|
+
|
|
633
653
|
### `Dispatcher.stream(options, factory[, callback])`
|
|
634
654
|
|
|
635
655
|
A faster version of `Dispatcher.request`. This method expects the second argument `factory` to return a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) stream which the response will be written to. This improves performance by avoiding creating an intermediate [`stream.Readable`](https://nodejs.org/api/stream.html#stream_readable_streams) stream when the user expects to directly pipe the response body to a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) stream.
|
|
@@ -1001,7 +1021,7 @@ The `dns` interceptor enables you to cache DNS lookups for a given duration, per
|
|
|
1001
1021
|
- It can be either `'4` or `6`.
|
|
1002
1022
|
- It will only take effect if `dualStack` is `false`.
|
|
1003
1023
|
- `lookup: (hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, addresses: DNSInterceptorRecord[]) => void) => void` - Custom lookup function. Default: `dns.lookup`.
|
|
1004
|
-
- For more info see [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback).
|
|
1024
|
+
- For more info see [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback).
|
|
1005
1025
|
- `pick: (origin: URL, records: DNSInterceptorRecords, affinity: 4 | 6) => DNSInterceptorRecord` - Custom pick function. Default: `RoundRobin`.
|
|
1006
1026
|
- The function should return a single record from the records array.
|
|
1007
1027
|
- By default a simplified version of Round Robin is used.
|
|
@@ -1233,6 +1253,16 @@ test('should not error if request status code is not in the specified error code
|
|
|
1233
1253
|
|
|
1234
1254
|
The Response Error Interceptor provides a robust mechanism for handling HTTP response errors by capturing detailed error information and propagating it through a structured `ResponseError` class. This enhancement improves error handling and debugging capabilities in applications using the interceptor.
|
|
1235
1255
|
|
|
1256
|
+
##### `Cache Interceptor`
|
|
1257
|
+
|
|
1258
|
+
The `cache` interceptor implements client-side response caching as described in
|
|
1259
|
+
[RFC9111](https://www.rfc-editor.org/rfc/rfc9111.html).
|
|
1260
|
+
|
|
1261
|
+
**Options**
|
|
1262
|
+
|
|
1263
|
+
- `store` - The [`CacheStore`](./CacheStore.md) to store and retrieve responses from. Default is [`MemoryCacheStore`](./CacheStore.md#memorycachestore).
|
|
1264
|
+
- `methods` - The [**safe** HTTP methods](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1) to cache the response of.
|
|
1265
|
+
|
|
1236
1266
|
## Instance Events
|
|
1237
1267
|
|
|
1238
1268
|
### Event: `'connect'`
|
package/docs/docs/api/Pool.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Extends: `undici.Dispatcher`
|
|
4
4
|
|
|
5
|
-
A pool of [Client](Client.md) instances connected to the same upstream target.
|
|
5
|
+
A pool of [Client](/docs/docs/api/Client.md) instances connected to the same upstream target.
|
|
6
6
|
|
|
7
7
|
Requests are not guaranteed to be dispatched in order of invocation.
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Client Lifecycle
|
|
2
2
|
|
|
3
|
-
An Undici [Client](Client.md) can be best described as a state machine. The following list is a summary of the various state transitions the `Client` will go through in its lifecycle. This document also contains detailed breakdowns of each state.
|
|
3
|
+
An Undici [Client](/docs/docs/api/Client.md) can be best described as a state machine. The following list is a summary of the various state transitions the `Client` will go through in its lifecycle. This document also contains detailed breakdowns of each state.
|
|
4
4
|
|
|
5
5
|
> This diagram is not a perfect representation of the undici Client. Since the Client class is not actually implemented as a state-machine, actual execution may deviate slightly from what is described below. Consider this as a general resource for understanding the inner workings of the Undici client rather than some kind of formal specification.
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ stateDiagram-v2
|
|
|
28
28
|
[*] --> idle
|
|
29
29
|
idle --> pending : connect
|
|
30
30
|
idle --> destroyed : destroy/close
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
pending --> idle : timeout
|
|
33
33
|
pending --> destroyed : destroy
|
|
34
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mocking Request
|
|
2
2
|
|
|
3
|
-
Undici has its own mocking [utility](
|
|
3
|
+
Undici has its own mocking [utility](/docs/docs/api/MockAgent.md). It allow us to intercept undici HTTP requests and return mocked values instead. It can be useful for testing purposes.
|
|
4
4
|
|
|
5
5
|
Example:
|
|
6
6
|
|
|
@@ -73,7 +73,7 @@ const badRequest = await bankTransfer('1234567890', '100')
|
|
|
73
73
|
assert.deepEqual(badRequest, { message: 'bank account not found' })
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
Explore other MockAgent functionality [here](
|
|
76
|
+
Explore other MockAgent functionality [here](/docs/docs/api/MockAgent.md)
|
|
77
77
|
|
|
78
78
|
## Debug Mock Value
|
|
79
79
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Connecting through a proxy is possible by:
|
|
4
4
|
|
|
5
|
-
- Using [ProxyAgent](
|
|
5
|
+
- Using [ProxyAgent](/docs/docs/api/ProxyAgent.md).
|
|
6
6
|
- Configuring `Client` or `Pool` constructor.
|
|
7
7
|
|
|
8
8
|
The proxy url should be passed to the `Client` or `Pool` constructor, while the upstream server url
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -40,7 +40,12 @@ module.exports.interceptors = {
|
|
|
40
40
|
redirect: require('./lib/interceptor/redirect'),
|
|
41
41
|
retry: require('./lib/interceptor/retry'),
|
|
42
42
|
dump: require('./lib/interceptor/dump'),
|
|
43
|
-
dns: require('./lib/interceptor/dns')
|
|
43
|
+
dns: require('./lib/interceptor/dns'),
|
|
44
|
+
cache: require('./lib/interceptor/cache')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports.cacheStores = {
|
|
48
|
+
MemoryCacheStore: require('./lib/cache/memory-cache-store')
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
module.exports.buildConnector = buildConnector
|
|
@@ -131,12 +136,13 @@ const { kConstruct } = require('./lib/core/symbols')
|
|
|
131
136
|
// in an older version of Node, it doesn't have any use without fetch.
|
|
132
137
|
module.exports.caches = new CacheStorage(kConstruct)
|
|
133
138
|
|
|
134
|
-
const { deleteCookie, getCookies, getSetCookies, setCookie } = require('./lib/web/cookies')
|
|
139
|
+
const { deleteCookie, getCookies, getSetCookies, setCookie, parseCookie } = require('./lib/web/cookies')
|
|
135
140
|
|
|
136
141
|
module.exports.deleteCookie = deleteCookie
|
|
137
142
|
module.exports.getCookies = getCookies
|
|
138
143
|
module.exports.getSetCookies = getSetCookies
|
|
139
144
|
module.exports.setCookie = setCookie
|
|
145
|
+
module.exports.parseCookie = parseCookie
|
|
140
146
|
|
|
141
147
|
const { parseMIMEType, serializeAMimeType } = require('./lib/web/fetch/data-url')
|
|
142
148
|
|
package/lib/api/api-request.js
CHANGED
|
@@ -65,7 +65,7 @@ class RequestHandler extends AsyncResource {
|
|
|
65
65
|
this.removeAbortListener = util.addAbortListener(signal, () => {
|
|
66
66
|
this.reason = signal.reason ?? new RequestAbortedError()
|
|
67
67
|
if (this.res) {
|
|
68
|
-
util.destroy(this.res, this.reason)
|
|
68
|
+
util.destroy(this.res.on('error', noop), this.reason)
|
|
69
69
|
} else if (this.abort) {
|
|
70
70
|
this.abort(this.reason)
|
|
71
71
|
}
|
|
@@ -153,7 +153,7 @@ class RequestHandler extends AsyncResource {
|
|
|
153
153
|
this.res = null
|
|
154
154
|
// Ensure all queued handlers are invoked before destroying res.
|
|
155
155
|
queueMicrotask(() => {
|
|
156
|
-
util.destroy(res, err)
|
|
156
|
+
util.destroy(res.on('error', noop), err)
|
|
157
157
|
})
|
|
158
158
|
}
|
|
159
159
|
|
package/lib/api/readable.js
CHANGED
|
@@ -164,7 +164,7 @@ class BodyReadable extends Readable {
|
|
|
164
164
|
* @see https://fetch.spec.whatwg.org/#dom-body-text
|
|
165
165
|
* @returns {Promise<string>}
|
|
166
166
|
*/
|
|
167
|
-
|
|
167
|
+
text () {
|
|
168
168
|
return consume(this, 'text')
|
|
169
169
|
}
|
|
170
170
|
|
|
@@ -174,7 +174,7 @@ class BodyReadable extends Readable {
|
|
|
174
174
|
* @see https://fetch.spec.whatwg.org/#dom-body-json
|
|
175
175
|
* @returns {Promise<unknown>}
|
|
176
176
|
*/
|
|
177
|
-
|
|
177
|
+
json () {
|
|
178
178
|
return consume(this, 'json')
|
|
179
179
|
}
|
|
180
180
|
|
|
@@ -184,7 +184,7 @@ class BodyReadable extends Readable {
|
|
|
184
184
|
* @see https://fetch.spec.whatwg.org/#dom-body-blob
|
|
185
185
|
* @returns {Promise<Blob>}
|
|
186
186
|
*/
|
|
187
|
-
|
|
187
|
+
blob () {
|
|
188
188
|
return consume(this, 'blob')
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -194,7 +194,7 @@ class BodyReadable extends Readable {
|
|
|
194
194
|
* @see https://fetch.spec.whatwg.org/#dom-body-bytes
|
|
195
195
|
* @returns {Promise<Uint8Array>}
|
|
196
196
|
*/
|
|
197
|
-
|
|
197
|
+
bytes () {
|
|
198
198
|
return consume(this, 'bytes')
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -204,7 +204,7 @@ class BodyReadable extends Readable {
|
|
|
204
204
|
* @see https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
|
205
205
|
* @returns {Promise<ArrayBuffer>}
|
|
206
206
|
*/
|
|
207
|
-
|
|
207
|
+
arrayBuffer () {
|
|
208
208
|
return consume(this, 'arrayBuffer')
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -355,7 +355,7 @@ function isUnusable (bodyReadable) {
|
|
|
355
355
|
* @param {string} type
|
|
356
356
|
* @returns {Promise<any>}
|
|
357
357
|
*/
|
|
358
|
-
|
|
358
|
+
function consume (stream, type) {
|
|
359
359
|
assert(!stream[kConsume])
|
|
360
360
|
|
|
361
361
|
return new Promise((resolve, reject) => {
|