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
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
const assert = require('node:assert')
|
|
2
|
+
|
|
3
|
+
const { kRetryHandlerDefaultRetry } = require('../core/symbols')
|
|
4
|
+
const { RequestRetryError } = require('../core/errors')
|
|
5
|
+
const { isDisturbed, parseHeaders, parseRangeHeader } = require('../core/util')
|
|
6
|
+
|
|
7
|
+
function calculateRetryAfterHeader (retryAfter) {
|
|
8
|
+
const current = Date.now()
|
|
9
|
+
const diff = new Date(retryAfter).getTime() - current
|
|
10
|
+
|
|
11
|
+
return diff
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class RetryHandler {
|
|
15
|
+
constructor (opts, handlers) {
|
|
16
|
+
const { retryOptions, ...dispatchOpts } = opts
|
|
17
|
+
const {
|
|
18
|
+
// Retry scoped
|
|
19
|
+
retry: retryFn,
|
|
20
|
+
maxRetries,
|
|
21
|
+
maxTimeout,
|
|
22
|
+
minTimeout,
|
|
23
|
+
timeoutFactor,
|
|
24
|
+
// Response scoped
|
|
25
|
+
methods,
|
|
26
|
+
errorCodes,
|
|
27
|
+
retryAfter,
|
|
28
|
+
statusCodes
|
|
29
|
+
} = retryOptions ?? {}
|
|
30
|
+
|
|
31
|
+
this.dispatch = handlers.dispatch
|
|
32
|
+
this.handler = handlers.handler
|
|
33
|
+
this.opts = dispatchOpts
|
|
34
|
+
this.abort = null
|
|
35
|
+
this.aborted = false
|
|
36
|
+
this.retryOpts = {
|
|
37
|
+
retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
|
|
38
|
+
retryAfter: retryAfter ?? true,
|
|
39
|
+
maxTimeout: maxTimeout ?? 30 * 1000, // 30s,
|
|
40
|
+
timeout: minTimeout ?? 500, // .5s
|
|
41
|
+
timeoutFactor: timeoutFactor ?? 2,
|
|
42
|
+
maxRetries: maxRetries ?? 5,
|
|
43
|
+
// What errors we should retry
|
|
44
|
+
methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'],
|
|
45
|
+
// Indicates which errors to retry
|
|
46
|
+
statusCodes: statusCodes ?? [500, 502, 503, 504, 429],
|
|
47
|
+
// List of errors to retry
|
|
48
|
+
errorCodes: errorCodes ?? [
|
|
49
|
+
'ECONNRESET',
|
|
50
|
+
'ECONNREFUSED',
|
|
51
|
+
'ENOTFOUND',
|
|
52
|
+
'ENETDOWN',
|
|
53
|
+
'ENETUNREACH',
|
|
54
|
+
'EHOSTDOWN',
|
|
55
|
+
'EHOSTUNREACH',
|
|
56
|
+
'EPIPE'
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.retryCount = 0
|
|
61
|
+
this.start = 0
|
|
62
|
+
this.end = null
|
|
63
|
+
this.etag = null
|
|
64
|
+
this.resume = null
|
|
65
|
+
|
|
66
|
+
// Handle possible onConnect duplication
|
|
67
|
+
this.handler.onConnect(reason => {
|
|
68
|
+
this.aborted = true
|
|
69
|
+
if (this.abort) {
|
|
70
|
+
this.abort(reason)
|
|
71
|
+
} else {
|
|
72
|
+
this.reason = reason
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onRequestSent () {
|
|
78
|
+
if (this.handler.onRequestSent) {
|
|
79
|
+
this.handler.onRequestSent()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onUpgrade (statusCode, headers, socket) {
|
|
84
|
+
if (this.handler.onUpgrade) {
|
|
85
|
+
this.handler.onUpgrade(statusCode, headers, socket)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onConnect (abort) {
|
|
90
|
+
if (this.aborted) {
|
|
91
|
+
abort(this.reason)
|
|
92
|
+
} else {
|
|
93
|
+
this.abort = abort
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
onBodySent (chunk) {
|
|
98
|
+
return this.handler.onBodySent(chunk)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) {
|
|
102
|
+
const { statusCode, code, headers } = err
|
|
103
|
+
const { method, retryOptions } = opts
|
|
104
|
+
const {
|
|
105
|
+
maxRetries,
|
|
106
|
+
timeout,
|
|
107
|
+
maxTimeout,
|
|
108
|
+
timeoutFactor,
|
|
109
|
+
statusCodes,
|
|
110
|
+
errorCodes,
|
|
111
|
+
methods
|
|
112
|
+
} = retryOptions
|
|
113
|
+
let { counter, currentTimeout } = state
|
|
114
|
+
|
|
115
|
+
currentTimeout =
|
|
116
|
+
currentTimeout != null && currentTimeout > 0 ? currentTimeout : timeout
|
|
117
|
+
|
|
118
|
+
// Any code that is not a Undici's originated and allowed to retry
|
|
119
|
+
if (
|
|
120
|
+
code &&
|
|
121
|
+
code !== 'UND_ERR_REQ_RETRY' &&
|
|
122
|
+
code !== 'UND_ERR_SOCKET' &&
|
|
123
|
+
!errorCodes.includes(code)
|
|
124
|
+
) {
|
|
125
|
+
cb(err)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If a set of method are provided and the current method is not in the list
|
|
130
|
+
if (Array.isArray(methods) && !methods.includes(method)) {
|
|
131
|
+
cb(err)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// If a set of status code are provided and the current status code is not in the list
|
|
136
|
+
if (
|
|
137
|
+
statusCode != null &&
|
|
138
|
+
Array.isArray(statusCodes) &&
|
|
139
|
+
!statusCodes.includes(statusCode)
|
|
140
|
+
) {
|
|
141
|
+
cb(err)
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// If we reached the max number of retries
|
|
146
|
+
if (counter > maxRetries) {
|
|
147
|
+
cb(err)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let retryAfterHeader = headers != null && headers['retry-after']
|
|
152
|
+
if (retryAfterHeader) {
|
|
153
|
+
retryAfterHeader = Number(retryAfterHeader)
|
|
154
|
+
retryAfterHeader = isNaN(retryAfterHeader)
|
|
155
|
+
? calculateRetryAfterHeader(retryAfterHeader)
|
|
156
|
+
: retryAfterHeader * 1e3 // Retry-After is in seconds
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const retryTimeout =
|
|
160
|
+
retryAfterHeader > 0
|
|
161
|
+
? Math.min(retryAfterHeader, maxTimeout)
|
|
162
|
+
: Math.min(currentTimeout * timeoutFactor ** counter, maxTimeout)
|
|
163
|
+
|
|
164
|
+
state.currentTimeout = retryTimeout
|
|
165
|
+
|
|
166
|
+
setTimeout(() => cb(null), retryTimeout)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
170
|
+
const headers = parseHeaders(rawHeaders)
|
|
171
|
+
|
|
172
|
+
this.retryCount += 1
|
|
173
|
+
|
|
174
|
+
if (statusCode >= 300) {
|
|
175
|
+
this.abort(
|
|
176
|
+
new RequestRetryError('Request failed', statusCode, {
|
|
177
|
+
headers,
|
|
178
|
+
count: this.retryCount
|
|
179
|
+
})
|
|
180
|
+
)
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Checkpoint for resume from where we left it
|
|
185
|
+
if (this.resume != null) {
|
|
186
|
+
this.resume = null
|
|
187
|
+
|
|
188
|
+
if (statusCode !== 206) {
|
|
189
|
+
return true
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const contentRange = parseRangeHeader(headers['content-range'])
|
|
193
|
+
// If no content range
|
|
194
|
+
if (!contentRange) {
|
|
195
|
+
this.abort(
|
|
196
|
+
new RequestRetryError('Content-Range mismatch', statusCode, {
|
|
197
|
+
headers,
|
|
198
|
+
count: this.retryCount
|
|
199
|
+
})
|
|
200
|
+
)
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Let's start with a weak etag check
|
|
205
|
+
if (this.etag != null && this.etag !== headers.etag) {
|
|
206
|
+
this.abort(
|
|
207
|
+
new RequestRetryError('ETag mismatch', statusCode, {
|
|
208
|
+
headers,
|
|
209
|
+
count: this.retryCount
|
|
210
|
+
})
|
|
211
|
+
)
|
|
212
|
+
return false
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const { start, size, end = size } = contentRange
|
|
216
|
+
|
|
217
|
+
assert(this.start === start, 'content-range mismatch')
|
|
218
|
+
assert(this.end == null || this.end === end, 'content-range mismatch')
|
|
219
|
+
|
|
220
|
+
this.resume = resume
|
|
221
|
+
return true
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (this.end == null) {
|
|
225
|
+
if (statusCode === 206) {
|
|
226
|
+
// First time we receive 206
|
|
227
|
+
const range = parseRangeHeader(headers['content-range'])
|
|
228
|
+
|
|
229
|
+
if (range == null) {
|
|
230
|
+
return this.handler.onHeaders(
|
|
231
|
+
statusCode,
|
|
232
|
+
rawHeaders,
|
|
233
|
+
resume,
|
|
234
|
+
statusMessage
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const { start, size, end = size } = range
|
|
239
|
+
|
|
240
|
+
assert(
|
|
241
|
+
start != null && Number.isFinite(start) && this.start !== start,
|
|
242
|
+
'content-range mismatch'
|
|
243
|
+
)
|
|
244
|
+
assert(Number.isFinite(start))
|
|
245
|
+
assert(
|
|
246
|
+
end != null && Number.isFinite(end) && this.end !== end,
|
|
247
|
+
'invalid content-length'
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
this.start = start
|
|
251
|
+
this.end = end
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// We make our best to checkpoint the body for further range headers
|
|
255
|
+
if (this.end == null) {
|
|
256
|
+
const contentLength = headers['content-length']
|
|
257
|
+
this.end = contentLength != null ? Number(contentLength) : null
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
assert(Number.isFinite(this.start))
|
|
261
|
+
assert(
|
|
262
|
+
this.end == null || Number.isFinite(this.end),
|
|
263
|
+
'invalid content-length'
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
this.resume = resume
|
|
267
|
+
this.etag = headers.etag != null ? headers.etag : null
|
|
268
|
+
|
|
269
|
+
return this.handler.onHeaders(
|
|
270
|
+
statusCode,
|
|
271
|
+
rawHeaders,
|
|
272
|
+
resume,
|
|
273
|
+
statusMessage
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const err = new RequestRetryError('Request failed', statusCode, {
|
|
278
|
+
headers,
|
|
279
|
+
count: this.retryCount
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
this.abort(err)
|
|
283
|
+
|
|
284
|
+
return false
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
onData (chunk) {
|
|
288
|
+
this.start += chunk.length
|
|
289
|
+
|
|
290
|
+
return this.handler.onData(chunk)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
onComplete (rawTrailers) {
|
|
294
|
+
this.retryCount = 0
|
|
295
|
+
return this.handler.onComplete(rawTrailers)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
onError (err) {
|
|
299
|
+
if (this.aborted || isDisturbed(this.opts.body)) {
|
|
300
|
+
return this.handler.onError(err)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this.retryOpts.retry(
|
|
304
|
+
err,
|
|
305
|
+
{
|
|
306
|
+
state: { counter: this.retryCount++, currentTimeout: this.retryAfter },
|
|
307
|
+
opts: { retryOptions: this.retryOpts, ...this.opts }
|
|
308
|
+
},
|
|
309
|
+
onRetry.bind(this)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
function onRetry (err) {
|
|
313
|
+
if (err != null || this.aborted || isDisturbed(this.opts.body)) {
|
|
314
|
+
return this.handler.onError(err)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (this.start !== 0) {
|
|
318
|
+
this.opts = {
|
|
319
|
+
...this.opts,
|
|
320
|
+
headers: {
|
|
321
|
+
...this.opts.headers,
|
|
322
|
+
range: `bytes=${this.start}-${this.end ?? ''}`
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
this.dispatch(this.opts, this)
|
|
329
|
+
} catch (err) {
|
|
330
|
+
this.handler.onError(err)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
module.exports = RetryHandler
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.28.0",
|
|
4
4
|
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
|
5
5
|
"homepage": "https://undici.nodejs.org",
|
|
6
6
|
"bugs": {
|
|
@@ -118,6 +118,7 @@
|
|
|
118
118
|
"jsdom": "^22.1.0",
|
|
119
119
|
"jsfuzz": "^1.0.15",
|
|
120
120
|
"mocha": "^10.0.0",
|
|
121
|
+
"mockttp": "^3.9.2",
|
|
121
122
|
"p-timeout": "^3.2.0",
|
|
122
123
|
"pre-commit": "^1.2.2",
|
|
123
124
|
"proxy": "^1.0.2",
|
package/types/client.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export declare namespace Client {
|
|
|
77
77
|
*/
|
|
78
78
|
allowH2?: boolean;
|
|
79
79
|
/**
|
|
80
|
-
* @description Dictates the maximum number of concurrent streams for a single H2 session. It can be
|
|
80
|
+
* @description Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame.
|
|
81
81
|
* @default 100
|
|
82
82
|
*/
|
|
83
83
|
maxConcurrentStreams?: number
|
package/types/dispatcher.d.ts
CHANGED
|
@@ -211,7 +211,7 @@ declare namespace Dispatcher {
|
|
|
211
211
|
/** Invoked when request is upgraded either due to a `Upgrade` header or `CONNECT` method. */
|
|
212
212
|
onUpgrade?(statusCode: number, headers: Buffer[] | string[] | null, socket: Duplex): void;
|
|
213
213
|
/** Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. */
|
|
214
|
-
onHeaders?(statusCode: number, headers: Buffer[] | string[] | null, resume: () => void): boolean;
|
|
214
|
+
onHeaders?(statusCode: number, headers: Buffer[] | string[] | null, resume: () => void, statusText: string): boolean;
|
|
215
215
|
/** Invoked when response payload data is received. */
|
|
216
216
|
onData?(chunk: Buffer): boolean;
|
|
217
217
|
/** Invoked when response payload and trailers have been received and the request has completed. */
|
package/types/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import MockPool from'./mock-pool'
|
|
|
14
14
|
import MockAgent from'./mock-agent'
|
|
15
15
|
import mockErrors from'./mock-errors'
|
|
16
16
|
import ProxyAgent from'./proxy-agent'
|
|
17
|
+
import RetryHandler from'./retry-handler'
|
|
17
18
|
import { request, pipeline, stream, connect, upgrade } from './api'
|
|
18
19
|
|
|
19
20
|
export * from './cookies'
|
|
@@ -27,7 +28,7 @@ export * from './content-type'
|
|
|
27
28
|
export * from './cache'
|
|
28
29
|
export { Interceptable } from './mock-interceptor'
|
|
29
30
|
|
|
30
|
-
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, RedirectHandler, DecoratorHandler }
|
|
31
|
+
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, RedirectHandler, DecoratorHandler, RetryHandler }
|
|
31
32
|
export default Undici
|
|
32
33
|
|
|
33
34
|
declare namespace Undici {
|
|
@@ -35,6 +36,7 @@ declare namespace Undici {
|
|
|
35
36
|
var Pool: typeof import('./pool').default;
|
|
36
37
|
var RedirectHandler: typeof import ('./handlers').RedirectHandler
|
|
37
38
|
var DecoratorHandler: typeof import ('./handlers').DecoratorHandler
|
|
39
|
+
var RetryHandler: typeof import ('./retry-handler').default
|
|
38
40
|
var createRedirectInterceptor: typeof import ('./interceptors').createRedirectInterceptor
|
|
39
41
|
var BalancedPool: typeof import('./balanced-pool').default;
|
|
40
42
|
var Client: typeof import('./client').default;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import Dispatcher from "./dispatcher";
|
|
2
|
+
|
|
3
|
+
export default RetryHandler;
|
|
4
|
+
|
|
5
|
+
declare class RetryHandler implements Dispatcher.DispatchHandlers {
|
|
6
|
+
constructor(
|
|
7
|
+
options: Dispatcher.DispatchOptions & {
|
|
8
|
+
retryOptions?: RetryHandler.RetryOptions;
|
|
9
|
+
},
|
|
10
|
+
retryHandlers: RetryHandler.RetryHandlers
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare namespace RetryHandler {
|
|
15
|
+
export type RetryState = { counter: number; currentTimeout: number };
|
|
16
|
+
|
|
17
|
+
export type RetryContext = {
|
|
18
|
+
state: RetryState;
|
|
19
|
+
opts: Dispatcher.DispatchOptions & {
|
|
20
|
+
retryOptions?: RetryHandler.RetryOptions;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type OnRetryCallback = (result?: Error | null) => void;
|
|
25
|
+
|
|
26
|
+
export type RetryCallback = (
|
|
27
|
+
err: Error,
|
|
28
|
+
context: {
|
|
29
|
+
state: RetryState;
|
|
30
|
+
opts: Dispatcher.DispatchOptions & {
|
|
31
|
+
retryOptions?: RetryHandler.RetryOptions;
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
callback: OnRetryCallback
|
|
35
|
+
) => number | null;
|
|
36
|
+
|
|
37
|
+
export interface RetryOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Callback to be invoked on every retry iteration.
|
|
40
|
+
* It receives the error, current state of the retry object and the options object
|
|
41
|
+
* passed when instantiating the retry handler.
|
|
42
|
+
*
|
|
43
|
+
* @type {RetryCallback}
|
|
44
|
+
* @memberof RetryOptions
|
|
45
|
+
*/
|
|
46
|
+
retry?: RetryCallback;
|
|
47
|
+
/**
|
|
48
|
+
* Maximum number of retries to allow.
|
|
49
|
+
*
|
|
50
|
+
* @type {number}
|
|
51
|
+
* @memberof RetryOptions
|
|
52
|
+
* @default 5
|
|
53
|
+
*/
|
|
54
|
+
maxRetries?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Max number of milliseconds allow between retries
|
|
57
|
+
*
|
|
58
|
+
* @type {number}
|
|
59
|
+
* @memberof RetryOptions
|
|
60
|
+
* @default 30000
|
|
61
|
+
*/
|
|
62
|
+
maxTimeout?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Initial number of milliseconds to wait before retrying for the first time.
|
|
65
|
+
*
|
|
66
|
+
* @type {number}
|
|
67
|
+
* @memberof RetryOptions
|
|
68
|
+
* @default 500
|
|
69
|
+
*/
|
|
70
|
+
minTimeout?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Factior to multiply the timeout factor between retries.
|
|
73
|
+
*
|
|
74
|
+
* @type {number}
|
|
75
|
+
* @memberof RetryOptions
|
|
76
|
+
* @default 2
|
|
77
|
+
*/
|
|
78
|
+
timeoutFactor?: number;
|
|
79
|
+
/**
|
|
80
|
+
* It enables to automatically infer timeout between retries based on the `Retry-After` header.
|
|
81
|
+
*
|
|
82
|
+
* @type {boolean}
|
|
83
|
+
* @memberof RetryOptions
|
|
84
|
+
* @default true
|
|
85
|
+
*/
|
|
86
|
+
retryAfter?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* HTTP methods to retry.
|
|
89
|
+
*
|
|
90
|
+
* @type {Dispatcher.HttpMethod[]}
|
|
91
|
+
* @memberof RetryOptions
|
|
92
|
+
* @default ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'],
|
|
93
|
+
*/
|
|
94
|
+
methods?: Dispatcher.HttpMethod[];
|
|
95
|
+
/**
|
|
96
|
+
* Error codes to be retried. e.g. `ECONNRESET`, `ENOTFOUND`, `ETIMEDOUT`, `ECONNREFUSED`, etc.
|
|
97
|
+
*
|
|
98
|
+
* @type {string[]}
|
|
99
|
+
* @default ['ECONNRESET','ECONNREFUSED','ENOTFOUND','ENETDOWN','ENETUNREACH','EHOSTDOWN','EHOSTUNREACH','EPIPE']
|
|
100
|
+
*/
|
|
101
|
+
errorCodes?: string[];
|
|
102
|
+
/**
|
|
103
|
+
* HTTP status codes to be retried.
|
|
104
|
+
*
|
|
105
|
+
* @type {number[]}
|
|
106
|
+
* @memberof RetryOptions
|
|
107
|
+
* @default [500, 502, 503, 504, 429],
|
|
108
|
+
*/
|
|
109
|
+
statusCodes?: number[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface RetryHandlers {
|
|
113
|
+
dispatch: Dispatcher["dispatch"];
|
|
114
|
+
handler: Dispatcher.DispatchHandlers;
|
|
115
|
+
}
|
|
116
|
+
}
|