undici 7.0.0-alpha.6 → 7.0.0-alpha.7
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/CacheStore.md +13 -1
- package/docs/docs/api/Dispatcher.md +6 -8
- package/docs/docs/api/RedirectHandler.md +1 -1
- package/docs/docs/api/RetryHandler.md +2 -2
- package/index.js +9 -0
- package/lib/cache/memory-cache-store.js +5 -10
- package/lib/cache/sqlite-cache-store.js +457 -0
- package/lib/core/util.js +5 -0
- package/lib/dispatcher/client-h1.js +11 -0
- package/lib/dispatcher/client-h2.js +20 -6
- package/lib/dispatcher/dispatcher-base.js +2 -1
- package/lib/dispatcher/dispatcher.js +4 -0
- package/lib/handler/cache-handler.js +49 -154
- package/lib/handler/cache-revalidation-handler.js +33 -71
- package/lib/handler/redirect-handler.js +32 -25
- package/lib/handler/retry-handler.js +9 -15
- package/lib/handler/unwrap-handler.js +96 -0
- package/lib/handler/wrap-handler.js +98 -0
- package/lib/interceptor/cache.js +41 -32
- package/lib/util/cache.js +75 -10
- package/package.json +1 -1
- package/types/agent.d.ts +1 -1
- package/types/cache-interceptor.d.ts +44 -6
- package/types/dispatcher.d.ts +28 -3
- package/types/env-http-proxy-agent.d.ts +1 -1
- package/types/handlers.d.ts +4 -4
- package/types/mock-agent.d.ts +1 -1
- package/types/mock-client.d.ts +1 -1
- package/types/mock-pool.d.ts +1 -1
- package/types/proxy-agent.d.ts +1 -1
- package/types/retry-handler.d.ts +3 -3
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const EventEmitter = require('node:events')
|
|
3
|
+
const WrapHandler = require('../handler/wrap-handler')
|
|
4
|
+
|
|
5
|
+
const wrapInterceptor = (dispatch) => (opts, handler) => dispatch(opts, WrapHandler.wrap(handler))
|
|
3
6
|
|
|
4
7
|
class Dispatcher extends EventEmitter {
|
|
5
8
|
dispatch () {
|
|
@@ -29,6 +32,7 @@ class Dispatcher extends EventEmitter {
|
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
dispatch = interceptor(dispatch)
|
|
35
|
+
dispatch = wrapInterceptor(dispatch)
|
|
32
36
|
|
|
33
37
|
if (dispatch == null || typeof dispatch !== 'function' || dispatch.length !== 2) {
|
|
34
38
|
throw new TypeError('invalid interceptor')
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const util = require('../core/util')
|
|
4
|
-
const DecoratorHandler = require('../handler/decorator-handler')
|
|
5
4
|
const {
|
|
6
5
|
parseCacheControlHeader,
|
|
7
6
|
parseVaryHeader,
|
|
@@ -11,9 +10,9 @@ const {
|
|
|
11
10
|
function noop () {}
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
|
|
15
14
|
*/
|
|
16
|
-
class CacheHandler
|
|
15
|
+
class CacheHandler {
|
|
17
16
|
/**
|
|
18
17
|
* @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}
|
|
19
18
|
*/
|
|
@@ -25,7 +24,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
25
24
|
#store
|
|
26
25
|
|
|
27
26
|
/**
|
|
28
|
-
* @type {import('../../types/dispatcher.d.ts').default.
|
|
27
|
+
* @type {import('../../types/dispatcher.d.ts').default.DispatchHandler}
|
|
29
28
|
*/
|
|
30
29
|
#handler
|
|
31
30
|
|
|
@@ -35,58 +34,41 @@ class CacheHandler extends DecoratorHandler {
|
|
|
35
34
|
#writeStream
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
|
-
* @param {import('../../types/cache-interceptor.d.ts').default.
|
|
37
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions} opts
|
|
39
38
|
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
|
|
40
|
-
* @param {import('../../types/dispatcher.d.ts').default.
|
|
39
|
+
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
|
|
41
40
|
*/
|
|
42
41
|
constructor (opts, cacheKey, handler) {
|
|
43
42
|
const { store } = opts
|
|
44
43
|
|
|
45
|
-
super(handler)
|
|
46
|
-
|
|
47
44
|
this.#store = store
|
|
48
45
|
this.#cacheKey = cacheKey
|
|
49
46
|
this.#handler = handler
|
|
50
47
|
}
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
onRequestStart (controller, context) {
|
|
50
|
+
this.#writeStream?.destroy()
|
|
51
|
+
this.#writeStream = undefined
|
|
52
|
+
this.#handler.onRequestStart?.(controller, context)
|
|
53
|
+
}
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
55
|
+
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
56
|
+
this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
61
57
|
}
|
|
62
58
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*
|
|
66
|
-
* @param {number} statusCode
|
|
67
|
-
* @param {Buffer[]} rawHeaders
|
|
68
|
-
* @param {() => void} resume
|
|
69
|
-
* @param {string} statusMessage
|
|
70
|
-
* @returns {boolean}
|
|
71
|
-
*/
|
|
72
|
-
onHeaders (
|
|
59
|
+
onResponseStart (
|
|
60
|
+
controller,
|
|
73
61
|
statusCode,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
statusMessage
|
|
62
|
+
statusMessage,
|
|
63
|
+
headers
|
|
77
64
|
) {
|
|
78
|
-
const downstreamOnHeaders = () =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
} else {
|
|
87
|
-
return true
|
|
88
|
-
}
|
|
89
|
-
}
|
|
65
|
+
const downstreamOnHeaders = () =>
|
|
66
|
+
this.#handler.onResponseStart?.(
|
|
67
|
+
controller,
|
|
68
|
+
statusCode,
|
|
69
|
+
statusMessage,
|
|
70
|
+
headers
|
|
71
|
+
)
|
|
90
72
|
|
|
91
73
|
if (
|
|
92
74
|
!util.safeHTTPMethods.includes(this.#cacheKey.method) &&
|
|
@@ -102,21 +84,12 @@ class CacheHandler extends DecoratorHandler {
|
|
|
102
84
|
return downstreamOnHeaders()
|
|
103
85
|
}
|
|
104
86
|
|
|
105
|
-
const parsedRawHeaders = util.parseRawHeaders(rawHeaders)
|
|
106
|
-
const headers = util.parseHeaders(parsedRawHeaders)
|
|
107
|
-
|
|
108
87
|
const cacheControlHeader = headers['cache-control']
|
|
109
|
-
|
|
110
|
-
? this.#store.isFull
|
|
111
|
-
: false
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
!cacheControlHeader ||
|
|
115
|
-
isCacheFull
|
|
116
|
-
) {
|
|
88
|
+
if (!cacheControlHeader) {
|
|
117
89
|
// Don't have the cache control header or the cache is full
|
|
118
90
|
return downstreamOnHeaders()
|
|
119
91
|
}
|
|
92
|
+
|
|
120
93
|
const cacheControlDirectives = parseCacheControlHeader(cacheControlHeader)
|
|
121
94
|
if (!canCacheResponse(statusCode, headers, cacheControlDirectives)) {
|
|
122
95
|
return downstreamOnHeaders()
|
|
@@ -130,11 +103,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
130
103
|
: undefined
|
|
131
104
|
const deleteAt = determineDeleteAt(now, cacheControlDirectives, staleAt)
|
|
132
105
|
|
|
133
|
-
const strippedHeaders = stripNecessaryHeaders(
|
|
134
|
-
rawHeaders,
|
|
135
|
-
parsedRawHeaders,
|
|
136
|
-
cacheControlDirectives
|
|
137
|
-
)
|
|
106
|
+
const strippedHeaders = stripNecessaryHeaders(headers, cacheControlDirectives)
|
|
138
107
|
|
|
139
108
|
/**
|
|
140
109
|
* @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
|
|
@@ -142,7 +111,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
142
111
|
const value = {
|
|
143
112
|
statusCode,
|
|
144
113
|
statusMessage,
|
|
145
|
-
|
|
114
|
+
headers: strippedHeaders,
|
|
146
115
|
vary: varyDirectives,
|
|
147
116
|
cachedAt: now,
|
|
148
117
|
staleAt,
|
|
@@ -158,7 +127,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
158
127
|
if (this.#writeStream) {
|
|
159
128
|
const handler = this
|
|
160
129
|
this.#writeStream
|
|
161
|
-
.on('drain', resume)
|
|
130
|
+
.on('drain', () => controller.resume())
|
|
162
131
|
.on('error', function () {
|
|
163
132
|
// TODO (fix): Make error somehow observable?
|
|
164
133
|
})
|
|
@@ -168,7 +137,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
168
137
|
}
|
|
169
138
|
|
|
170
139
|
// TODO (fix): Should we resume even if was paused downstream?
|
|
171
|
-
resume()
|
|
140
|
+
controller.resume()
|
|
172
141
|
})
|
|
173
142
|
}
|
|
174
143
|
}
|
|
@@ -176,55 +145,23 @@ class CacheHandler extends DecoratorHandler {
|
|
|
176
145
|
return downstreamOnHeaders()
|
|
177
146
|
}
|
|
178
147
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
* @param {Buffer} chunk
|
|
183
|
-
* @returns {boolean}
|
|
184
|
-
*/
|
|
185
|
-
onData (chunk) {
|
|
186
|
-
let paused = false
|
|
187
|
-
|
|
188
|
-
if (this.#writeStream) {
|
|
189
|
-
paused ||= this.#writeStream.write(chunk) === false
|
|
148
|
+
onResponseData (controller, chunk) {
|
|
149
|
+
if (this.#writeStream?.write(chunk) === false) {
|
|
150
|
+
controller.pause()
|
|
190
151
|
}
|
|
191
152
|
|
|
192
|
-
|
|
193
|
-
paused ||= this.#handler.onData(chunk) === false
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return !paused
|
|
153
|
+
this.#handler.onResponseData?.(controller, chunk)
|
|
197
154
|
}
|
|
198
155
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
* @param {string[] | null} rawTrailers
|
|
203
|
-
*/
|
|
204
|
-
onComplete (rawTrailers) {
|
|
205
|
-
if (this.#writeStream) {
|
|
206
|
-
this.#writeStream.end()
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (typeof this.#handler.onComplete === 'function') {
|
|
210
|
-
return this.#handler.onComplete(rawTrailers)
|
|
211
|
-
}
|
|
156
|
+
onResponseEnd (controller, trailers) {
|
|
157
|
+
this.#writeStream?.end()
|
|
158
|
+
this.#handler.onResponseEnd?.(controller, trailers)
|
|
212
159
|
}
|
|
213
160
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
*/
|
|
219
|
-
onError (err) {
|
|
220
|
-
if (this.#writeStream) {
|
|
221
|
-
this.#writeStream.destroy(err)
|
|
222
|
-
this.#writeStream = undefined
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (typeof this.#handler.onError === 'function') {
|
|
226
|
-
this.#handler.onError(err)
|
|
227
|
-
}
|
|
161
|
+
onResponseError (controller, err) {
|
|
162
|
+
this.#writeStream?.destroy(err)
|
|
163
|
+
this.#writeStream = undefined
|
|
164
|
+
this.#handler.onResponseError?.(controller, err)
|
|
228
165
|
}
|
|
229
166
|
}
|
|
230
167
|
|
|
@@ -236,10 +173,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
236
173
|
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
|
|
237
174
|
*/
|
|
238
175
|
function canCacheResponse (statusCode, headers, cacheControlDirectives) {
|
|
239
|
-
if (
|
|
240
|
-
statusCode !== 200 &&
|
|
241
|
-
statusCode !== 307
|
|
242
|
-
) {
|
|
176
|
+
if (statusCode !== 200 && statusCode !== 307) {
|
|
243
177
|
return false
|
|
244
178
|
}
|
|
245
179
|
|
|
@@ -309,7 +243,7 @@ function determineStaleAt (now, headers, cacheControlDirectives) {
|
|
|
309
243
|
if (headers.expire && typeof headers.expire === 'string') {
|
|
310
244
|
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
|
|
311
245
|
const expiresDate = new Date(headers.expire)
|
|
312
|
-
if (expiresDate instanceof Date &&
|
|
246
|
+
if (expiresDate instanceof Date && Number.isFinite(expiresDate.valueOf())) {
|
|
313
247
|
return now + (Date.now() - expiresDate.getTime())
|
|
314
248
|
}
|
|
315
249
|
}
|
|
@@ -332,12 +266,11 @@ function determineDeleteAt (now, cacheControlDirectives, staleAt) {
|
|
|
332
266
|
|
|
333
267
|
/**
|
|
334
268
|
* Strips headers required to be removed in cached responses
|
|
335
|
-
* @param {
|
|
336
|
-
* @param {string[]} parsedRawHeaders
|
|
269
|
+
* @param {Record<string, string | string[]>} headers
|
|
337
270
|
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
|
|
338
|
-
* @returns {
|
|
271
|
+
* @returns {Record<string, string | string []>}
|
|
339
272
|
*/
|
|
340
|
-
function stripNecessaryHeaders (
|
|
273
|
+
function stripNecessaryHeaders (headers, cacheControlDirectives) {
|
|
341
274
|
const headersToRemove = ['connection']
|
|
342
275
|
|
|
343
276
|
if (Array.isArray(cacheControlDirectives['no-cache'])) {
|
|
@@ -349,51 +282,13 @@ function stripNecessaryHeaders (rawHeaders, parsedRawHeaders, cacheControlDirect
|
|
|
349
282
|
}
|
|
350
283
|
|
|
351
284
|
let strippedHeaders
|
|
352
|
-
|
|
353
|
-
let offset = 0
|
|
354
|
-
for (let i = 0; i < parsedRawHeaders.length; i += 2) {
|
|
355
|
-
const headerName = parsedRawHeaders[i]
|
|
356
|
-
|
|
285
|
+
for (const headerName of Object.keys(headers)) {
|
|
357
286
|
if (headersToRemove.includes(headerName)) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
// This is the first header we want to remove, let's create the array
|
|
361
|
-
// Since we're stripping headers, this will over allocate. We'll trim
|
|
362
|
-
// it later.
|
|
363
|
-
strippedHeaders = new Array(parsedRawHeaders.length)
|
|
364
|
-
|
|
365
|
-
// Backfill the previous headers into it
|
|
366
|
-
for (let j = 0; j < i; j += 2) {
|
|
367
|
-
strippedHeaders[j] = parsedRawHeaders[j]
|
|
368
|
-
strippedHeaders[j + 1] = parsedRawHeaders[j + 1]
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// We can't map indices 1:1 from stripped headers to rawHeaders without
|
|
373
|
-
// creating holes (if we skip a header, we now have two holes where at
|
|
374
|
-
// element should be). So, let's keep an offset to keep strippedHeaders
|
|
375
|
-
// flattened. We can also use this at the end for trimming the empty
|
|
376
|
-
// elements off of strippedHeaders.
|
|
377
|
-
offset += 2
|
|
378
|
-
|
|
379
|
-
continue
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// We want to keep this header. Let's add it to strippedHeaders if it exists
|
|
383
|
-
if (strippedHeaders) {
|
|
384
|
-
strippedHeaders[i - offset] = parsedRawHeaders[i]
|
|
385
|
-
strippedHeaders[i + 1 - offset] = parsedRawHeaders[i + 1]
|
|
287
|
+
strippedHeaders ??= { ...headers }
|
|
288
|
+
delete headers[headerName]
|
|
386
289
|
}
|
|
387
290
|
}
|
|
388
|
-
|
|
389
|
-
if (strippedHeaders) {
|
|
390
|
-
// Trim off the empty values at the end
|
|
391
|
-
strippedHeaders.length -= offset
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return strippedHeaders
|
|
395
|
-
? util.encodeRawHeaders(strippedHeaders)
|
|
396
|
-
: rawHeaders
|
|
291
|
+
return strippedHeaders ?? headers
|
|
397
292
|
}
|
|
398
293
|
|
|
399
294
|
module.exports = CacheHandler
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
|
-
const DecoratorHandler = require('../handler/decorator-handler')
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* This takes care of revalidation requests we send to the origin. If we get
|
|
@@ -14,123 +13,86 @@ const DecoratorHandler = require('../handler/decorator-handler')
|
|
|
14
13
|
*
|
|
15
14
|
* @see https://www.rfc-editor.org/rfc/rfc9111.html#name-validation
|
|
16
15
|
*
|
|
17
|
-
* @
|
|
18
|
-
* @implements {DispatchHandlers}
|
|
16
|
+
* @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
|
|
19
17
|
*/
|
|
20
|
-
class CacheRevalidationHandler
|
|
18
|
+
class CacheRevalidationHandler {
|
|
21
19
|
#successful = false
|
|
22
20
|
/**
|
|
23
|
-
* @type {((boolean) => void) | null}
|
|
21
|
+
* @type {((boolean, any) => void) | null}
|
|
24
22
|
*/
|
|
25
23
|
#callback
|
|
26
24
|
/**
|
|
27
|
-
* @type {(import('../../types/dispatcher.d.ts').default.
|
|
25
|
+
* @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)}
|
|
28
26
|
*/
|
|
29
27
|
#handler
|
|
30
28
|
|
|
31
|
-
#
|
|
29
|
+
#context
|
|
32
30
|
|
|
33
31
|
/**
|
|
34
|
-
* @param {(boolean) => void} callback Function to call if the cached value is valid
|
|
35
|
-
* @param {import('../../types/dispatcher.d.ts').default.
|
|
32
|
+
* @param {(boolean, any) => void} callback Function to call if the cached value is valid
|
|
33
|
+
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
|
|
36
34
|
*/
|
|
37
35
|
constructor (callback, handler) {
|
|
38
36
|
if (typeof callback !== 'function') {
|
|
39
37
|
throw new TypeError('callback must be a function')
|
|
40
38
|
}
|
|
41
39
|
|
|
42
|
-
super(handler)
|
|
43
|
-
|
|
44
40
|
this.#callback = callback
|
|
45
41
|
this.#handler = handler
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
|
|
44
|
+
onRequestStart (controller, context) {
|
|
49
45
|
this.#successful = false
|
|
50
|
-
this.#
|
|
46
|
+
this.#context = context
|
|
51
47
|
}
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* @param {string} statusMessage
|
|
60
|
-
* @returns {boolean}
|
|
61
|
-
*/
|
|
62
|
-
onHeaders (
|
|
49
|
+
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
50
|
+
this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
onResponseStart (
|
|
54
|
+
controller,
|
|
63
55
|
statusCode,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
statusMessage
|
|
56
|
+
statusMessage,
|
|
57
|
+
headers
|
|
67
58
|
) {
|
|
68
59
|
assert(this.#callback != null)
|
|
69
60
|
|
|
70
61
|
// https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo
|
|
71
62
|
this.#successful = statusCode === 304
|
|
72
|
-
this.#callback(this.#successful)
|
|
63
|
+
this.#callback(this.#successful, this.#context)
|
|
73
64
|
this.#callback = null
|
|
74
65
|
|
|
75
66
|
if (this.#successful) {
|
|
76
67
|
return true
|
|
77
68
|
}
|
|
78
69
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
rawHeaders,
|
|
87
|
-
resume,
|
|
88
|
-
statusMessage
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return true
|
|
70
|
+
this.#handler.onRequestStart?.(controller, this.#context)
|
|
71
|
+
this.#handler.onResponseStart?.(
|
|
72
|
+
controller,
|
|
73
|
+
statusCode,
|
|
74
|
+
statusMessage,
|
|
75
|
+
headers
|
|
76
|
+
)
|
|
93
77
|
}
|
|
94
78
|
|
|
95
|
-
|
|
96
|
-
* @see {DispatchHandlers.onData}
|
|
97
|
-
*
|
|
98
|
-
* @param {Buffer} chunk
|
|
99
|
-
* @returns {boolean}
|
|
100
|
-
*/
|
|
101
|
-
onData (chunk) {
|
|
79
|
+
onResponseData (controller, chunk) {
|
|
102
80
|
if (this.#successful) {
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (typeof this.#handler.onData === 'function') {
|
|
107
|
-
return this.#handler.onData(chunk)
|
|
81
|
+
return
|
|
108
82
|
}
|
|
109
83
|
|
|
110
|
-
return
|
|
84
|
+
return this.#handler.onResponseData(controller, chunk)
|
|
111
85
|
}
|
|
112
86
|
|
|
113
|
-
|
|
114
|
-
* @see {DispatchHandlers.onComplete}
|
|
115
|
-
*
|
|
116
|
-
* @param {string[] | null} rawTrailers
|
|
117
|
-
*/
|
|
118
|
-
onComplete (rawTrailers) {
|
|
87
|
+
onResponseEnd (controller, trailers) {
|
|
119
88
|
if (this.#successful) {
|
|
120
89
|
return
|
|
121
90
|
}
|
|
122
91
|
|
|
123
|
-
|
|
124
|
-
this.#handler.onComplete(rawTrailers)
|
|
125
|
-
}
|
|
92
|
+
this.#handler.onResponseEnd?.(controller, trailers)
|
|
126
93
|
}
|
|
127
94
|
|
|
128
|
-
|
|
129
|
-
* @see {DispatchHandlers.onError}
|
|
130
|
-
*
|
|
131
|
-
* @param {Error} err
|
|
132
|
-
*/
|
|
133
|
-
onError (err) {
|
|
95
|
+
onResponseError (controller, err) {
|
|
134
96
|
if (this.#successful) {
|
|
135
97
|
return
|
|
136
98
|
}
|
|
@@ -140,8 +102,8 @@ class CacheRevalidationHandler extends DecoratorHandler {
|
|
|
140
102
|
this.#callback = null
|
|
141
103
|
}
|
|
142
104
|
|
|
143
|
-
if (typeof this.#handler.
|
|
144
|
-
this.#handler.
|
|
105
|
+
if (typeof this.#handler.onResponseError === 'function') {
|
|
106
|
+
this.#handler.onResponseError(controller, err)
|
|
145
107
|
} else {
|
|
146
108
|
throw err
|
|
147
109
|
}
|
|
@@ -10,6 +10,8 @@ const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
|
|
|
10
10
|
|
|
11
11
|
const kBody = Symbol('body')
|
|
12
12
|
|
|
13
|
+
const noop = () => {}
|
|
14
|
+
|
|
13
15
|
class BodyAsyncIterable {
|
|
14
16
|
constructor (body) {
|
|
15
17
|
this[kBody] = body
|
|
@@ -38,8 +40,6 @@ class RedirectHandler {
|
|
|
38
40
|
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
util.assertRequestHandler(handler, opts.method, opts.upgrade)
|
|
42
|
-
|
|
43
43
|
this.dispatch = dispatch
|
|
44
44
|
this.location = null
|
|
45
45
|
this.abort = null
|
|
@@ -47,7 +47,6 @@ class RedirectHandler {
|
|
|
47
47
|
this.maxRedirections = maxRedirections
|
|
48
48
|
this.handler = handler
|
|
49
49
|
this.history = []
|
|
50
|
-
this.redirectionLimitReached = false
|
|
51
50
|
|
|
52
51
|
if (util.isStream(this.opts.body)) {
|
|
53
52
|
// TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
|
|
@@ -97,27 +96,42 @@ class RedirectHandler {
|
|
|
97
96
|
this.handler.onError(error)
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
onHeaders (statusCode,
|
|
101
|
-
this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body)
|
|
102
|
-
? null
|
|
103
|
-
: parseLocation(statusCode, headers)
|
|
104
|
-
|
|
99
|
+
onHeaders (statusCode, rawHeaders, resume, statusText) {
|
|
105
100
|
if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) {
|
|
106
|
-
|
|
107
|
-
|
|
101
|
+
throw new Error('max redirects')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// https://tools.ietf.org/html/rfc7231#section-6.4.2
|
|
105
|
+
// https://fetch.spec.whatwg.org/#http-redirect-fetch
|
|
106
|
+
// In case of HTTP 301 or 302 with POST, change the method to GET
|
|
107
|
+
if ((statusCode === 301 || statusCode === 302) && this.opts.method === 'POST') {
|
|
108
|
+
this.opts.method = 'GET'
|
|
109
|
+
if (util.isStream(this.opts.body)) {
|
|
110
|
+
util.destroy(this.opts.body.on('error', noop))
|
|
108
111
|
}
|
|
112
|
+
this.opts.body = null
|
|
113
|
+
}
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
|
116
|
+
// In case of HTTP 303, always replace method to be either HEAD or GET
|
|
117
|
+
if (statusCode === 303 && this.opts.method !== 'HEAD') {
|
|
118
|
+
this.opts.method = 'GET'
|
|
119
|
+
if (util.isStream(this.opts.body)) {
|
|
120
|
+
util.destroy(this.opts.body.on('error', noop))
|
|
121
|
+
}
|
|
122
|
+
this.opts.body = null
|
|
113
123
|
}
|
|
114
124
|
|
|
125
|
+
this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body)
|
|
126
|
+
? null
|
|
127
|
+
: parseLocation(statusCode, rawHeaders)
|
|
128
|
+
|
|
115
129
|
if (this.opts.origin) {
|
|
116
130
|
this.history.push(new URL(this.opts.path, this.opts.origin))
|
|
117
131
|
}
|
|
118
132
|
|
|
119
133
|
if (!this.location) {
|
|
120
|
-
return this.handler.onHeaders(statusCode,
|
|
134
|
+
return this.handler.onHeaders(statusCode, rawHeaders, resume, statusText)
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)))
|
|
@@ -131,13 +145,6 @@ class RedirectHandler {
|
|
|
131
145
|
this.opts.origin = origin
|
|
132
146
|
this.opts.maxRedirections = 0
|
|
133
147
|
this.opts.query = null
|
|
134
|
-
|
|
135
|
-
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
|
136
|
-
// In case of HTTP 303, always replace method to be either HEAD or GET
|
|
137
|
-
if (statusCode === 303 && this.opts.method !== 'HEAD') {
|
|
138
|
-
this.opts.method = 'GET'
|
|
139
|
-
this.opts.body = null
|
|
140
|
-
}
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
onData (chunk) {
|
|
@@ -191,14 +198,14 @@ class RedirectHandler {
|
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
200
|
|
|
194
|
-
function parseLocation (statusCode,
|
|
201
|
+
function parseLocation (statusCode, rawHeaders) {
|
|
195
202
|
if (redirectableStatusCodes.indexOf(statusCode) === -1) {
|
|
196
203
|
return null
|
|
197
204
|
}
|
|
198
205
|
|
|
199
|
-
for (let i = 0; i <
|
|
200
|
-
if (
|
|
201
|
-
return
|
|
206
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
207
|
+
if (rawHeaders[i].length === 8 && util.headerNameToString(rawHeaders[i]) === 'location') {
|
|
208
|
+
return rawHeaders[i + 1]
|
|
202
209
|
}
|
|
203
210
|
}
|
|
204
211
|
}
|
|
@@ -37,6 +37,7 @@ class RetryHandler {
|
|
|
37
37
|
this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
|
|
38
38
|
this.abort = null
|
|
39
39
|
this.aborted = false
|
|
40
|
+
this.connectCalled = false
|
|
40
41
|
this.retryOpts = {
|
|
41
42
|
retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
|
|
42
43
|
retryAfter: retryAfter ?? true,
|
|
@@ -68,16 +69,6 @@ class RetryHandler {
|
|
|
68
69
|
this.end = null
|
|
69
70
|
this.etag = null
|
|
70
71
|
this.resume = null
|
|
71
|
-
|
|
72
|
-
// Handle possible onConnect duplication
|
|
73
|
-
this.handler.onConnect(reason => {
|
|
74
|
-
this.aborted = true
|
|
75
|
-
if (this.abort) {
|
|
76
|
-
this.abort(reason)
|
|
77
|
-
} else {
|
|
78
|
-
this.reason = reason
|
|
79
|
-
}
|
|
80
|
-
})
|
|
81
72
|
}
|
|
82
73
|
|
|
83
74
|
onRequestSent () {
|
|
@@ -92,11 +83,14 @@ class RetryHandler {
|
|
|
92
83
|
}
|
|
93
84
|
}
|
|
94
85
|
|
|
95
|
-
onConnect (abort) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.
|
|
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)
|
|
100
94
|
}
|
|
101
95
|
}
|
|
102
96
|
|