Skip to content

Commit 4b1bed0

Browse files
nodejs-github-bottargos
authored andcommitted
deps: update undici to 5.28.0
PR-URL: #50915 Reviewed-By: Matthew Aitken <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 7adf239 commit 4b1bed0

25 files changed

+2377
-421
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Returns: `Client`
3333
* **autoSelectFamily**: `boolean` (optional) - Default: depends on local Node version, on Node 18.13.0 and above is `false`. Enables a family autodetection algorithm that loosely implements section 5 of [RFC 8305](https://tools.ietf.org/html/rfc8305#section-5). See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details. This option is ignored if not supported by the current Node version.
3434
* **autoSelectFamilyAttemptTimeout**: `number` - Default: depends on local Node version, on Node 18.13.0 and above is `250`. The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details.
3535
* **allowH2**: `boolean` - Default: `false`. Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
36-
* **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overriden by a SETTINGS remote frame.
36+
* **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.
3737

3838
#### Parameter: `ConnectOptions`
3939

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

+38-4
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ const mockPool = mockAgent.get('http://localhost:3000')
3535

3636
### `MockPool.intercept(options)`
3737

38-
This method defines the interception rules for matching against requests for a MockPool or MockPool. We can intercept multiple times on a single instance, but each intercept is only used once.
39-
For example if you expect to make 2 requests inside a test, you need to call `intercept()` twice. Assuming you use `disableNetConnect()` you will get `MockNotMatchedError` on the second request when you only call `intercept()` once.
38+
This method defines the interception rules for matching against requests for a MockPool or MockPool. We can intercept multiple times on a single instance, but each intercept is only used once. For example if you expect to make 2 requests inside a test, you need to call `intercept()` twice. Assuming you use `disableNetConnect()` you will get `MockNotMatchedError` on the second request when you only call `intercept()` once.
4039

4140
When defining interception rules, all the rules must pass for a request to be intercepted. If a request is not intercepted, a real request will be attempted.
4241

@@ -54,11 +53,11 @@ Returns: `MockInterceptor` corresponding to the input options.
5453

5554
### Parameter: `MockPoolInterceptOptions`
5655

57-
* **path** `string | RegExp | (path: string) => boolean` - a matcher for the HTTP request path.
56+
* **path** `string | RegExp | (path: string) => boolean` - a matcher for the HTTP request path. When a `RegExp` or callback is used, it will match against the request path including all query parameters in alphabetical order. When a `string` is provided, the query parameters can be conveniently specified through the `MockPoolInterceptOptions.query` setting.
5857
* **method** `string | RegExp | (method: string) => boolean` - (optional) - a matcher for the HTTP request method. Defaults to `GET`.
5958
* **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body.
6059
* **headers** `Record<string, string | RegExp | (body: string) => boolean`> - (optional) - a matcher for the HTTP request headers. To be intercepted, a request must match all defined headers. Extra headers not defined here may (or may not) be included in the request and do not affect the interception in any way.
61-
* **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params.
60+
* **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params. Only applies when a `string` was provided for `MockPoolInterceptOptions.path`.
6261

6362
### Return: `MockInterceptor`
6463

@@ -458,6 +457,41 @@ const result3 = await request('http://localhost:3000/foo')
458457
// Will not match and make attempt a real request
459458
```
460459

460+
#### Example - Mocked request with path callback
461+
462+
```js
463+
import { MockAgent, setGlobalDispatcher, request } from 'undici'
464+
import querystring from 'querystring'
465+
466+
const mockAgent = new MockAgent()
467+
setGlobalDispatcher(mockAgent)
468+
469+
const mockPool = mockAgent.get('http://localhost:3000')
470+
471+
const matchPath = requestPath => {
472+
const [pathname, search] = requestPath.split('?')
473+
const requestQuery = querystring.parse(search)
474+
475+
if (!pathname.startsWith('/foo')) {
476+
return false
477+
}
478+
479+
if (!Object.keys(requestQuery).includes('foo') || requestQuery.foo !== 'bar') {
480+
return false
481+
}
482+
483+
return true
484+
}
485+
486+
mockPool.intercept({
487+
path: matchPath,
488+
method: 'GET'
489+
}).reply(200, 'foo')
490+
491+
const result = await request('http://localhost:3000/foo?foo=bar')
492+
// Will match and return mocked data
493+
```
494+
461495
### `MockPool.close()`
462496

463497
Closes the mock pool and de-registers from associated MockAgent.
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Class: RetryHandler
2+
3+
Extends: `undici.DispatcherHandlers`
4+
5+
A handler class that implements the retry logic for a request.
6+
7+
## `new RetryHandler(dispatchOptions, retryHandlers, [retryOptions])`
8+
9+
Arguments:
10+
11+
- **options** `Dispatch.DispatchOptions & RetryOptions` (required) - It is an intersection of `Dispatcher.DispatchOptions` and `RetryOptions`.
12+
- **retryHandlers** `RetryHandlers` (required) - Object containing the `dispatch` to be used on every retry, and `handler` for handling the `dispatch` lifecycle.
13+
14+
Returns: `retryHandler`
15+
16+
### Parameter: `Dispatch.DispatchOptions & RetryOptions`
17+
18+
Extends: [`Dispatch.DispatchOptions`](Dispatcher.md#parameter-dispatchoptions).
19+
20+
#### `RetryOptions`
21+
22+
- **retry** `(err: Error, context: RetryContext, callback: (err?: Error | null) => void) => void` (optional) - Function to be called after every retry. It should pass error if no more retries should be performed.
23+
- **maxRetries** `number` (optional) - Maximum number of retries. Default: `5`
24+
- **maxTimeout** `number` (optional) - Maximum number of milliseconds to wait before retrying. Default: `30000` (30 seconds)
25+
- **minTimeout** `number` (optional) - Minimum number of milliseconds to wait before retrying. Default: `500` (half a second)
26+
- **timeoutFactor** `number` (optional) - Factor to multiply the timeout by for each retry attempt. Default: `2`
27+
- **retryAfter** `boolean` (optional) - It enables automatic retry after the `Retry-After` header is received. Default: `true`
28+
-
29+
- **methods** `string[]` (optional) - Array of HTTP methods to retry. Default: `['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE']`
30+
- **statusCodes** `number[]` (optional) - Array of HTTP status codes to retry. Default: `[429, 500, 502, 503, 504]`
31+
- **errorCodes** `string[]` (optional) - Array of Error codes to retry. Default: `['ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ENETDOWN','ENETUNREACH', 'EHOSTDOWN',
32+
33+
**`RetryContext`**
34+
35+
- `state`: `RetryState` - Current retry state. It can be mutated.
36+
- `opts`: `Dispatch.DispatchOptions & RetryOptions` - Options passed to the retry handler.
37+
38+
### Parameter `RetryHandlers`
39+
40+
- **dispatch** `(options: Dispatch.DispatchOptions, handlers: Dispatch.DispatchHandlers) => Promise<Dispatch.DispatchResponse>` (required) - Dispatch function to be called after every retry.
41+
- **handler** Extends [`Dispatch.DispatchHandlers`](Dispatcher.md#dispatcherdispatchoptions-handler) (required) - Handler function to be called after the request is successful or the retries are exhausted.
42+
43+
Examples:
44+
45+
```js
46+
const client = new Client(`http://localhost:${server.address().port}`);
47+
const chunks = [];
48+
const handler = new RetryHandler(
49+
{
50+
...dispatchOptions,
51+
retryOptions: {
52+
// custom retry function
53+
retry: function (err, state, callback) {
54+
counter++;
55+
56+
if (err.code && err.code === "UND_ERR_DESTROYED") {
57+
callback(err);
58+
return;
59+
}
60+
61+
if (err.statusCode === 206) {
62+
callback(err);
63+
return;
64+
}
65+
66+
setTimeout(() => callback(null), 1000);
67+
},
68+
},
69+
},
70+
{
71+
dispatch: (...args) => {
72+
return client.dispatch(...args);
73+
},
74+
handler: {
75+
onConnect() {},
76+
onBodySent() {},
77+
onHeaders(status, _rawHeaders, resume, _statusMessage) {
78+
// do something with headers
79+
},
80+
onData(chunk) {
81+
chunks.push(chunk);
82+
return true;
83+
},
84+
onComplete() {},
85+
onError() {
86+
// handle error properly
87+
},
88+
},
89+
}
90+
);
91+
```
92+
93+
#### Example - Basic RetryHandler with defaults
94+
95+
```js
96+
const client = new Client(`http://localhost:${server.address().port}`);
97+
const handler = new RetryHandler(dispatchOptions, {
98+
dispatch: client.dispatch.bind(client),
99+
handler: {
100+
onConnect() {},
101+
onBodySent() {},
102+
onHeaders(status, _rawHeaders, resume, _statusMessage) {},
103+
onData(chunk) {},
104+
onComplete() {},
105+
onError(err) {},
106+
},
107+
});
108+
```

deps/undici/src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const MockAgent = require('./lib/mock/mock-agent')
1515
const MockPool = require('./lib/mock/mock-pool')
1616
const mockErrors = require('./lib/mock/mock-errors')
1717
const ProxyAgent = require('./lib/proxy-agent')
18+
const RetryHandler = require('./lib/handler/RetryHandler')
1819
const { getGlobalDispatcher, setGlobalDispatcher } = require('./lib/global')
1920
const DecoratorHandler = require('./lib/handler/DecoratorHandler')
2021
const RedirectHandler = require('./lib/handler/RedirectHandler')
@@ -36,6 +37,7 @@ module.exports.Pool = Pool
3637
module.exports.BalancedPool = BalancedPool
3738
module.exports.Agent = Agent
3839
module.exports.ProxyAgent = ProxyAgent
40+
module.exports.RetryHandler = RetryHandler
3941

4042
module.exports.DecoratorHandler = DecoratorHandler
4143
module.exports.RedirectHandler = RedirectHandler

deps/undici/src/lib/api/readable.js

+40-25
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const kBody = Symbol('kBody')
1616
const kAbort = Symbol('abort')
1717
const kContentType = Symbol('kContentType')
1818

19+
const noop = () => {}
20+
1921
module.exports = class BodyReadable extends Readable {
2022
constructor ({
2123
resume,
@@ -149,37 +151,50 @@ module.exports = class BodyReadable extends Readable {
149151
return this[kBody]
150152
}
151153

152-
async dump (opts) {
154+
dump (opts) {
153155
let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144
154156
const signal = opts && opts.signal
155-
const abortFn = () => {
156-
this.destroy()
157-
}
158-
let signalListenerCleanup
157+
159158
if (signal) {
160-
if (typeof signal !== 'object' || !('aborted' in signal)) {
161-
throw new InvalidArgumentError('signal must be an AbortSignal')
162-
}
163-
util.throwIfAborted(signal)
164-
signalListenerCleanup = util.addAbortListener(signal, abortFn)
165-
}
166-
try {
167-
for await (const chunk of this) {
168-
util.throwIfAborted(signal)
169-
limit -= Buffer.byteLength(chunk)
170-
if (limit < 0) {
171-
return
159+
try {
160+
if (typeof signal !== 'object' || !('aborted' in signal)) {
161+
throw new InvalidArgumentError('signal must be an AbortSignal')
172162
}
163+
util.throwIfAborted(signal)
164+
} catch (err) {
165+
return Promise.reject(err)
173166
}
174-
} catch {
175-
util.throwIfAborted(signal)
176-
} finally {
177-
if (typeof signalListenerCleanup === 'function') {
178-
signalListenerCleanup()
179-
} else if (signalListenerCleanup) {
180-
signalListenerCleanup[Symbol.dispose]()
181-
}
182167
}
168+
169+
if (this.closed) {
170+
return Promise.resolve(null)
171+
}
172+
173+
return new Promise((resolve, reject) => {
174+
const signalListenerCleanup = signal
175+
? util.addAbortListener(signal, () => {
176+
this.destroy()
177+
})
178+
: noop
179+
180+
this
181+
.on('close', function () {
182+
signalListenerCleanup()
183+
if (signal?.aborted) {
184+
reject(signal.reason || Object.assign(new Error('The operation was aborted'), { name: 'AbortError' }))
185+
} else {
186+
resolve(null)
187+
}
188+
})
189+
.on('error', noop)
190+
.on('data', function (chunk) {
191+
limit -= chunk.length
192+
if (limit <= 0) {
193+
this.destroy()
194+
}
195+
})
196+
.resume()
197+
})
183198
}
184199
}
185200

deps/undici/src/lib/client.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,7 @@ async function connect (client) {
11831183
const idx = hostname.indexOf(']')
11841184

11851185
assert(idx !== -1)
1186-
const ip = hostname.substr(1, idx - 1)
1186+
const ip = hostname.substring(1, idx)
11871187

11881188
assert(net.isIP(ip))
11891189
hostname = ip
@@ -1682,6 +1682,7 @@ function writeH2 (client, session, request) {
16821682
return false
16831683
}
16841684

1685+
/** @type {import('node:http2').ClientHttp2Stream} */
16851686
let stream
16861687
const h2State = client[kHTTP2SessionState]
16871688

@@ -1777,14 +1778,10 @@ function writeH2 (client, session, request) {
17771778
const shouldEndStream = method === 'GET' || method === 'HEAD'
17781779
if (expectContinue) {
17791780
headers[HTTP2_HEADER_EXPECT] = '100-continue'
1780-
/**
1781-
* @type {import('node:http2').ClientHttp2Stream}
1782-
*/
17831781
stream = session.request(headers, { endStream: shouldEndStream, signal })
17841782

17851783
stream.once('continue', writeBodyH2)
17861784
} else {
1787-
/** @type {import('node:http2').ClientHttp2Stream} */
17881785
stream = session.request(headers, {
17891786
endStream: shouldEndStream,
17901787
signal
@@ -1796,7 +1793,9 @@ function writeH2 (client, session, request) {
17961793
++h2State.openStreams
17971794

17981795
stream.once('response', headers => {
1799-
if (request.onHeaders(Number(headers[HTTP2_HEADER_STATUS]), headers, stream.resume.bind(stream), '') === false) {
1796+
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
1797+
1798+
if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) {
18001799
stream.pause()
18011800
}
18021801
})
@@ -1972,7 +1971,11 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
19721971
}
19731972
}
19741973
const onAbort = function () {
1975-
onFinished(new RequestAbortedError())
1974+
if (finished) {
1975+
return
1976+
}
1977+
const err = new RequestAbortedError()
1978+
queueMicrotask(() => onFinished(err))
19761979
}
19771980
const onFinished = function (err) {
19781981
if (finished) {

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,19 @@ class ResponseExceededMaxSizeError extends UndiciError {
193193
}
194194
}
195195

196+
class RequestRetryError extends UndiciError {
197+
constructor (message, code, { headers, data }) {
198+
super(message)
199+
Error.captureStackTrace(this, RequestRetryError)
200+
this.name = 'RequestRetryError'
201+
this.message = message || 'Request retry error'
202+
this.code = 'UND_ERR_REQ_RETRY'
203+
this.statusCode = code
204+
this.data = data
205+
this.headers = headers
206+
}
207+
}
208+
196209
module.exports = {
197210
HTTPParserError,
198211
UndiciError,
@@ -212,5 +225,6 @@ module.exports = {
212225
NotSupportedError,
213226
ResponseContentLengthMismatchError,
214227
BalancedPoolMissingUpstreamError,
215-
ResponseExceededMaxSizeError
228+
ResponseExceededMaxSizeError,
229+
RequestRetryError
216230
}

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

+2-10
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,7 @@ class Request {
229229

230230
onBodySent (chunk) {
231231
if (this[kHandler].onBodySent) {
232-
try {
233-
this[kHandler].onBodySent(chunk)
234-
} catch (err) {
235-
this.onError(err)
236-
}
232+
return this[kHandler].onBodySent(chunk)
237233
}
238234
}
239235

@@ -243,11 +239,7 @@ class Request {
243239
}
244240

245241
if (this[kHandler].onRequestSent) {
246-
try {
247-
this[kHandler].onRequestSent()
248-
} catch (err) {
249-
this.onError(err)
250-
}
242+
return this[kHandler].onRequestSent()
251243
}
252244
}
253245

0 commit comments

Comments
 (0)