undici 7.0.0 → 7.1.1
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 +27 -9
- package/docs/docs/api/Client.md +11 -2
- package/docs/docs/api/ProxyAgent.md +98 -2
- package/index.js +2 -8
- package/lib/cache/sqlite-cache-store.js +5 -1
- package/lib/dispatcher/client-h2.js +9 -10
- package/lib/handler/decorator-handler.js +23 -27
- package/lib/handler/wrap-handler.js +3 -6
- package/lib/interceptor/cache.js +5 -2
- package/lib/interceptor/dns.js +9 -9
- package/lib/interceptor/dump.js +29 -41
- package/lib/interceptor/response-error.js +17 -19
- package/lib/web/fetch/data-url.js +2 -2
- package/package.json +3 -3
- package/types/index.d.ts +2 -1
package/README.md
CHANGED
|
@@ -281,17 +281,23 @@ stalls or deadlocks when running out of connections.
|
|
|
281
281
|
|
|
282
282
|
```js
|
|
283
283
|
// Do
|
|
284
|
-
const headers = await fetch(url)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
return res.headers
|
|
290
|
-
})
|
|
284
|
+
const { body, headers } = await fetch(url);
|
|
285
|
+
for await (const chunk of body) {
|
|
286
|
+
// force consumption of body
|
|
287
|
+
}
|
|
291
288
|
|
|
292
289
|
// Do not
|
|
293
|
-
const headers = await fetch(url)
|
|
294
|
-
|
|
290
|
+
const { headers } = await fetch(url);
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The same applies for `request` too:
|
|
294
|
+
```js
|
|
295
|
+
// Do
|
|
296
|
+
const { body, headers } = await request(url);
|
|
297
|
+
await res.body.dump(); // force consumption of body
|
|
298
|
+
|
|
299
|
+
// Do not
|
|
300
|
+
const { headers } = await request(url);
|
|
295
301
|
```
|
|
296
302
|
|
|
297
303
|
However, if you want to get only headers, it might be better to use `HEAD` request method. Usage of this method will obviate the need for consumption or cancelling of the response body. See [MDN - HTTP - HTTP request methods - HEAD](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) for more details.
|
|
@@ -434,6 +440,8 @@ and `undici.Agent`) which will enable the family autoselection algorithm when es
|
|
|
434
440
|
* [__Matthew Aitken__](https://github.com/KhafraDev), <https://www.npmjs.com/~khaf>
|
|
435
441
|
* [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag>
|
|
436
442
|
* [__Szymon Marczak__](https://github.com/szmarczak), <https://www.npmjs.com/~szmarczak>
|
|
443
|
+
|
|
444
|
+
## Past Collaborators
|
|
437
445
|
* [__Tomas Della Vedova__](https://github.com/delvedor), <https://www.npmjs.com/~delvedor>
|
|
438
446
|
|
|
439
447
|
### Releasers
|
|
@@ -443,6 +451,16 @@ and `undici.Agent`) which will enable the family autoselection algorithm when es
|
|
|
443
451
|
* [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag>
|
|
444
452
|
* [__Matthew Aitken__](https://github.com/KhafraDev), <https://www.npmjs.com/~khaf>
|
|
445
453
|
|
|
454
|
+
## Long Term Support
|
|
455
|
+
|
|
456
|
+
Undici aligns with the Node.js LTS schedule. The following table shows the supported versions:
|
|
457
|
+
|
|
458
|
+
| Version | Node.js | End of Life |
|
|
459
|
+
|---------|-------------|-------------|
|
|
460
|
+
| 5.x | v18.x | 2024-04-30 |
|
|
461
|
+
| 6.x | v20.x v22.x | 2026-04-30 |
|
|
462
|
+
| 7.x | v24.x | 2027-04-30 |
|
|
463
|
+
|
|
446
464
|
## License
|
|
447
465
|
|
|
448
466
|
MIT
|
package/docs/docs/api/Client.md
CHANGED
|
@@ -17,8 +17,6 @@ Returns: `Client`
|
|
|
17
17
|
|
|
18
18
|
### Parameter: `ClientOptions`
|
|
19
19
|
|
|
20
|
-
> ⚠️ Warning: The `H2` support is experimental.
|
|
21
|
-
|
|
22
20
|
* **bodyTimeout** `number | null` (optional) - Default: `300e3` - 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. Please note the `timeout` will be reset if you keep writing data to the socket everytime.
|
|
23
21
|
* **headersTimeout** `number | null` (optional) - Default: `300e3` - 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.
|
|
24
22
|
* **keepAliveMaxTimeout** `number | null` (optional) - Default: `600e3` - The maximum allowed `keepAliveTimeout`, in milliseconds, when overridden by *keep-alive* hints from the server. Defaults to 10 minutes.
|
|
@@ -34,6 +32,17 @@ Returns: `Client`
|
|
|
34
32
|
* **allowH2**: `boolean` - Default: `false`. Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
|
|
35
33
|
* **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame.
|
|
36
34
|
|
|
35
|
+
> **Notes about HTTP/2**
|
|
36
|
+
> - It only works under TLS connections. h2c is not supported.
|
|
37
|
+
> - The server must support HTTP/2 and choose it as the protocol during the ALPN negotiation.
|
|
38
|
+
> - The server must not have a bigger priority for HTTP/1.1 than HTTP/2.
|
|
39
|
+
> - Pseudo headers are automatically attached to the request. If you try to set them, they will be overwritten.
|
|
40
|
+
> - The `:path` header is automatically set to the request path.
|
|
41
|
+
> - The `:method` header is automatically set to the request method.
|
|
42
|
+
> - The `:scheme` header is automatically set to the request scheme.
|
|
43
|
+
> - The `:authority` header is automatically set to the request `host[:port]`.
|
|
44
|
+
> - `PUSH` frames are yet not supported.
|
|
45
|
+
|
|
37
46
|
#### Parameter: `ConnectOptions`
|
|
38
47
|
|
|
39
48
|
Every Tls option, see [here](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback).
|
|
@@ -15,6 +15,7 @@ Returns: `ProxyAgent`
|
|
|
15
15
|
### Parameter: `ProxyAgentOptions`
|
|
16
16
|
|
|
17
17
|
Extends: [`AgentOptions`](/docs/docs/api/Agent.md#parameter-agentoptions)
|
|
18
|
+
> It ommits `AgentOptions#connect`.
|
|
18
19
|
|
|
19
20
|
* **uri** `string | URL` (required) - The URI of the proxy server. This can be provided as a string, as an instance of the URL class, or as an object with a `uri` property of type string.
|
|
20
21
|
If the `uri` is provided as a string or `uri` is an object with an `uri` property of type string, then it will be parsed into a `URL` object according to the [WHATWG URL Specification](https://url.spec.whatwg.org).
|
|
@@ -22,8 +23,8 @@ For detailed information on the parsing process and potential validation errors,
|
|
|
22
23
|
* **token** `string` (optional) - It can be passed by a string of token for authentication.
|
|
23
24
|
* **auth** `string` (**deprecated**) - Use token.
|
|
24
25
|
* **clientFactory** `(origin: URL, opts: Object) => Dispatcher` (optional) - Default: `(origin, opts) => new Pool(origin, opts)`
|
|
25
|
-
* **requestTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the request.
|
|
26
|
-
* **proxyTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the proxy server.
|
|
26
|
+
* **requestTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the request. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions).
|
|
27
|
+
* **proxyTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the proxy server. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions).
|
|
27
28
|
|
|
28
29
|
Examples:
|
|
29
30
|
|
|
@@ -35,6 +36,13 @@ const proxyAgent = new ProxyAgent('my.proxy.server')
|
|
|
35
36
|
const proxyAgent = new ProxyAgent(new URL('my.proxy.server'))
|
|
36
37
|
// or
|
|
37
38
|
const proxyAgent = new ProxyAgent({ uri: 'my.proxy.server' })
|
|
39
|
+
// or
|
|
40
|
+
const proxyAgent = new ProxyAgent({
|
|
41
|
+
uri: new URL('my.proxy.server'),
|
|
42
|
+
proxyTls: {
|
|
43
|
+
signal: AbortSignal.timeout(1000)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
38
46
|
```
|
|
39
47
|
|
|
40
48
|
#### Example - Basic ProxyAgent instantiation
|
|
@@ -128,3 +136,91 @@ Implements [`Agent.dispatch(options, handlers)`](/docs/docs/api/Agent.md#paramet
|
|
|
128
136
|
### `ProxyAgent.request(options[, callback])`
|
|
129
137
|
|
|
130
138
|
See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
#### Example - ProxyAgent with Fetch
|
|
142
|
+
|
|
143
|
+
This example demonstrates how to use `fetch` with a proxy via `ProxyAgent`. It is particularly useful for scenarios requiring proxy tunneling.
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
import { ProxyAgent, fetch } from 'undici';
|
|
147
|
+
|
|
148
|
+
// Define the ProxyAgent
|
|
149
|
+
const proxyAgent = new ProxyAgent('http://localhost:8000');
|
|
150
|
+
|
|
151
|
+
// Make a GET request through the proxy
|
|
152
|
+
const response = await fetch('http://localhost:3000/foo', {
|
|
153
|
+
dispatcher: proxyAgent,
|
|
154
|
+
method: 'GET',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
console.log('Response status:', response.status);
|
|
158
|
+
console.log('Response data:', await response.text());
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
#### Example - ProxyAgent with a Custom Proxy Server
|
|
164
|
+
|
|
165
|
+
This example shows how to create a custom proxy server and use it with `ProxyAgent`.
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
import * as http from 'node:http';
|
|
169
|
+
import { createProxy } from 'proxy';
|
|
170
|
+
import { ProxyAgent, fetch } from 'undici';
|
|
171
|
+
|
|
172
|
+
// Create a proxy server
|
|
173
|
+
const proxyServer = createProxy(http.createServer());
|
|
174
|
+
proxyServer.listen(8000, () => {
|
|
175
|
+
console.log('Proxy server running on port 8000');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Define and use the ProxyAgent
|
|
179
|
+
const proxyAgent = new ProxyAgent('http://localhost:8000');
|
|
180
|
+
|
|
181
|
+
const response = await fetch('http://example.com', {
|
|
182
|
+
dispatcher: proxyAgent,
|
|
183
|
+
method: 'GET',
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
console.log('Response status:', response.status);
|
|
187
|
+
console.log('Response data:', await response.text());
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
#### Example - ProxyAgent with HTTPS Tunneling
|
|
193
|
+
|
|
194
|
+
This example demonstrates how to perform HTTPS tunneling using a proxy.
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
import { ProxyAgent, fetch } from 'undici';
|
|
198
|
+
|
|
199
|
+
// Define a ProxyAgent for HTTPS proxy
|
|
200
|
+
const proxyAgent = new ProxyAgent('https://secure.proxy.server');
|
|
201
|
+
|
|
202
|
+
// Make a request to an HTTPS endpoint via the proxy
|
|
203
|
+
const response = await fetch('https://secure.endpoint.com/api/data', {
|
|
204
|
+
dispatcher: proxyAgent,
|
|
205
|
+
method: 'GET',
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
console.log('Response status:', response.status);
|
|
209
|
+
console.log('Response data:', await response.json());
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### Example - ProxyAgent as a Global Dispatcher
|
|
213
|
+
|
|
214
|
+
`ProxyAgent` can be configured as a global dispatcher, making it available for all requests without explicitly passing it. This simplifies code and is useful when a single proxy configuration applies to all requests.
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
import { ProxyAgent, setGlobalDispatcher, fetch } from 'undici';
|
|
218
|
+
|
|
219
|
+
// Define and configure the ProxyAgent
|
|
220
|
+
const proxyAgent = new ProxyAgent('http://localhost:8000');
|
|
221
|
+
setGlobalDispatcher(proxyAgent);
|
|
222
|
+
|
|
223
|
+
// Make requests without specifying the dispatcher
|
|
224
|
+
const response = await fetch('http://example.com');
|
|
225
|
+
console.log('Response status:', response.status);
|
|
226
|
+
console.log('Response data:', await response.text());
|
package/index.js
CHANGED
|
@@ -49,14 +49,8 @@ module.exports.cacheStores = {
|
|
|
49
49
|
MemoryCacheStore: require('./lib/cache/memory-cache-store')
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
module.exports.cacheStores.SqliteCacheStore = SqliteCacheStore
|
|
55
|
-
} catch (err) {
|
|
56
|
-
if (err.code !== 'ERR_UNKNOWN_BUILTIN_MODULE') {
|
|
57
|
-
throw err
|
|
58
|
-
}
|
|
59
|
-
}
|
|
52
|
+
const SqliteCacheStore = require('./lib/cache/sqlite-cache-store')
|
|
53
|
+
module.exports.cacheStores.SqliteCacheStore = SqliteCacheStore
|
|
60
54
|
|
|
61
55
|
module.exports.buildConnector = buildConnector
|
|
62
56
|
module.exports.errors = errors
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { DatabaseSync } = require('node:sqlite')
|
|
4
3
|
const { Writable } = require('stream')
|
|
5
4
|
const { assertCacheKey, assertCacheValue } = require('../util/cache.js')
|
|
6
5
|
|
|
6
|
+
let DatabaseSync
|
|
7
|
+
|
|
7
8
|
const VERSION = 3
|
|
8
9
|
|
|
9
10
|
// 2gb
|
|
@@ -101,6 +102,9 @@ module.exports = class SqliteCacheStore {
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
if (!DatabaseSync) {
|
|
106
|
+
DatabaseSync = require('node:sqlite').DatabaseSync
|
|
107
|
+
}
|
|
104
108
|
this.#db = new DatabaseSync(opts?.location ?? ':memory:')
|
|
105
109
|
|
|
106
110
|
this.#db.exec(`
|
|
@@ -30,14 +30,12 @@ const {
|
|
|
30
30
|
kClosed,
|
|
31
31
|
kBodyTimeout
|
|
32
32
|
} = require('../core/symbols.js')
|
|
33
|
+
const { channels } = require('../core/diagnostics.js')
|
|
33
34
|
|
|
34
35
|
const kOpenStreams = Symbol('open streams')
|
|
35
36
|
|
|
36
37
|
let extractBody
|
|
37
38
|
|
|
38
|
-
// Experimental
|
|
39
|
-
let h2ExperimentalWarned = false
|
|
40
|
-
|
|
41
39
|
/** @type {import('http2')} */
|
|
42
40
|
let http2
|
|
43
41
|
try {
|
|
@@ -82,13 +80,6 @@ function parseH2Headers (headers) {
|
|
|
82
80
|
async function connectH2 (client, socket) {
|
|
83
81
|
client[kSocket] = socket
|
|
84
82
|
|
|
85
|
-
if (!h2ExperimentalWarned) {
|
|
86
|
-
h2ExperimentalWarned = true
|
|
87
|
-
process.emitWarning('H2 support is experimental, expect them to change at any time.', {
|
|
88
|
-
code: 'UNDICI-H2'
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
83
|
const session = http2.connect(client[kUrl], {
|
|
93
84
|
createConnection: () => socket,
|
|
94
85
|
peerMaxConcurrentStreams: client[kMaxConcurrentStreams],
|
|
@@ -458,6 +449,14 @@ function writeH2 (client, request) {
|
|
|
458
449
|
|
|
459
450
|
session.ref()
|
|
460
451
|
|
|
452
|
+
if (channels.sendHeaders.hasSubscribers) {
|
|
453
|
+
let header = ''
|
|
454
|
+
for (const key in headers) {
|
|
455
|
+
header += `${key}: ${headers[key]}\r\n`
|
|
456
|
+
}
|
|
457
|
+
channels.sendHeaders.publish({ request, headers: header, socket: session[kSocket] })
|
|
458
|
+
}
|
|
459
|
+
|
|
461
460
|
// TODO(metcoder95): add support for sending trailers
|
|
462
461
|
const shouldEndStream = method === 'GET' || method === 'HEAD' || body === null
|
|
463
462
|
if (expectContinue) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
|
+
const WrapHandler = require('./wrap-handler')
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @deprecated
|
|
@@ -9,63 +10,58 @@ module.exports = class DecoratorHandler {
|
|
|
9
10
|
#handler
|
|
10
11
|
#onCompleteCalled = false
|
|
11
12
|
#onErrorCalled = false
|
|
13
|
+
#onResponseStartCalled = false
|
|
12
14
|
|
|
13
15
|
constructor (handler) {
|
|
14
16
|
if (typeof handler !== 'object' || handler === null) {
|
|
15
17
|
throw new TypeError('handler must be an object')
|
|
16
18
|
}
|
|
17
|
-
this.#handler = handler
|
|
19
|
+
this.#handler = WrapHandler.wrap(handler)
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
onRequestStart (...args) {
|
|
23
|
+
this.#handler.onRequestStart?.(...args)
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
this.#onErrorCalled = true
|
|
26
|
-
return this.#handler.onError?.(...args)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
onUpgrade (...args) {
|
|
26
|
+
onRequestUpgrade (...args) {
|
|
30
27
|
assert(!this.#onCompleteCalled)
|
|
31
28
|
assert(!this.#onErrorCalled)
|
|
32
29
|
|
|
33
|
-
return this.#handler.
|
|
30
|
+
return this.#handler.onRequestUpgrade?.(...args)
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
|
|
33
|
+
onResponseStart (...args) {
|
|
37
34
|
assert(!this.#onCompleteCalled)
|
|
38
35
|
assert(!this.#onErrorCalled)
|
|
36
|
+
assert(!this.#onResponseStartCalled)
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
onHeaders (...args) {
|
|
44
|
-
assert(!this.#onCompleteCalled)
|
|
45
|
-
assert(!this.#onErrorCalled)
|
|
38
|
+
this.#onResponseStartCalled = true
|
|
46
39
|
|
|
47
|
-
return this.#handler.
|
|
40
|
+
return this.#handler.onResponseStart?.(...args)
|
|
48
41
|
}
|
|
49
42
|
|
|
50
|
-
|
|
43
|
+
onResponseData (...args) {
|
|
51
44
|
assert(!this.#onCompleteCalled)
|
|
52
45
|
assert(!this.#onErrorCalled)
|
|
53
46
|
|
|
54
|
-
return this.#handler.
|
|
47
|
+
return this.#handler.onResponseData?.(...args)
|
|
55
48
|
}
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
onResponseEnd (...args) {
|
|
58
51
|
assert(!this.#onCompleteCalled)
|
|
59
52
|
assert(!this.#onErrorCalled)
|
|
60
53
|
|
|
61
54
|
this.#onCompleteCalled = true
|
|
62
|
-
return this.#handler.
|
|
55
|
+
return this.#handler.onResponseEnd?.(...args)
|
|
63
56
|
}
|
|
64
57
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return this.#handler.onBodySent?.(...args)
|
|
58
|
+
onResponseError (...args) {
|
|
59
|
+
this.#onErrorCalled = true
|
|
60
|
+
return this.#handler.onResponseError?.(...args)
|
|
70
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @deprecated
|
|
65
|
+
*/
|
|
66
|
+
onBodySent () {}
|
|
71
67
|
}
|
|
@@ -53,8 +53,7 @@ module.exports = class WrapHandler {
|
|
|
53
53
|
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
54
54
|
const rawHeaders = []
|
|
55
55
|
for (const [key, val] of Object.entries(headers)) {
|
|
56
|
-
|
|
57
|
-
rawHeaders.push(Buffer.from(key), Buffer.from(val))
|
|
56
|
+
rawHeaders.push(Buffer.from(key), Array.isArray(val) ? val.map(v => Buffer.from(v)) : Buffer.from(val))
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
this.#handler.onUpgrade?.(statusCode, rawHeaders, socket)
|
|
@@ -63,8 +62,7 @@ module.exports = class WrapHandler {
|
|
|
63
62
|
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
64
63
|
const rawHeaders = []
|
|
65
64
|
for (const [key, val] of Object.entries(headers)) {
|
|
66
|
-
|
|
67
|
-
rawHeaders.push(Buffer.from(key), Buffer.from(val))
|
|
65
|
+
rawHeaders.push(Buffer.from(key), Array.isArray(val) ? val.map(v => Buffer.from(v)) : Buffer.from(val))
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
if (this.#handler.onHeaders?.(statusCode, rawHeaders, () => controller.resume(), statusMessage) === false) {
|
|
@@ -81,8 +79,7 @@ module.exports = class WrapHandler {
|
|
|
81
79
|
onResponseEnd (controller, trailers) {
|
|
82
80
|
const rawTrailers = []
|
|
83
81
|
for (const [key, val] of Object.entries(trailers)) {
|
|
84
|
-
|
|
85
|
-
rawTrailers.push(Buffer.from(key), Buffer.from(val))
|
|
82
|
+
rawTrailers.push(Buffer.from(key), Array.isArray(val) ? val.map(v => Buffer.from(v)) : Buffer.from(val))
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
this.#handler.onComplete?.(rawTrailers)
|
package/lib/interceptor/cache.js
CHANGED
|
@@ -225,8 +225,11 @@ function handleResult (
|
|
|
225
225
|
|
|
226
226
|
let headers = {
|
|
227
227
|
...opts.headers,
|
|
228
|
-
'if-modified-since': new Date(result.cachedAt).toUTCString()
|
|
229
|
-
|
|
228
|
+
'if-modified-since': new Date(result.cachedAt).toUTCString()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (result.etag) {
|
|
232
|
+
headers['if-none-match'] = result.etag
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
if (result.vary) {
|
package/lib/interceptor/dns.js
CHANGED
|
@@ -222,19 +222,18 @@ class DNSDispatchHandler extends DecoratorHandler {
|
|
|
222
222
|
#state = null
|
|
223
223
|
#opts = null
|
|
224
224
|
#dispatch = null
|
|
225
|
-
#handler = null
|
|
226
225
|
#origin = null
|
|
226
|
+
#controller = null
|
|
227
227
|
|
|
228
228
|
constructor (state, { origin, handler, dispatch }, opts) {
|
|
229
229
|
super(handler)
|
|
230
230
|
this.#origin = origin
|
|
231
|
-
this.#handler = handler
|
|
232
231
|
this.#opts = { ...opts }
|
|
233
232
|
this.#state = state
|
|
234
233
|
this.#dispatch = dispatch
|
|
235
234
|
}
|
|
236
235
|
|
|
237
|
-
|
|
236
|
+
onResponseError (controller, err) {
|
|
238
237
|
switch (err.code) {
|
|
239
238
|
case 'ETIMEDOUT':
|
|
240
239
|
case 'ECONNREFUSED': {
|
|
@@ -242,7 +241,8 @@ class DNSDispatchHandler extends DecoratorHandler {
|
|
|
242
241
|
// We delete the record and retry
|
|
243
242
|
this.#state.runLookup(this.#origin, this.#opts, (err, newOrigin) => {
|
|
244
243
|
if (err) {
|
|
245
|
-
|
|
244
|
+
super.onResponseError(controller, err)
|
|
245
|
+
return
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
const dispatchOpts = {
|
|
@@ -253,18 +253,18 @@ class DNSDispatchHandler extends DecoratorHandler {
|
|
|
253
253
|
this.#dispatch(dispatchOpts, this)
|
|
254
254
|
})
|
|
255
255
|
|
|
256
|
-
// if dual-stack disabled, we error out
|
|
257
256
|
return
|
|
258
257
|
}
|
|
259
258
|
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
// if dual-stack disabled, we error out
|
|
260
|
+
super.onResponseError(controller, err)
|
|
261
|
+
break
|
|
262
262
|
}
|
|
263
263
|
case 'ENOTFOUND':
|
|
264
264
|
this.#state.deleteRecord(this.#origin)
|
|
265
265
|
// eslint-disable-next-line no-fallthrough
|
|
266
266
|
default:
|
|
267
|
-
|
|
267
|
+
super.onResponseError(controller, err)
|
|
268
268
|
break
|
|
269
269
|
}
|
|
270
270
|
}
|
|
@@ -358,7 +358,7 @@ module.exports = interceptorOpts => {
|
|
|
358
358
|
servername: origin.hostname, // For SNI on TLS
|
|
359
359
|
origin: newOrigin,
|
|
360
360
|
headers: {
|
|
361
|
-
host: origin.
|
|
361
|
+
host: origin.host,
|
|
362
362
|
...origDispatchOpts.headers
|
|
363
363
|
}
|
|
364
364
|
}
|
package/lib/interceptor/dump.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const util = require('../core/util')
|
|
4
3
|
const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
|
|
5
4
|
const DecoratorHandler = require('../handler/decorator-handler')
|
|
6
5
|
|
|
7
6
|
class DumpHandler extends DecoratorHandler {
|
|
8
7
|
#maxSize = 1024 * 1024
|
|
9
|
-
#abort = null
|
|
10
8
|
#dumped = false
|
|
11
|
-
#aborted = false
|
|
12
9
|
#size = 0
|
|
13
|
-
#
|
|
14
|
-
|
|
10
|
+
#controller = null
|
|
11
|
+
aborted = false
|
|
12
|
+
reason = false
|
|
15
13
|
|
|
16
|
-
constructor ({ maxSize }, handler) {
|
|
14
|
+
constructor ({ maxSize, signal }, handler) {
|
|
17
15
|
if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) {
|
|
18
16
|
throw new InvalidArgumentError('maxSize must be a number greater than 0')
|
|
19
17
|
}
|
|
@@ -21,23 +19,22 @@ class DumpHandler extends DecoratorHandler {
|
|
|
21
19
|
super(handler)
|
|
22
20
|
|
|
23
21
|
this.#maxSize = maxSize ?? this.#maxSize
|
|
24
|
-
this.#handler = handler
|
|
22
|
+
// this.#handler = handler
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
this
|
|
29
|
-
|
|
30
|
-
this.#handler.onConnect(this.#customAbort.bind(this))
|
|
25
|
+
#abort (reason) {
|
|
26
|
+
this.aborted = true
|
|
27
|
+
this.reason = reason
|
|
31
28
|
}
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.#
|
|
30
|
+
onRequestStart (controller, context) {
|
|
31
|
+
controller.abort = this.#abort.bind(this)
|
|
32
|
+
this.#controller = controller
|
|
33
|
+
|
|
34
|
+
return super.onRequestStart(controller, context)
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
40
|
-
const headers = util.parseHeaders(rawHeaders)
|
|
37
|
+
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
41
38
|
const contentLength = headers['content-length']
|
|
42
39
|
|
|
43
40
|
if (contentLength != null && contentLength > this.#maxSize) {
|
|
@@ -48,55 +45,50 @@ class DumpHandler extends DecoratorHandler {
|
|
|
48
45
|
)
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
if (this
|
|
48
|
+
if (this.aborted === true) {
|
|
52
49
|
return true
|
|
53
50
|
}
|
|
54
51
|
|
|
55
|
-
return
|
|
56
|
-
statusCode,
|
|
57
|
-
rawHeaders,
|
|
58
|
-
resume,
|
|
59
|
-
statusMessage
|
|
60
|
-
)
|
|
52
|
+
return super.onResponseStart(controller, statusCode, headers, statusMessage)
|
|
61
53
|
}
|
|
62
54
|
|
|
63
|
-
|
|
55
|
+
onResponseError (controller, err) {
|
|
64
56
|
if (this.#dumped) {
|
|
65
57
|
return
|
|
66
58
|
}
|
|
67
59
|
|
|
68
|
-
err = this.#reason ?? err
|
|
60
|
+
err = this.#controller.reason ?? err
|
|
69
61
|
|
|
70
|
-
|
|
62
|
+
super.onResponseError(controller, err)
|
|
71
63
|
}
|
|
72
64
|
|
|
73
|
-
|
|
65
|
+
onResponseData (controller, chunk) {
|
|
74
66
|
this.#size = this.#size + chunk.length
|
|
75
67
|
|
|
76
68
|
if (this.#size >= this.#maxSize) {
|
|
77
69
|
this.#dumped = true
|
|
78
70
|
|
|
79
|
-
if (this
|
|
80
|
-
|
|
71
|
+
if (this.aborted === true) {
|
|
72
|
+
super.onResponseError(controller, this.reason)
|
|
81
73
|
} else {
|
|
82
|
-
|
|
74
|
+
super.onResponseEnd(controller, {})
|
|
83
75
|
}
|
|
84
76
|
}
|
|
85
77
|
|
|
86
78
|
return true
|
|
87
79
|
}
|
|
88
80
|
|
|
89
|
-
|
|
81
|
+
onResponseEnd (controller, trailers) {
|
|
90
82
|
if (this.#dumped) {
|
|
91
83
|
return
|
|
92
84
|
}
|
|
93
85
|
|
|
94
|
-
if (this.#aborted) {
|
|
95
|
-
|
|
86
|
+
if (this.#controller.aborted === true) {
|
|
87
|
+
super.onResponseError(controller, this.reason)
|
|
96
88
|
return
|
|
97
89
|
}
|
|
98
90
|
|
|
99
|
-
|
|
91
|
+
super.onResponseEnd(controller, trailers)
|
|
100
92
|
}
|
|
101
93
|
}
|
|
102
94
|
|
|
@@ -107,13 +99,9 @@ function createDumpInterceptor (
|
|
|
107
99
|
) {
|
|
108
100
|
return dispatch => {
|
|
109
101
|
return function Intercept (opts, handler) {
|
|
110
|
-
const { dumpMaxSize = defaultMaxSize } =
|
|
111
|
-
opts
|
|
102
|
+
const { dumpMaxSize = defaultMaxSize } = opts
|
|
112
103
|
|
|
113
|
-
const dumpHandler = new DumpHandler(
|
|
114
|
-
{ maxSize: dumpMaxSize },
|
|
115
|
-
handler
|
|
116
|
-
)
|
|
104
|
+
const dumpHandler = new DumpHandler({ maxSize: dumpMaxSize, signal: opts.signal }, handler)
|
|
117
105
|
|
|
118
106
|
return dispatch(opts, dumpHandler)
|
|
119
107
|
}
|
|
@@ -1,43 +1,41 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { parseHeaders } = require('../core/util')
|
|
3
|
+
// const { parseHeaders } = require('../core/util')
|
|
4
4
|
const DecoratorHandler = require('../handler/decorator-handler')
|
|
5
5
|
const { ResponseError } = require('../core/errors')
|
|
6
6
|
|
|
7
7
|
class ResponseErrorHandler extends DecoratorHandler {
|
|
8
|
-
#handler
|
|
9
8
|
#statusCode
|
|
10
9
|
#contentType
|
|
11
10
|
#decoder
|
|
12
11
|
#headers
|
|
13
12
|
#body
|
|
14
13
|
|
|
15
|
-
constructor (
|
|
14
|
+
constructor (_opts, { handler }) {
|
|
16
15
|
super(handler)
|
|
17
|
-
this.#handler = handler
|
|
18
16
|
}
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
#checkContentType (contentType) {
|
|
19
|
+
return (this.#contentType ?? '').indexOf(contentType) === 0
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onRequestStart (controller, context) {
|
|
21
23
|
this.#statusCode = 0
|
|
22
24
|
this.#contentType = null
|
|
23
25
|
this.#decoder = null
|
|
24
26
|
this.#headers = null
|
|
25
27
|
this.#body = ''
|
|
26
28
|
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
#checkContentType (contentType) {
|
|
31
|
-
return this.#contentType.indexOf(contentType) === 0
|
|
29
|
+
return super.onRequestStart(controller, context)
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
35
33
|
this.#statusCode = statusCode
|
|
36
34
|
this.#headers = headers
|
|
37
35
|
this.#contentType = headers['content-type']
|
|
38
36
|
|
|
39
37
|
if (this.#statusCode < 400) {
|
|
40
|
-
return
|
|
38
|
+
return super.onResponseStart(controller, statusCode, headers, statusMessage)
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
if (this.#checkContentType('application/json') || this.#checkContentType('text/plain')) {
|
|
@@ -45,15 +43,15 @@ class ResponseErrorHandler extends DecoratorHandler {
|
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
onResponseData (controller, chunk) {
|
|
49
47
|
if (this.#statusCode < 400) {
|
|
50
|
-
return
|
|
48
|
+
return super.onResponseData(controller, chunk)
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? ''
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
onResponseEnd (controller, trailers) {
|
|
57
55
|
if (this.#statusCode >= 400) {
|
|
58
56
|
this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''
|
|
59
57
|
|
|
@@ -77,14 +75,14 @@ class ResponseErrorHandler extends DecoratorHandler {
|
|
|
77
75
|
Error.stackTraceLimit = stackTraceLimit
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
|
|
78
|
+
super.onResponseError(controller, err)
|
|
81
79
|
} else {
|
|
82
|
-
|
|
80
|
+
super.onResponseEnd(controller, trailers)
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
83
|
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
onResponseError (controller, err) {
|
|
85
|
+
super.onResponseError(controller, err)
|
|
88
86
|
}
|
|
89
87
|
}
|
|
90
88
|
|
|
@@ -283,7 +283,7 @@ function parseMIMEType (input) {
|
|
|
283
283
|
|
|
284
284
|
// 5. If position is past the end of input, then return
|
|
285
285
|
// failure
|
|
286
|
-
if (position.position
|
|
286
|
+
if (position.position >= input.length) {
|
|
287
287
|
return 'failure'
|
|
288
288
|
}
|
|
289
289
|
|
|
@@ -364,7 +364,7 @@ function parseMIMEType (input) {
|
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
// 6. If position is past the end of input, then break.
|
|
367
|
-
if (position.position
|
|
367
|
+
if (position.position >= input.length) {
|
|
368
368
|
break
|
|
369
369
|
}
|
|
370
370
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
|
5
5
|
"homepage": "https://undici.nodejs.org",
|
|
6
6
|
"bugs": {
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"prepare": "husky && node ./scripts/platform-shell.js"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
|
-
"@fastify/busboy": "3.
|
|
109
|
+
"@fastify/busboy": "3.1.0",
|
|
110
110
|
"@matteo.collina/tspl": "^0.1.1",
|
|
111
111
|
"@sinonjs/fake-timers": "^12.0.0",
|
|
112
112
|
"@types/node": "^18.19.50",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"https-pem": "^3.0.0",
|
|
122
122
|
"husky": "^9.0.7",
|
|
123
123
|
"jest": "^29.0.2",
|
|
124
|
-
"neostandard": "^0.
|
|
124
|
+
"neostandard": "^0.12.0",
|
|
125
125
|
"node-forge": "^1.3.1",
|
|
126
126
|
"proxy": "^2.1.1",
|
|
127
127
|
"tsd": "^0.31.2",
|
package/types/index.d.ts
CHANGED
|
@@ -64,6 +64,7 @@ declare namespace Undici {
|
|
|
64
64
|
const caches: typeof import('./cache').caches
|
|
65
65
|
const interceptors: typeof import('./interceptors').default
|
|
66
66
|
const cacheStores: {
|
|
67
|
-
MemoryCacheStore: typeof import('./cache-interceptor').default.MemoryCacheStore
|
|
67
|
+
MemoryCacheStore: typeof import('./cache-interceptor').default.MemoryCacheStore,
|
|
68
|
+
SqliteCacheStore: typeof import('./cache-interceptor').default.SqliteCacheStore
|
|
68
69
|
}
|
|
69
70
|
}
|