undici 7.0.0-alpha.7 → 7.0.0-alpha.9
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/docs/api/Dispatcher.md +15 -189
- package/index.js +1 -0
- package/lib/cache/memory-cache-store.js +2 -0
- package/lib/cache/sqlite-cache-store.js +32 -43
- package/lib/core/errors.js +2 -2
- package/lib/dispatcher/dispatcher-base.js +4 -2
- package/lib/handler/cache-handler.js +148 -49
- package/lib/handler/cache-revalidation-handler.js +21 -10
- package/lib/handler/decorator-handler.js +3 -0
- package/lib/handler/redirect-handler.js +15 -38
- package/lib/handler/retry-handler.js +65 -100
- package/lib/handler/unwrap-handler.js +2 -2
- package/lib/handler/wrap-handler.js +2 -2
- package/lib/interceptor/cache.js +250 -201
- package/lib/interceptor/response-error.js +9 -5
- package/lib/util/cache.js +42 -31
- package/package.json +4 -3
- package/types/cache-interceptor.d.ts +40 -0
- package/types/dispatcher.d.ts +1 -1
|
@@ -3,9 +3,9 @@ const assert = require('node:assert')
|
|
|
3
3
|
|
|
4
4
|
const { kRetryHandlerDefaultRetry } = require('../core/symbols')
|
|
5
5
|
const { RequestRetryError } = require('../core/errors')
|
|
6
|
+
const WrapHandler = require('./wrap-handler')
|
|
6
7
|
const {
|
|
7
8
|
isDisturbed,
|
|
8
|
-
parseHeaders,
|
|
9
9
|
parseRangeHeader,
|
|
10
10
|
wrapRequestBody
|
|
11
11
|
} = require('../core/util')
|
|
@@ -16,7 +16,7 @@ function calculateRetryAfterHeader (retryAfter) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
class RetryHandler {
|
|
19
|
-
constructor (opts,
|
|
19
|
+
constructor (opts, { dispatch, handler }) {
|
|
20
20
|
const { retryOptions, ...dispatchOpts } = opts
|
|
21
21
|
const {
|
|
22
22
|
// Retry scoped
|
|
@@ -32,12 +32,9 @@ class RetryHandler {
|
|
|
32
32
|
statusCodes
|
|
33
33
|
} = retryOptions ?? {}
|
|
34
34
|
|
|
35
|
-
this.dispatch =
|
|
36
|
-
this.handler =
|
|
35
|
+
this.dispatch = dispatch
|
|
36
|
+
this.handler = WrapHandler.wrap(handler)
|
|
37
37
|
this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
|
|
38
|
-
this.abort = null
|
|
39
|
-
this.aborted = false
|
|
40
|
-
this.connectCalled = false
|
|
41
38
|
this.retryOpts = {
|
|
42
39
|
retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
|
|
43
40
|
retryAfter: retryAfter ?? true,
|
|
@@ -65,37 +62,20 @@ class RetryHandler {
|
|
|
65
62
|
|
|
66
63
|
this.retryCount = 0
|
|
67
64
|
this.retryCountCheckpoint = 0
|
|
65
|
+
this.headersSent = false
|
|
68
66
|
this.start = 0
|
|
69
67
|
this.end = null
|
|
70
68
|
this.etag = null
|
|
71
|
-
this.resume = null
|
|
72
69
|
}
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
if (this.
|
|
76
|
-
this.handler.
|
|
71
|
+
onRequestStart (controller, context) {
|
|
72
|
+
if (!this.headersSent) {
|
|
73
|
+
this.handler.onRequestStart?.(controller, context)
|
|
77
74
|
}
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this.handler.onUpgrade(statusCode, headers, socket)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
onConnect (abort, context) {
|
|
87
|
-
this.abort = abort
|
|
88
|
-
if (!this.connectCalled) {
|
|
89
|
-
this.connectCalled = true
|
|
90
|
-
this.handler.onConnect(reason => {
|
|
91
|
-
this.aborted = true
|
|
92
|
-
this.abort(reason)
|
|
93
|
-
}, context)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
onBodySent (chunk) {
|
|
98
|
-
if (this.handler.onBodySent) return this.handler.onBodySent(chunk)
|
|
77
|
+
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
78
|
+
this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
99
79
|
}
|
|
100
80
|
|
|
101
81
|
static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) {
|
|
@@ -153,83 +133,68 @@ class RetryHandler {
|
|
|
153
133
|
? Math.min(retryAfterHeader, maxTimeout)
|
|
154
134
|
: Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout)
|
|
155
135
|
|
|
156
|
-
setTimeout(() => cb(null), retryTimeout)
|
|
136
|
+
setTimeout(() => cb(null), retryTimeout).unref()
|
|
157
137
|
}
|
|
158
138
|
|
|
159
|
-
|
|
160
|
-
const headers = parseHeaders(rawHeaders)
|
|
161
|
-
|
|
139
|
+
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
162
140
|
this.retryCount += 1
|
|
163
141
|
|
|
164
142
|
if (statusCode >= 300) {
|
|
165
143
|
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
|
166
|
-
|
|
144
|
+
this.headersSent = true
|
|
145
|
+
this.handler.onResponseStart?.(
|
|
146
|
+
controller,
|
|
167
147
|
statusCode,
|
|
168
|
-
|
|
169
|
-
resume,
|
|
148
|
+
headers,
|
|
170
149
|
statusMessage
|
|
171
150
|
)
|
|
151
|
+
return
|
|
172
152
|
} else {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
})
|
|
180
|
-
)
|
|
181
|
-
return false
|
|
153
|
+
throw new RequestRetryError('Request failed', statusCode, {
|
|
154
|
+
headers,
|
|
155
|
+
data: {
|
|
156
|
+
count: this.retryCount
|
|
157
|
+
}
|
|
158
|
+
})
|
|
182
159
|
}
|
|
183
160
|
}
|
|
184
161
|
|
|
185
162
|
// Checkpoint for resume from where we left it
|
|
186
|
-
if (this.
|
|
187
|
-
this.resume = null
|
|
188
|
-
|
|
163
|
+
if (this.headersSent) {
|
|
189
164
|
// Only Partial Content 206 supposed to provide Content-Range,
|
|
190
165
|
// any other status code that partially consumed the payload
|
|
191
166
|
// should not be retried because it would result in downstream
|
|
192
167
|
// wrongly concatenate multiple responses.
|
|
193
168
|
if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
})
|
|
199
|
-
)
|
|
200
|
-
return false
|
|
169
|
+
throw new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
|
|
170
|
+
headers,
|
|
171
|
+
data: { count: this.retryCount }
|
|
172
|
+
})
|
|
201
173
|
}
|
|
202
174
|
|
|
203
175
|
const contentRange = parseRangeHeader(headers['content-range'])
|
|
204
176
|
// If no content range
|
|
205
177
|
if (!contentRange) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
})
|
|
211
|
-
)
|
|
212
|
-
return false
|
|
178
|
+
throw new RequestRetryError('Content-Range mismatch', statusCode, {
|
|
179
|
+
headers,
|
|
180
|
+
data: { count: this.retryCount }
|
|
181
|
+
})
|
|
213
182
|
}
|
|
214
183
|
|
|
215
184
|
// Let's start with a weak etag check
|
|
216
185
|
if (this.etag != null && this.etag !== headers.etag) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
})
|
|
222
|
-
)
|
|
223
|
-
return false
|
|
186
|
+
throw new RequestRetryError('ETag mismatch', statusCode, {
|
|
187
|
+
headers,
|
|
188
|
+
data: { count: this.retryCount }
|
|
189
|
+
})
|
|
224
190
|
}
|
|
225
191
|
|
|
226
|
-
const { start, size, end = size - 1 } = contentRange
|
|
192
|
+
const { start, size, end = size ? size - 1 : null } = contentRange
|
|
227
193
|
|
|
228
194
|
assert(this.start === start, 'content-range mismatch')
|
|
229
195
|
assert(this.end == null || this.end === end, 'content-range mismatch')
|
|
230
196
|
|
|
231
|
-
|
|
232
|
-
return true
|
|
197
|
+
return
|
|
233
198
|
}
|
|
234
199
|
|
|
235
200
|
if (this.end == null) {
|
|
@@ -238,15 +203,17 @@ class RetryHandler {
|
|
|
238
203
|
const range = parseRangeHeader(headers['content-range'])
|
|
239
204
|
|
|
240
205
|
if (range == null) {
|
|
241
|
-
|
|
206
|
+
this.headersSent = true
|
|
207
|
+
this.handler.onResponseStart?.(
|
|
208
|
+
controller,
|
|
242
209
|
statusCode,
|
|
243
|
-
|
|
244
|
-
resume,
|
|
210
|
+
headers,
|
|
245
211
|
statusMessage
|
|
246
212
|
)
|
|
213
|
+
return
|
|
247
214
|
}
|
|
248
215
|
|
|
249
|
-
const { start, size, end = size - 1 } = range
|
|
216
|
+
const { start, size, end = size ? size - 1 : null } = range
|
|
250
217
|
assert(
|
|
251
218
|
start != null && Number.isFinite(start),
|
|
252
219
|
'content-range mismatch'
|
|
@@ -269,7 +236,7 @@ class RetryHandler {
|
|
|
269
236
|
'invalid content-length'
|
|
270
237
|
)
|
|
271
238
|
|
|
272
|
-
this.resume =
|
|
239
|
+
this.resume = true
|
|
273
240
|
this.etag = headers.etag != null ? headers.etag : null
|
|
274
241
|
|
|
275
242
|
// Weak etags are not useful for comparison nor cache
|
|
@@ -283,38 +250,36 @@ class RetryHandler {
|
|
|
283
250
|
this.etag = null
|
|
284
251
|
}
|
|
285
252
|
|
|
286
|
-
|
|
253
|
+
this.headersSent = true
|
|
254
|
+
this.handler.onResponseStart?.(
|
|
255
|
+
controller,
|
|
287
256
|
statusCode,
|
|
288
|
-
|
|
289
|
-
resume,
|
|
257
|
+
headers,
|
|
290
258
|
statusMessage
|
|
291
259
|
)
|
|
260
|
+
} else {
|
|
261
|
+
throw new RequestRetryError('Request failed', statusCode, {
|
|
262
|
+
headers,
|
|
263
|
+
data: { count: this.retryCount }
|
|
264
|
+
})
|
|
292
265
|
}
|
|
293
|
-
|
|
294
|
-
const err = new RequestRetryError('Request failed', statusCode, {
|
|
295
|
-
headers,
|
|
296
|
-
data: { count: this.retryCount }
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
this.abort(err)
|
|
300
|
-
|
|
301
|
-
return false
|
|
302
266
|
}
|
|
303
267
|
|
|
304
|
-
|
|
268
|
+
onResponseData (controller, chunk) {
|
|
305
269
|
this.start += chunk.length
|
|
306
270
|
|
|
307
|
-
|
|
271
|
+
this.handler.onResponseData?.(controller, chunk)
|
|
308
272
|
}
|
|
309
273
|
|
|
310
|
-
|
|
274
|
+
onResponseEnd (controller, trailers) {
|
|
311
275
|
this.retryCount = 0
|
|
312
|
-
return this.handler.
|
|
276
|
+
return this.handler.onResponseEnd?.(controller, trailers)
|
|
313
277
|
}
|
|
314
278
|
|
|
315
|
-
|
|
316
|
-
if (
|
|
317
|
-
|
|
279
|
+
onResponseError (controller, err) {
|
|
280
|
+
if (!controller || controller.aborted || isDisturbed(this.opts.body)) {
|
|
281
|
+
this.handler.onResponseError?.(controller, err)
|
|
282
|
+
return
|
|
318
283
|
}
|
|
319
284
|
|
|
320
285
|
// We reconcile in case of a mix between network errors
|
|
@@ -343,8 +308,8 @@ class RetryHandler {
|
|
|
343
308
|
* @returns
|
|
344
309
|
*/
|
|
345
310
|
function onRetry (err) {
|
|
346
|
-
if (err != null ||
|
|
347
|
-
return this.handler.
|
|
311
|
+
if (err != null || controller?.aborted || isDisturbed(this.opts.body)) {
|
|
312
|
+
return this.handler.onResponseError?.(controller, err)
|
|
348
313
|
}
|
|
349
314
|
|
|
350
315
|
if (this.start !== 0) {
|
|
@@ -368,7 +333,7 @@ class RetryHandler {
|
|
|
368
333
|
this.retryCountCheckpoint = this.retryCount
|
|
369
334
|
this.dispatch(this.opts, this)
|
|
370
335
|
} catch (err) {
|
|
371
|
-
this.handler.
|
|
336
|
+
this.handler.onResponseError?.(controller, err)
|
|
372
337
|
}
|
|
373
338
|
}
|
|
374
339
|
}
|
|
@@ -73,7 +73,7 @@ module.exports = class UnwrapHandler {
|
|
|
73
73
|
|
|
74
74
|
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
75
75
|
this.#controller[kResume] = resume
|
|
76
|
-
this.#handler.onResponseStart?.(this.#controller, statusCode,
|
|
76
|
+
this.#handler.onResponseStart?.(this.#controller, statusCode, parseHeaders(rawHeaders), statusMessage)
|
|
77
77
|
return !this.#controller.paused
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -87,7 +87,7 @@ module.exports = class UnwrapHandler {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
onError (err) {
|
|
90
|
-
if (!this.#handler.
|
|
90
|
+
if (!this.#handler.onResponseError) {
|
|
91
91
|
throw new InvalidArgumentError('invalid onError method')
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -38,7 +38,7 @@ module.exports = class WrapHandler {
|
|
|
38
38
|
|
|
39
39
|
onError (err) {
|
|
40
40
|
if (!this.#handler.onError) {
|
|
41
|
-
throw
|
|
41
|
+
throw err
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
return this.#handler.onError?.(err)
|
|
@@ -60,7 +60,7 @@ module.exports = class WrapHandler {
|
|
|
60
60
|
this.#handler.onUpgrade?.(statusCode, rawHeaders, socket)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
onResponseStart (controller, statusCode,
|
|
63
|
+
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
64
64
|
const rawHeaders = []
|
|
65
65
|
for (const [key, val] of Object.entries(headers)) {
|
|
66
66
|
// TODO (fix): What if val is Array
|