@nxtedition/nxt-undici 6.2.12 → 6.2.14
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/lib/interceptor/dns.js +3 -2
- package/lib/interceptor/response-error.js +29 -71
- package/lib/interceptor/response-retry.js +144 -141
- package/lib/utils.js +68 -1
- package/package.json +1 -1
package/lib/interceptor/dns.js
CHANGED
|
@@ -42,8 +42,11 @@ export default () => (dispatch) => {
|
|
|
42
42
|
|
|
43
43
|
if (err) {
|
|
44
44
|
logger?.error({ err, dns: { hostname } }, 'lookup failed')
|
|
45
|
+
|
|
45
46
|
resolve([err, null])
|
|
46
47
|
} else {
|
|
48
|
+
logger?.debug({ dns: { hostname, records } }, 'lookup completed')
|
|
49
|
+
|
|
47
50
|
const now = getFastNow()
|
|
48
51
|
const val = records.map(({ address, ttl }) => ({
|
|
49
52
|
address,
|
|
@@ -53,8 +56,6 @@ export default () => (dispatch) => {
|
|
|
53
56
|
counter: 0,
|
|
54
57
|
}))
|
|
55
58
|
|
|
56
|
-
logger?.debug({ err, dns: { hostname, records } }, 'lookup completed')
|
|
57
|
-
|
|
58
59
|
cache.set(hostname, val)
|
|
59
60
|
|
|
60
61
|
resolve([null, val])
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { DecoratorHandler } from '../utils.js'
|
|
1
|
+
import { DecoratorHandler, decorateError } from '../utils.js'
|
|
3
2
|
|
|
4
3
|
class Handler extends DecoratorHandler {
|
|
5
4
|
#statusCode = 0
|
|
6
|
-
#contentType
|
|
7
5
|
#decoder
|
|
8
6
|
#headers
|
|
7
|
+
#trailers
|
|
9
8
|
#body = ''
|
|
10
9
|
#opts
|
|
11
10
|
|
|
@@ -15,13 +14,8 @@ class Handler extends DecoratorHandler {
|
|
|
15
14
|
this.#opts = opts
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
#checkContentType(contentType) {
|
|
19
|
-
return (this.#contentType ?? '').indexOf(contentType) === 0
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
onConnect(abort) {
|
|
23
18
|
this.#statusCode = 0
|
|
24
|
-
this.#contentType = null
|
|
25
19
|
this.#decoder = null
|
|
26
20
|
this.#headers = null
|
|
27
21
|
this.#body = ''
|
|
@@ -32,14 +26,17 @@ class Handler extends DecoratorHandler {
|
|
|
32
26
|
onHeaders(statusCode, headers, resume) {
|
|
33
27
|
this.#statusCode = statusCode
|
|
34
28
|
this.#headers = headers
|
|
35
|
-
this.#contentType = headers['content-type']
|
|
36
29
|
|
|
37
30
|
if (this.#statusCode < 400) {
|
|
38
31
|
return super.onHeaders(statusCode, headers, resume)
|
|
39
32
|
}
|
|
40
33
|
|
|
41
|
-
if (
|
|
34
|
+
if (
|
|
35
|
+
this.#headers['content-type']?.startsWith('application/json') ||
|
|
36
|
+
this.#headers['content-type']?.startsWith('text/plain')
|
|
37
|
+
) {
|
|
42
38
|
this.#decoder = new TextDecoder('utf-8')
|
|
39
|
+
this.#body = ''
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
42
|
|
|
@@ -52,72 +49,33 @@ class Handler extends DecoratorHandler {
|
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
onComplete(trailers) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
this.#body = JSON.parse(this.#body)
|
|
61
|
-
} catch {
|
|
62
|
-
// Do nothing...
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let err
|
|
67
|
-
const stackTraceLimit = Error.stackTraceLimit
|
|
68
|
-
Error.stackTraceLimit = 0
|
|
69
|
-
try {
|
|
70
|
-
err = createHttpError(this.#statusCode)
|
|
71
|
-
} finally {
|
|
72
|
-
Error.stackTraceLimit = stackTraceLimit
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
super.onError(this.#decorateError(err))
|
|
76
|
-
} else {
|
|
77
|
-
super.onComplete(trailers)
|
|
52
|
+
this.#trailers = trailers
|
|
53
|
+
|
|
54
|
+
if (this.#statusCode < 400) {
|
|
55
|
+
return super.onComplete(trailers)
|
|
78
56
|
}
|
|
79
|
-
}
|
|
80
57
|
|
|
81
|
-
|
|
82
|
-
|
|
58
|
+
this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''
|
|
59
|
+
|
|
60
|
+
super.onError(
|
|
61
|
+
decorateError(null, this.#opts, {
|
|
62
|
+
statusCode: this.#statusCode,
|
|
63
|
+
headers: this.#headers,
|
|
64
|
+
trailers: this.#trailers,
|
|
65
|
+
body: this.#body,
|
|
66
|
+
}),
|
|
67
|
+
)
|
|
83
68
|
}
|
|
84
69
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
err
|
|
88
|
-
|
|
89
|
-
err.req = {
|
|
90
|
-
method: this.#opts?.method,
|
|
91
|
-
headers: this.#opts?.headers,
|
|
92
|
-
body:
|
|
93
|
-
// TODO (fix): JSON.stringify POJO
|
|
94
|
-
typeof this.#opts?.body !== 'string' || this.#opts.body.length > 1024
|
|
95
|
-
? undefined
|
|
96
|
-
: this.#opts.body,
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
err.res = {
|
|
70
|
+
onError(err) {
|
|
71
|
+
super.onError(
|
|
72
|
+
decorateError(err, this.#opts, {
|
|
73
|
+
statusCode: this.#statusCode,
|
|
100
74
|
headers: this.#headers,
|
|
101
|
-
|
|
102
|
-
body:
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (this.#body) {
|
|
106
|
-
if (this.#body.reason != null) {
|
|
107
|
-
err.reason ??= this.#body.reason
|
|
108
|
-
}
|
|
109
|
-
if (this.#body.code != null) {
|
|
110
|
-
err.code ??= this.#body.code
|
|
111
|
-
}
|
|
112
|
-
if (this.#body.error != null) {
|
|
113
|
-
err.error ??= this.#body.error
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return err
|
|
118
|
-
} catch (er) {
|
|
119
|
-
return new AggregateError([er, err])
|
|
120
|
-
}
|
|
75
|
+
trailers: this.#trailers,
|
|
76
|
+
body: null,
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
121
79
|
}
|
|
122
80
|
}
|
|
123
81
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import tp from 'node:timers/promises'
|
|
3
|
-
import { DecoratorHandler, isDisturbed, parseRangeHeader } from '../utils.js'
|
|
4
|
-
import createHttpError from 'http-errors'
|
|
3
|
+
import { DecoratorHandler, isDisturbed, decorateError, parseRangeHeader } from '../utils.js'
|
|
5
4
|
|
|
6
5
|
function noop() {}
|
|
7
6
|
|
|
@@ -11,12 +10,14 @@ class Handler extends DecoratorHandler {
|
|
|
11
10
|
#opts
|
|
12
11
|
|
|
13
12
|
#retryCount = 0
|
|
13
|
+
#retryError = null
|
|
14
14
|
#headersSent = false
|
|
15
15
|
#errorSent = false
|
|
16
16
|
|
|
17
17
|
#statusCode = 0
|
|
18
|
-
#headers
|
|
19
|
-
#trailers
|
|
18
|
+
#headers
|
|
19
|
+
#trailers
|
|
20
|
+
#body
|
|
20
21
|
|
|
21
22
|
#abort
|
|
22
23
|
#aborted = false
|
|
@@ -26,7 +27,6 @@ class Handler extends DecoratorHandler {
|
|
|
26
27
|
#pos
|
|
27
28
|
#end
|
|
28
29
|
#etag
|
|
29
|
-
#error
|
|
30
30
|
|
|
31
31
|
constructor(opts, { handler, dispatch }) {
|
|
32
32
|
super(handler)
|
|
@@ -47,13 +47,13 @@ class Handler extends DecoratorHandler {
|
|
|
47
47
|
onConnect(abort) {
|
|
48
48
|
this.#statusCode = 0
|
|
49
49
|
this.#headers = null
|
|
50
|
+
this.#body = null
|
|
50
51
|
this.#trailers = null
|
|
51
52
|
|
|
52
53
|
if (!this.#headersSent) {
|
|
53
54
|
this.#pos = null
|
|
54
55
|
this.#end = null
|
|
55
56
|
this.#etag = null
|
|
56
|
-
this.#error = null
|
|
57
57
|
this.#resume = null
|
|
58
58
|
|
|
59
59
|
super.onConnect((reason) => {
|
|
@@ -79,25 +79,28 @@ class Handler extends DecoratorHandler {
|
|
|
79
79
|
this.#statusCode = statusCode
|
|
80
80
|
this.#headers = headers
|
|
81
81
|
|
|
82
|
-
if (this.#
|
|
82
|
+
if (!this.#headersSent) {
|
|
83
83
|
assert(this.#etag == null)
|
|
84
84
|
assert(this.#pos == null)
|
|
85
85
|
assert(this.#end == null)
|
|
86
86
|
assert(this.#headersSent === false)
|
|
87
87
|
|
|
88
88
|
if (headers.trailer) {
|
|
89
|
-
|
|
89
|
+
this.#headersSent = true
|
|
90
|
+
return super.onHeaders(statusCode, headers, resume)
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
const contentLength = headers['content-length'] ? Number(headers['content-length']) : null
|
|
93
94
|
if (contentLength != null && !Number.isFinite(contentLength)) {
|
|
94
|
-
|
|
95
|
+
this.#headersSent = true
|
|
96
|
+
return super.onHeaders(statusCode, headers, resume)
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
if (statusCode === 206) {
|
|
98
100
|
const range = parseRangeHeader(headers['content-range'])
|
|
99
101
|
if (!range) {
|
|
100
|
-
|
|
102
|
+
this.#headersSent = true
|
|
103
|
+
return super.onHeaders(statusCode, headers, resume)
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
const { start, size, end = size } = range
|
|
@@ -117,9 +120,11 @@ class Handler extends DecoratorHandler {
|
|
|
117
120
|
this.#end = contentLength
|
|
118
121
|
this.#etag = headers.etag
|
|
119
122
|
} else if (statusCode >= 400) {
|
|
123
|
+
this.#body = []
|
|
120
124
|
return true
|
|
121
125
|
} else {
|
|
122
|
-
|
|
126
|
+
this.#headersSent = true
|
|
127
|
+
return super.onHeaders(statusCode, headers, resume)
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
// Weak etags are not useful for comparison nor cache
|
|
@@ -134,17 +139,20 @@ class Handler extends DecoratorHandler {
|
|
|
134
139
|
|
|
135
140
|
this.#resume = resume
|
|
136
141
|
|
|
137
|
-
|
|
142
|
+
this.#headersSent = true
|
|
143
|
+
return super.onHeaders(statusCode, headers, () => this.#resume?.())
|
|
138
144
|
} else if (statusCode === 206 || (this.#pos === 0 && statusCode === 200)) {
|
|
139
145
|
assert(this.#etag != null || !this.#pos)
|
|
140
146
|
|
|
141
147
|
if (this.#pos > 0 && this.#etag !== headers.etag) {
|
|
142
|
-
|
|
148
|
+
this.#maybeError(null)
|
|
149
|
+
return null
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
const contentRange = parseRangeHeader(headers['content-range'])
|
|
146
153
|
if (!contentRange) {
|
|
147
|
-
|
|
154
|
+
this.#maybeError(null)
|
|
155
|
+
return null
|
|
148
156
|
}
|
|
149
157
|
|
|
150
158
|
const { start, size, end = size } = contentRange
|
|
@@ -156,72 +164,93 @@ class Handler extends DecoratorHandler {
|
|
|
156
164
|
// TODO (fix): What if we were paused before the error?
|
|
157
165
|
return true
|
|
158
166
|
} else {
|
|
159
|
-
|
|
167
|
+
this.#maybeError(this.#retryError)
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
onData(chunk) {
|
|
164
|
-
if (this.#statusCode >= 400) {
|
|
165
|
-
// TODO (fix): Limit the amount of data we read?
|
|
166
|
-
return true
|
|
167
|
-
}
|
|
168
|
-
|
|
169
172
|
if (this.#pos != null) {
|
|
170
173
|
this.#pos += chunk.byteLength
|
|
171
174
|
}
|
|
172
|
-
return super.onData(chunk)
|
|
173
|
-
}
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
if (this.#statusCode < 400) {
|
|
177
|
+
return super.onData(chunk)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.#body?.push(chunk)
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
onComplete(trailers) {
|
|
180
184
|
this.#trailers = trailers
|
|
181
185
|
|
|
182
|
-
if (this.#statusCode
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
+
if (this.#statusCode < 400) {
|
|
187
|
+
return super.onComplete(trailers)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.#maybeRetry(null)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
onError(err) {
|
|
194
|
+
this.#maybeRetry(err)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#maybeAbort(err) {
|
|
198
|
+
if (this.#abort && !this.#aborted) {
|
|
199
|
+
this.#aborted = true
|
|
200
|
+
this.#abort(err)
|
|
186
201
|
}
|
|
187
202
|
}
|
|
188
203
|
|
|
189
204
|
#maybeError(err) {
|
|
190
205
|
if (err) {
|
|
191
|
-
this.#
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
if (!this.#errorSent) {
|
|
207
|
+
this.#errorSent = true
|
|
208
|
+
super.onError(err)
|
|
209
|
+
}
|
|
210
|
+
} else if (!this.#headersSent) {
|
|
211
|
+
super.onHeaders(this.#statusCode, this.#headers, noop)
|
|
212
|
+
if (this.#aborted) {
|
|
213
|
+
return
|
|
214
|
+
}
|
|
198
215
|
|
|
199
|
-
if (
|
|
200
|
-
|
|
216
|
+
if (this.#body) {
|
|
217
|
+
for (const chunk of this.#body) {
|
|
218
|
+
super.onData(chunk)
|
|
219
|
+
if (this.#aborted) {
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
}
|
|
201
223
|
}
|
|
224
|
+
|
|
225
|
+
super.onComplete(this.#trailers)
|
|
202
226
|
}
|
|
227
|
+
|
|
228
|
+
this.#maybeAbort(err)
|
|
203
229
|
}
|
|
204
230
|
|
|
205
|
-
#maybeRetry(err
|
|
231
|
+
#maybeRetry(err) {
|
|
206
232
|
if (this.#aborted || isDisturbed(this.#opts.body) || (this.#pos && !this.#etag)) {
|
|
207
233
|
this.#maybeError(err)
|
|
208
234
|
return
|
|
209
235
|
}
|
|
210
236
|
|
|
211
|
-
if (!err) {
|
|
212
|
-
// TOOD (fix): Avoid creating an Error and do onHeaders + onComplete.
|
|
213
|
-
const stackTraceLimit = Error.stackTraceLimit
|
|
214
|
-
Error.stackTraceLimit = 0
|
|
215
|
-
try {
|
|
216
|
-
err = createHttpError(statusCode ?? 500)
|
|
217
|
-
} finally {
|
|
218
|
-
Error.stackTraceLimit = stackTraceLimit
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
237
|
let retryPromise
|
|
223
238
|
try {
|
|
224
|
-
|
|
239
|
+
if (typeof this.#opts.retry === 'function') {
|
|
240
|
+
retryPromise = this.#opts.retry(
|
|
241
|
+
decorateError(err, this.#opts, {
|
|
242
|
+
statusCode: this.#statusCode,
|
|
243
|
+
headers: this.#headers,
|
|
244
|
+
trailers: this.#trailers,
|
|
245
|
+
body: this.#body,
|
|
246
|
+
}),
|
|
247
|
+
this.#retryCount,
|
|
248
|
+
this.#opts,
|
|
249
|
+
() => this.#retryFn(err, this.#retryCount, this.#opts),
|
|
250
|
+
)
|
|
251
|
+
} else {
|
|
252
|
+
retryPromise = this.#retryFn(err, this.#retryCount, this.#opts)
|
|
253
|
+
}
|
|
225
254
|
} catch (err) {
|
|
226
255
|
retryPromise = Promise.reject(err)
|
|
227
256
|
}
|
|
@@ -231,123 +260,97 @@ class Handler extends DecoratorHandler {
|
|
|
231
260
|
return
|
|
232
261
|
}
|
|
233
262
|
|
|
234
|
-
this.#error = err
|
|
235
|
-
|
|
236
263
|
retryPromise
|
|
237
|
-
.then((
|
|
264
|
+
.then((shouldRetry) => {
|
|
238
265
|
if (this.#aborted) {
|
|
239
|
-
this.#
|
|
240
|
-
} else if (isDisturbed(this.#opts.body)) {
|
|
241
|
-
this.#
|
|
266
|
+
this.#maybeError(this.#reason)
|
|
267
|
+
} else if (shouldRetry === false || isDisturbed(this.#opts.body)) {
|
|
268
|
+
this.#maybeError(err)
|
|
242
269
|
} else if (!this.#headersSent) {
|
|
243
|
-
this.#retryCount++
|
|
244
270
|
this.#opts.logger?.debug({ err, retryCount: this.#retryCount }, 'retry response headers')
|
|
271
|
+
|
|
272
|
+
this.#retryCount++
|
|
273
|
+
this.#retryError = err
|
|
274
|
+
|
|
245
275
|
this.#dispatch(this.#opts, this)
|
|
246
276
|
} else {
|
|
247
277
|
assert(Number.isFinite(this.#pos))
|
|
248
278
|
assert(this.#end == null || (Number.isFinite(this.#end) && this.#end > 0))
|
|
249
279
|
|
|
250
|
-
this.#opts =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
headers: {
|
|
254
|
-
...this.#opts.headers,
|
|
255
|
-
...opts?.headers,
|
|
256
|
-
'if-match': this.#etag,
|
|
257
|
-
range: `bytes=${this.#pos}-${this.#end ? this.#end - 1 : ''}`,
|
|
258
|
-
},
|
|
259
|
-
}
|
|
280
|
+
this.#opts.headers['if-match'] = this.#etag
|
|
281
|
+
this.#opts.headers.range = `bytes=${this.#pos}-${this.#end ? this.#end - 1 : ''}`
|
|
282
|
+
this.#opts.logger?.debug({ err, retryCount: this.#retryCount }, 'retry response body')
|
|
260
283
|
|
|
261
284
|
this.#retryCount++
|
|
262
|
-
this.#
|
|
285
|
+
this.#retryError = err
|
|
286
|
+
|
|
263
287
|
this.#dispatch(this.#opts, this)
|
|
264
288
|
}
|
|
265
289
|
})
|
|
266
290
|
.catch((err) => {
|
|
267
|
-
|
|
268
|
-
this.#onError(err)
|
|
269
|
-
}
|
|
291
|
+
this.#maybeError(err)
|
|
270
292
|
})
|
|
271
293
|
}
|
|
272
294
|
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
this.#errorSent = true
|
|
276
|
-
super.onError(err)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
#onHeaders(statusCode, headers, resume) {
|
|
280
|
-
assert(!this.#headersSent)
|
|
281
|
-
this.#headersSent = true
|
|
282
|
-
return super.onHeaders(statusCode, headers, resume)
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export default () => (dispatch) => (opts, handler) =>
|
|
287
|
-
opts.retry !== false &&
|
|
288
|
-
!opts.upgrade &&
|
|
289
|
-
(opts.method === 'HEAD' ||
|
|
290
|
-
opts.method === 'GET' ||
|
|
291
|
-
opts.method === 'PUT' ||
|
|
292
|
-
opts.method === 'PATCH' ||
|
|
293
|
-
opts.idempotent)
|
|
294
|
-
? dispatch(opts, new Handler(opts, { handler, dispatch }))
|
|
295
|
-
: dispatch(opts, handler)
|
|
296
|
-
|
|
297
|
-
async function retryFn(err, retryCount, opts) {
|
|
298
|
-
let retryOpts = opts?.retry
|
|
299
|
-
|
|
300
|
-
if (!retryOpts) {
|
|
301
|
-
throw err
|
|
302
|
-
}
|
|
295
|
+
async #retryFn(err, retryCount, opts) {
|
|
296
|
+
let retryOpts = opts?.retry
|
|
303
297
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
298
|
+
if (!retryOpts) {
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
307
301
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
302
|
+
if (typeof retryOpts === 'number') {
|
|
303
|
+
retryOpts = { count: retryOpts }
|
|
304
|
+
}
|
|
311
305
|
|
|
312
|
-
|
|
306
|
+
const retryMax = retryOpts?.count ?? 8
|
|
313
307
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
308
|
+
if (retryCount > retryMax) {
|
|
309
|
+
return false
|
|
310
|
+
}
|
|
317
311
|
|
|
318
|
-
|
|
312
|
+
const statusCode =
|
|
313
|
+
err?.statusCode ?? err?.status ?? err?.$metadata?.httpStatusCode ?? this.#statusCode
|
|
314
|
+
const headers = err?.headers ?? this.#headers
|
|
315
|
+
|
|
316
|
+
if (statusCode && [420, 429, 502, 503, 504].includes(statusCode)) {
|
|
317
|
+
const retryAfter = headers?.['retry-after'] ? Number(headers['retry-after']) * 1e3 : null
|
|
318
|
+
const delay =
|
|
319
|
+
retryAfter != null && Number.isFinite(retryAfter)
|
|
320
|
+
? retryAfter
|
|
321
|
+
: Math.min(10e3, retryCount * 1e3)
|
|
322
|
+
return tp.setTimeout(delay, true, { signal: opts.signal })
|
|
323
|
+
}
|
|
319
324
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
325
|
+
if (
|
|
326
|
+
err?.code &&
|
|
327
|
+
[
|
|
328
|
+
'ECONNRESET',
|
|
329
|
+
'ECONNREFUSED',
|
|
330
|
+
'ENOTFOUND',
|
|
331
|
+
'ENETDOWN',
|
|
332
|
+
'ENETUNREACH',
|
|
333
|
+
'EHOSTDOWN',
|
|
334
|
+
'EHOSTUNREACH',
|
|
335
|
+
'EPIPE',
|
|
336
|
+
'ENODATA',
|
|
337
|
+
'UND_ERR_CONNECT_TIMEOUT',
|
|
338
|
+
].includes(err.code)
|
|
339
|
+
) {
|
|
340
|
+
return tp.setTimeout(Math.min(10e3, retryCount * 1e3), true, { signal: opts.signal })
|
|
327
341
|
}
|
|
328
|
-
}
|
|
329
342
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
'ECONNRESET',
|
|
334
|
-
'ECONNREFUSED',
|
|
335
|
-
'ENOTFOUND',
|
|
336
|
-
'ENETDOWN',
|
|
337
|
-
'ENETUNREACH',
|
|
338
|
-
'EHOSTDOWN',
|
|
339
|
-
'EHOSTUNREACH',
|
|
340
|
-
'EPIPE',
|
|
341
|
-
'ENODATA',
|
|
342
|
-
'UND_ERR_CONNECT_TIMEOUT',
|
|
343
|
-
].includes(err.code)
|
|
344
|
-
) {
|
|
345
|
-
return tp.setTimeout(Math.min(10e3, retryCount * 1e3), undefined, { signal: opts.signal })
|
|
346
|
-
}
|
|
343
|
+
if (err?.message && ['other side closed'].includes(err.message)) {
|
|
344
|
+
return tp.setTimeout(Math.min(10e3, retryCount * 1e3), true, { signal: opts.signal })
|
|
345
|
+
}
|
|
347
346
|
|
|
348
|
-
|
|
349
|
-
return tp.setTimeout(Math.min(10e3, retryCount * 1e3), undefined, { signal: opts.signal })
|
|
347
|
+
return false
|
|
350
348
|
}
|
|
351
|
-
|
|
352
|
-
throw err
|
|
353
349
|
}
|
|
350
|
+
|
|
351
|
+
export default () => (dispatch) => (opts, handler) =>
|
|
352
|
+
opts.retry !== false &&
|
|
353
|
+
!opts.upgrade &&
|
|
354
|
+
(/^(HEAD|GET|PUT|PATCH)$/.test(opts.method) || opts.idempotent)
|
|
355
|
+
? dispatch(opts, new Handler(opts, { handler, dispatch }))
|
|
356
|
+
: dispatch(opts, handler)
|
package/lib/utils.js
CHANGED
|
@@ -2,6 +2,7 @@ import cacheControlParser from 'cache-control-parser'
|
|
|
2
2
|
import stream from 'node:stream'
|
|
3
3
|
import assert from 'node:assert'
|
|
4
4
|
import { util } from '@nxtedition/undici'
|
|
5
|
+
import createHttpError from 'http-errors'
|
|
5
6
|
|
|
6
7
|
let fastNow = Date.now()
|
|
7
8
|
|
|
@@ -351,9 +352,75 @@ export function parseHeaders(headers, obj) {
|
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
// See https://github.com/nodejs/node/pull/46528
|
|
354
|
-
if ('content-length' in obj && 'content-disposition' in obj) {
|
|
355
|
+
if (obj != null && 'content-length' in obj && 'content-disposition' in obj) {
|
|
355
356
|
obj['content-disposition'] = Buffer.from(obj['content-disposition']).toString('latin1')
|
|
356
357
|
}
|
|
357
358
|
|
|
358
359
|
return obj
|
|
359
360
|
}
|
|
361
|
+
|
|
362
|
+
export function decorateError(err, opts, { statusCode, headers, trailers, body }) {
|
|
363
|
+
try {
|
|
364
|
+
if (err == null) {
|
|
365
|
+
const stackTraceLimit = Error.stackTraceLimit
|
|
366
|
+
Error.stackTraceLimit = 0
|
|
367
|
+
try {
|
|
368
|
+
err = createHttpError(statusCode)
|
|
369
|
+
} finally {
|
|
370
|
+
Error.stackTraceLimit = stackTraceLimit
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (statusCode != null) {
|
|
375
|
+
err.statusCode = statusCode
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
err.url ??= opts.origin ? new URL(opts.path, opts.origin).href : null
|
|
379
|
+
|
|
380
|
+
err.req = {
|
|
381
|
+
method: opts?.method,
|
|
382
|
+
headers: opts?.headers,
|
|
383
|
+
body:
|
|
384
|
+
// TODO (fix): JSON.stringify POJO
|
|
385
|
+
typeof opts?.body !== 'string' || opts.body.length > 1024 ? undefined : opts.body,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (Array.isArray(body) && body.every((x) => Buffer.isBuffer(x))) {
|
|
389
|
+
body = Buffer.concat(body).toString()
|
|
390
|
+
} else if (typeof body !== 'string') {
|
|
391
|
+
body = null
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (typeof body === 'string' && headers?.['content-type']?.startsWith('application/json')) {
|
|
395
|
+
try {
|
|
396
|
+
body = JSON.parse(body)
|
|
397
|
+
} catch {
|
|
398
|
+
// Do nothing...
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
err.res = {
|
|
403
|
+
headers,
|
|
404
|
+
trailers,
|
|
405
|
+
// TODO (fix): JSON.stringify POJO
|
|
406
|
+
body: typeof body !== 'string' || body.length < 1024 ? undefined : body,
|
|
407
|
+
statusCode,
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (body) {
|
|
411
|
+
if (body.reason != null) {
|
|
412
|
+
err.reason ??= body.reason
|
|
413
|
+
}
|
|
414
|
+
if (body.code != null) {
|
|
415
|
+
err.code ??= body.code
|
|
416
|
+
}
|
|
417
|
+
if (body.error != null) {
|
|
418
|
+
err.error ??= body.error
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return err
|
|
423
|
+
} catch (er) {
|
|
424
|
+
return new AggregateError([er, err])
|
|
425
|
+
}
|
|
426
|
+
}
|