undici 5.27.2 → 5.28.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/docs/api/Client.md +1 -1
- package/docs/api/MockPool.md +38 -4
- package/docs/api/RetryHandler.md +108 -0
- package/index.js +2 -0
- package/lib/api/readable.js +40 -25
- package/lib/client.js +10 -7
- package/lib/core/errors.js +15 -1
- package/lib/core/request.js +2 -10
- package/lib/core/symbols.js +2 -1
- package/lib/core/util.js +21 -13
- package/lib/fetch/headers.js +93 -59
- package/lib/fetch/index.js +4 -4
- package/lib/fetch/request.js +2 -2
- package/lib/fetch/util.js +38 -33
- package/lib/fetch/webidl.js +2 -4
- package/lib/handler/RetryHandler.js +336 -0
- package/package.json +2 -1
- package/types/client.d.ts +1 -1
- package/types/dispatcher.d.ts +1 -1
- package/types/index.d.ts +3 -1
- package/types/retry-handler.d.ts +116 -0
package/docs/api/Client.md
CHANGED
|
@@ -33,7 +33,7 @@ Returns: `Client`
|
|
|
33
33
|
* **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.
|
|
34
34
|
* **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.
|
|
35
35
|
* **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
|
|
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.
|
|
37
37
|
|
|
38
38
|
#### Parameter: `ConnectOptions`
|
|
39
39
|
|
package/docs/api/MockPool.md
CHANGED
|
@@ -35,8 +35,7 @@ const mockPool = mockAgent.get('http://localhost:3000')
|
|
|
35
35
|
|
|
36
36
|
### `MockPool.intercept(options)`
|
|
37
37
|
|
|
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.
|
|
40
39
|
|
|
41
40
|
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.
|
|
42
41
|
|
|
@@ -54,11 +53,11 @@ Returns: `MockInterceptor` corresponding to the input options.
|
|
|
54
53
|
|
|
55
54
|
### Parameter: `MockPoolInterceptOptions`
|
|
56
55
|
|
|
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.
|
|
58
57
|
* **method** `string | RegExp | (method: string) => boolean` - (optional) - a matcher for the HTTP request method. Defaults to `GET`.
|
|
59
58
|
* **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body.
|
|
60
59
|
* **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`.
|
|
62
61
|
|
|
63
62
|
### Return: `MockInterceptor`
|
|
64
63
|
|
|
@@ -458,6 +457,41 @@ const result3 = await request('http://localhost:3000/foo')
|
|
|
458
457
|
// Will not match and make attempt a real request
|
|
459
458
|
```
|
|
460
459
|
|
|
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
|
+
|
|
461
495
|
### `MockPool.close()`
|
|
462
496
|
|
|
463
497
|
Closes the mock pool and de-registers from associated MockAgent.
|
|
@@ -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
|
+
```
|
package/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const MockAgent = require('./lib/mock/mock-agent')
|
|
|
15
15
|
const MockPool = require('./lib/mock/mock-pool')
|
|
16
16
|
const mockErrors = require('./lib/mock/mock-errors')
|
|
17
17
|
const ProxyAgent = require('./lib/proxy-agent')
|
|
18
|
+
const RetryHandler = require('./lib/handler/RetryHandler')
|
|
18
19
|
const { getGlobalDispatcher, setGlobalDispatcher } = require('./lib/global')
|
|
19
20
|
const DecoratorHandler = require('./lib/handler/DecoratorHandler')
|
|
20
21
|
const RedirectHandler = require('./lib/handler/RedirectHandler')
|
|
@@ -36,6 +37,7 @@ module.exports.Pool = Pool
|
|
|
36
37
|
module.exports.BalancedPool = BalancedPool
|
|
37
38
|
module.exports.Agent = Agent
|
|
38
39
|
module.exports.ProxyAgent = ProxyAgent
|
|
40
|
+
module.exports.RetryHandler = RetryHandler
|
|
39
41
|
|
|
40
42
|
module.exports.DecoratorHandler = DecoratorHandler
|
|
41
43
|
module.exports.RedirectHandler = RedirectHandler
|
package/lib/api/readable.js
CHANGED
|
@@ -16,6 +16,8 @@ const kBody = Symbol('kBody')
|
|
|
16
16
|
const kAbort = Symbol('abort')
|
|
17
17
|
const kContentType = Symbol('kContentType')
|
|
18
18
|
|
|
19
|
+
const noop = () => {}
|
|
20
|
+
|
|
19
21
|
module.exports = class BodyReadable extends Readable {
|
|
20
22
|
constructor ({
|
|
21
23
|
resume,
|
|
@@ -149,37 +151,50 @@ module.exports = class BodyReadable extends Readable {
|
|
|
149
151
|
return this[kBody]
|
|
150
152
|
}
|
|
151
153
|
|
|
152
|
-
|
|
154
|
+
dump (opts) {
|
|
153
155
|
let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144
|
|
154
156
|
const signal = opts && opts.signal
|
|
155
|
-
|
|
156
|
-
this.destroy()
|
|
157
|
-
}
|
|
158
|
-
let signalListenerCleanup
|
|
157
|
+
|
|
159
158
|
if (signal) {
|
|
160
|
-
|
|
161
|
-
|
|
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')
|
|
172
162
|
}
|
|
163
|
+
util.throwIfAborted(signal)
|
|
164
|
+
} catch (err) {
|
|
165
|
+
return Promise.reject(err)
|
|
173
166
|
}
|
|
174
|
-
} catch {
|
|
175
|
-
util.throwIfAborted(signal)
|
|
176
|
-
} finally {
|
|
177
|
-
if (typeof signalListenerCleanup === 'function') {
|
|
178
|
-
signalListenerCleanup()
|
|
179
|
-
} else if (signalListenerCleanup) {
|
|
180
|
-
signalListenerCleanup[Symbol.dispose]()
|
|
181
|
-
}
|
|
182
167
|
}
|
|
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
|
+
})
|
|
183
198
|
}
|
|
184
199
|
}
|
|
185
200
|
|
package/lib/client.js
CHANGED
|
@@ -1183,7 +1183,7 @@ async function connect (client) {
|
|
|
1183
1183
|
const idx = hostname.indexOf(']')
|
|
1184
1184
|
|
|
1185
1185
|
assert(idx !== -1)
|
|
1186
|
-
const ip = hostname.
|
|
1186
|
+
const ip = hostname.substring(1, idx)
|
|
1187
1187
|
|
|
1188
1188
|
assert(net.isIP(ip))
|
|
1189
1189
|
hostname = ip
|
|
@@ -1682,6 +1682,7 @@ function writeH2 (client, session, request) {
|
|
|
1682
1682
|
return false
|
|
1683
1683
|
}
|
|
1684
1684
|
|
|
1685
|
+
/** @type {import('node:http2').ClientHttp2Stream} */
|
|
1685
1686
|
let stream
|
|
1686
1687
|
const h2State = client[kHTTP2SessionState]
|
|
1687
1688
|
|
|
@@ -1777,14 +1778,10 @@ function writeH2 (client, session, request) {
|
|
|
1777
1778
|
const shouldEndStream = method === 'GET' || method === 'HEAD'
|
|
1778
1779
|
if (expectContinue) {
|
|
1779
1780
|
headers[HTTP2_HEADER_EXPECT] = '100-continue'
|
|
1780
|
-
/**
|
|
1781
|
-
* @type {import('node:http2').ClientHttp2Stream}
|
|
1782
|
-
*/
|
|
1783
1781
|
stream = session.request(headers, { endStream: shouldEndStream, signal })
|
|
1784
1782
|
|
|
1785
1783
|
stream.once('continue', writeBodyH2)
|
|
1786
1784
|
} else {
|
|
1787
|
-
/** @type {import('node:http2').ClientHttp2Stream} */
|
|
1788
1785
|
stream = session.request(headers, {
|
|
1789
1786
|
endStream: shouldEndStream,
|
|
1790
1787
|
signal
|
|
@@ -1796,7 +1793,9 @@ function writeH2 (client, session, request) {
|
|
|
1796
1793
|
++h2State.openStreams
|
|
1797
1794
|
|
|
1798
1795
|
stream.once('response', headers => {
|
|
1799
|
-
|
|
1796
|
+
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
|
|
1797
|
+
|
|
1798
|
+
if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) {
|
|
1800
1799
|
stream.pause()
|
|
1801
1800
|
}
|
|
1802
1801
|
})
|
|
@@ -1972,7 +1971,11 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
|
|
|
1972
1971
|
}
|
|
1973
1972
|
}
|
|
1974
1973
|
const onAbort = function () {
|
|
1975
|
-
|
|
1974
|
+
if (finished) {
|
|
1975
|
+
return
|
|
1976
|
+
}
|
|
1977
|
+
const err = new RequestAbortedError()
|
|
1978
|
+
queueMicrotask(() => onFinished(err))
|
|
1976
1979
|
}
|
|
1977
1980
|
const onFinished = function (err) {
|
|
1978
1981
|
if (finished) {
|
package/lib/core/errors.js
CHANGED
|
@@ -193,6 +193,19 @@ class ResponseExceededMaxSizeError extends UndiciError {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
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
|
+
|
|
196
209
|
module.exports = {
|
|
197
210
|
HTTPParserError,
|
|
198
211
|
UndiciError,
|
|
@@ -212,5 +225,6 @@ module.exports = {
|
|
|
212
225
|
NotSupportedError,
|
|
213
226
|
ResponseContentLengthMismatchError,
|
|
214
227
|
BalancedPoolMissingUpstreamError,
|
|
215
|
-
ResponseExceededMaxSizeError
|
|
228
|
+
ResponseExceededMaxSizeError,
|
|
229
|
+
RequestRetryError
|
|
216
230
|
}
|
package/lib/core/request.js
CHANGED
|
@@ -229,11 +229,7 @@ class Request {
|
|
|
229
229
|
|
|
230
230
|
onBodySent (chunk) {
|
|
231
231
|
if (this[kHandler].onBodySent) {
|
|
232
|
-
|
|
233
|
-
this[kHandler].onBodySent(chunk)
|
|
234
|
-
} catch (err) {
|
|
235
|
-
this.onError(err)
|
|
236
|
-
}
|
|
232
|
+
return this[kHandler].onBodySent(chunk)
|
|
237
233
|
}
|
|
238
234
|
}
|
|
239
235
|
|
|
@@ -243,11 +239,7 @@ class Request {
|
|
|
243
239
|
}
|
|
244
240
|
|
|
245
241
|
if (this[kHandler].onRequestSent) {
|
|
246
|
-
|
|
247
|
-
this[kHandler].onRequestSent()
|
|
248
|
-
} catch (err) {
|
|
249
|
-
this.onError(err)
|
|
250
|
-
}
|
|
242
|
+
return this[kHandler].onRequestSent()
|
|
251
243
|
}
|
|
252
244
|
}
|
|
253
245
|
|
package/lib/core/symbols.js
CHANGED
|
@@ -57,5 +57,6 @@ module.exports = {
|
|
|
57
57
|
kHTTP2BuildRequest: Symbol('http2 build request'),
|
|
58
58
|
kHTTP1BuildRequest: Symbol('http1 build request'),
|
|
59
59
|
kHTTP2CopyHeaders: Symbol('http2 copy headers'),
|
|
60
|
-
kHTTPConnVersion: Symbol('http connection version')
|
|
60
|
+
kHTTPConnVersion: Symbol('http connection version'),
|
|
61
|
+
kRetryHandlerDefaultRetry: Symbol('retry agent default retry')
|
|
61
62
|
}
|
package/lib/core/util.js
CHANGED
|
@@ -125,13 +125,13 @@ function getHostname (host) {
|
|
|
125
125
|
const idx = host.indexOf(']')
|
|
126
126
|
|
|
127
127
|
assert(idx !== -1)
|
|
128
|
-
return host.
|
|
128
|
+
return host.substring(1, idx)
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
const idx = host.indexOf(':')
|
|
132
132
|
if (idx === -1) return host
|
|
133
133
|
|
|
134
|
-
return host.
|
|
134
|
+
return host.substring(0, idx)
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// IP addresses are not valid server names per RFC6066
|
|
@@ -228,7 +228,7 @@ function parseHeaders (headers, obj = {}) {
|
|
|
228
228
|
|
|
229
229
|
if (!val) {
|
|
230
230
|
if (Array.isArray(headers[i + 1])) {
|
|
231
|
-
obj[key] = headers[i + 1]
|
|
231
|
+
obj[key] = headers[i + 1].map(x => x.toString('utf8'))
|
|
232
232
|
} else {
|
|
233
233
|
obj[key] = headers[i + 1].toString('utf8')
|
|
234
234
|
}
|
|
@@ -431,16 +431,7 @@ function throwIfAborted (signal) {
|
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
-
let events
|
|
435
434
|
function addAbortListener (signal, listener) {
|
|
436
|
-
if (typeof Symbol.dispose === 'symbol') {
|
|
437
|
-
if (!events) {
|
|
438
|
-
events = require('events')
|
|
439
|
-
}
|
|
440
|
-
if (typeof events.addAbortListener === 'function' && 'aborted' in signal) {
|
|
441
|
-
return events.addAbortListener(signal, listener)
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
435
|
if ('addEventListener' in signal) {
|
|
445
436
|
signal.addEventListener('abort', listener, { once: true })
|
|
446
437
|
return () => signal.removeEventListener('abort', listener)
|
|
@@ -464,6 +455,21 @@ function toUSVString (val) {
|
|
|
464
455
|
return `${val}`
|
|
465
456
|
}
|
|
466
457
|
|
|
458
|
+
// Parsed accordingly to RFC 9110
|
|
459
|
+
// https://www.rfc-editor.org/rfc/rfc9110#field.content-range
|
|
460
|
+
function parseRangeHeader (range) {
|
|
461
|
+
if (range == null || range === '') return { start: 0, end: null, size: null }
|
|
462
|
+
|
|
463
|
+
const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null
|
|
464
|
+
return m
|
|
465
|
+
? {
|
|
466
|
+
start: parseInt(m[1]),
|
|
467
|
+
end: m[2] ? parseInt(m[2]) : null,
|
|
468
|
+
size: m[3] ? parseInt(m[3]) : null
|
|
469
|
+
}
|
|
470
|
+
: null
|
|
471
|
+
}
|
|
472
|
+
|
|
467
473
|
const kEnumerableProperty = Object.create(null)
|
|
468
474
|
kEnumerableProperty.enumerable = true
|
|
469
475
|
|
|
@@ -497,7 +503,9 @@ module.exports = {
|
|
|
497
503
|
buildURL,
|
|
498
504
|
throwIfAborted,
|
|
499
505
|
addAbortListener,
|
|
506
|
+
parseRangeHeader,
|
|
500
507
|
nodeMajor,
|
|
501
508
|
nodeMinor,
|
|
502
|
-
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13)
|
|
509
|
+
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13),
|
|
510
|
+
safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE']
|
|
503
511
|
}
|