undici 7.0.0-alpha.6 → 7.0.0-alpha.8
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 +8 -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 +7 -10
- package/lib/cache/sqlite-cache-store.js +446 -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 +189 -195
- package/lib/handler/cache-revalidation-handler.js +44 -71
- package/lib/handler/decorator-handler.js +3 -0
- package/lib/handler/redirect-handler.js +39 -55
- 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 +254 -193
- package/lib/util/cache.js +117 -41
- package/package.json +4 -3
- package/types/agent.d.ts +1 -1
- package/types/cache-interceptor.d.ts +84 -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,21 +10,33 @@ const {
|
|
|
11
10
|
function noop () {}
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandler} DispatchHandler
|
|
14
|
+
*
|
|
15
|
+
* @implements {DispatchHandler}
|
|
15
16
|
*/
|
|
16
|
-
class CacheHandler
|
|
17
|
+
class CacheHandler {
|
|
17
18
|
/**
|
|
18
19
|
* @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}
|
|
19
20
|
*/
|
|
20
21
|
#cacheKey
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @type {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions['type']}
|
|
25
|
+
*/
|
|
26
|
+
#cacheType
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @type {number | undefined}
|
|
30
|
+
*/
|
|
31
|
+
#cacheByDefault
|
|
32
|
+
|
|
22
33
|
/**
|
|
23
34
|
* @type {import('../../types/cache-interceptor.d.ts').default.CacheStore}
|
|
24
35
|
*/
|
|
25
36
|
#store
|
|
26
37
|
|
|
27
38
|
/**
|
|
28
|
-
* @type {import('../../types/dispatcher.d.ts').default.
|
|
39
|
+
* @type {import('../../types/dispatcher.d.ts').default.DispatchHandler}
|
|
29
40
|
*/
|
|
30
41
|
#handler
|
|
31
42
|
|
|
@@ -35,58 +46,41 @@ class CacheHandler extends DecoratorHandler {
|
|
|
35
46
|
#writeStream
|
|
36
47
|
|
|
37
48
|
/**
|
|
38
|
-
* @param {import('../../types/cache-interceptor.d.ts').default.
|
|
49
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions} opts
|
|
39
50
|
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
|
|
40
|
-
* @param {import('../../types/dispatcher.d.ts').default.
|
|
51
|
+
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
|
|
41
52
|
*/
|
|
42
|
-
constructor (
|
|
43
|
-
const { store } = opts
|
|
44
|
-
|
|
45
|
-
super(handler)
|
|
46
|
-
|
|
53
|
+
constructor ({ store, type, cacheByDefault }, cacheKey, handler) {
|
|
47
54
|
this.#store = store
|
|
55
|
+
this.#cacheType = type
|
|
56
|
+
this.#cacheByDefault = cacheByDefault
|
|
48
57
|
this.#cacheKey = cacheKey
|
|
49
58
|
this.#handler = handler
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
onRequestStart (controller, context) {
|
|
62
|
+
this.#writeStream?.destroy()
|
|
63
|
+
this.#writeStream = undefined
|
|
64
|
+
this.#handler.onRequestStart?.(controller, context)
|
|
65
|
+
}
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
67
|
+
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
68
|
+
this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
61
69
|
}
|
|
62
70
|
|
|
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 (
|
|
71
|
+
onResponseStart (
|
|
72
|
+
controller,
|
|
73
73
|
statusCode,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
statusMessage
|
|
74
|
+
statusMessage,
|
|
75
|
+
headers
|
|
77
76
|
) {
|
|
78
|
-
const downstreamOnHeaders = () =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
} else {
|
|
87
|
-
return true
|
|
88
|
-
}
|
|
89
|
-
}
|
|
77
|
+
const downstreamOnHeaders = () =>
|
|
78
|
+
this.#handler.onResponseStart?.(
|
|
79
|
+
controller,
|
|
80
|
+
statusCode,
|
|
81
|
+
statusMessage,
|
|
82
|
+
headers
|
|
83
|
+
)
|
|
90
84
|
|
|
91
85
|
if (
|
|
92
86
|
!util.safeHTTPMethods.includes(this.#cacheKey.method) &&
|
|
@@ -102,39 +96,49 @@ class CacheHandler extends DecoratorHandler {
|
|
|
102
96
|
return downstreamOnHeaders()
|
|
103
97
|
}
|
|
104
98
|
|
|
105
|
-
const parsedRawHeaders = util.parseRawHeaders(rawHeaders)
|
|
106
|
-
const headers = util.parseHeaders(parsedRawHeaders)
|
|
107
|
-
|
|
108
99
|
const cacheControlHeader = headers['cache-control']
|
|
109
|
-
|
|
110
|
-
? this.#store.isFull
|
|
111
|
-
: false
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
!cacheControlHeader ||
|
|
115
|
-
isCacheFull
|
|
116
|
-
) {
|
|
100
|
+
if (!cacheControlHeader && !headers['expires'] && !this.#cacheByDefault) {
|
|
117
101
|
// Don't have the cache control header or the cache is full
|
|
118
102
|
return downstreamOnHeaders()
|
|
119
103
|
}
|
|
120
|
-
|
|
121
|
-
|
|
104
|
+
|
|
105
|
+
const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {}
|
|
106
|
+
if (!canCacheResponse(this.#cacheType, statusCode, headers, cacheControlDirectives)) {
|
|
122
107
|
return downstreamOnHeaders()
|
|
123
108
|
}
|
|
124
109
|
|
|
110
|
+
const age = getAge(headers)
|
|
111
|
+
|
|
125
112
|
const now = Date.now()
|
|
126
|
-
const staleAt = determineStaleAt(now, headers, cacheControlDirectives)
|
|
113
|
+
const staleAt = determineStaleAt(this.#cacheType, now, headers, cacheControlDirectives) ?? this.#cacheByDefault
|
|
127
114
|
if (staleAt) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
115
|
+
let baseTime = now
|
|
116
|
+
if (headers['date']) {
|
|
117
|
+
const parsedDate = parseInt(headers['date'])
|
|
118
|
+
const date = new Date(isNaN(parsedDate) ? headers['date'] : parsedDate)
|
|
119
|
+
if (date instanceof Date && !isNaN(date)) {
|
|
120
|
+
baseTime = date.getTime()
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const absoluteStaleAt = staleAt + baseTime
|
|
125
|
+
|
|
126
|
+
if (now >= absoluteStaleAt || (age && age >= staleAt)) {
|
|
127
|
+
// Response is already stale
|
|
128
|
+
return downstreamOnHeaders()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let varyDirectives
|
|
132
|
+
if (this.#cacheKey.headers && headers.vary) {
|
|
133
|
+
varyDirectives = parseVaryHeader(headers.vary, this.#cacheKey.headers)
|
|
134
|
+
if (!varyDirectives) {
|
|
135
|
+
// Parse error
|
|
136
|
+
return downstreamOnHeaders()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const deleteAt = determineDeleteAt(cacheControlDirectives, absoluteStaleAt)
|
|
141
|
+
const strippedHeaders = stripNecessaryHeaders(headers, cacheControlDirectives)
|
|
138
142
|
|
|
139
143
|
/**
|
|
140
144
|
* @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
|
|
@@ -142,10 +146,11 @@ class CacheHandler extends DecoratorHandler {
|
|
|
142
146
|
const value = {
|
|
143
147
|
statusCode,
|
|
144
148
|
statusMessage,
|
|
145
|
-
|
|
149
|
+
headers: strippedHeaders,
|
|
146
150
|
vary: varyDirectives,
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
cacheControlDirectives,
|
|
152
|
+
cachedAt: age ? now - (age * 1000) : now,
|
|
153
|
+
staleAt: absoluteStaleAt,
|
|
149
154
|
deleteAt
|
|
150
155
|
}
|
|
151
156
|
|
|
@@ -158,9 +163,10 @@ class CacheHandler extends DecoratorHandler {
|
|
|
158
163
|
if (this.#writeStream) {
|
|
159
164
|
const handler = this
|
|
160
165
|
this.#writeStream
|
|
161
|
-
.on('drain', resume)
|
|
166
|
+
.on('drain', () => controller.resume())
|
|
162
167
|
.on('error', function () {
|
|
163
168
|
// TODO (fix): Make error somehow observable?
|
|
169
|
+
handler.#writeStream = undefined
|
|
164
170
|
})
|
|
165
171
|
.on('close', function () {
|
|
166
172
|
if (handler.#writeStream === this) {
|
|
@@ -168,7 +174,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
// TODO (fix): Should we resume even if was paused downstream?
|
|
171
|
-
resume()
|
|
177
|
+
controller.resume()
|
|
172
178
|
})
|
|
173
179
|
}
|
|
174
180
|
}
|
|
@@ -176,83 +182,52 @@ class CacheHandler extends DecoratorHandler {
|
|
|
176
182
|
return downstreamOnHeaders()
|
|
177
183
|
}
|
|
178
184
|
|
|
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
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (typeof this.#handler.onData === 'function') {
|
|
193
|
-
paused ||= this.#handler.onData(chunk) === false
|
|
185
|
+
onResponseData (controller, chunk) {
|
|
186
|
+
if (this.#writeStream?.write(chunk) === false) {
|
|
187
|
+
controller.pause()
|
|
194
188
|
}
|
|
195
189
|
|
|
196
|
-
|
|
190
|
+
this.#handler.onResponseData?.(controller, chunk)
|
|
197
191
|
}
|
|
198
192
|
|
|
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
|
-
}
|
|
193
|
+
onResponseEnd (controller, trailers) {
|
|
194
|
+
this.#writeStream?.end()
|
|
195
|
+
this.#handler.onResponseEnd?.(controller, trailers)
|
|
212
196
|
}
|
|
213
197
|
|
|
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
|
-
}
|
|
198
|
+
onResponseError (controller, err) {
|
|
199
|
+
this.#writeStream?.destroy(err)
|
|
200
|
+
this.#writeStream = undefined
|
|
201
|
+
this.#handler.onResponseError?.(controller, err)
|
|
228
202
|
}
|
|
229
203
|
}
|
|
230
204
|
|
|
231
205
|
/**
|
|
232
206
|
* @see https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
|
|
233
207
|
*
|
|
208
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
|
|
234
209
|
* @param {number} statusCode
|
|
235
210
|
* @param {Record<string, string | string[]>} headers
|
|
236
|
-
* @param {import('
|
|
211
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
237
212
|
*/
|
|
238
|
-
function canCacheResponse (statusCode, headers, cacheControlDirectives) {
|
|
239
|
-
if (
|
|
240
|
-
statusCode !== 200 &&
|
|
241
|
-
statusCode !== 307
|
|
242
|
-
) {
|
|
213
|
+
function canCacheResponse (cacheType, statusCode, headers, cacheControlDirectives) {
|
|
214
|
+
if (statusCode !== 200 && statusCode !== 307) {
|
|
243
215
|
return false
|
|
244
216
|
}
|
|
245
217
|
|
|
246
218
|
if (
|
|
247
|
-
cacheControlDirectives.private === true ||
|
|
248
219
|
cacheControlDirectives['no-cache'] === true ||
|
|
249
220
|
cacheControlDirectives['no-store']
|
|
250
221
|
) {
|
|
251
222
|
return false
|
|
252
223
|
}
|
|
253
224
|
|
|
225
|
+
if (cacheType === 'shared' && cacheControlDirectives.private === true) {
|
|
226
|
+
return false
|
|
227
|
+
}
|
|
228
|
+
|
|
254
229
|
// https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1-5
|
|
255
|
-
if (headers.vary
|
|
230
|
+
if (headers.vary?.includes('*')) {
|
|
256
231
|
return false
|
|
257
232
|
}
|
|
258
233
|
|
|
@@ -281,64 +256,120 @@ function canCacheResponse (statusCode, headers, cacheControlDirectives) {
|
|
|
281
256
|
}
|
|
282
257
|
|
|
283
258
|
/**
|
|
259
|
+
* @param {Record<string, string | string[]>} headers
|
|
260
|
+
* @returns {number | undefined}
|
|
261
|
+
*/
|
|
262
|
+
function getAge (headers) {
|
|
263
|
+
if (!headers.age) {
|
|
264
|
+
return undefined
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const age = parseInt(Array.isArray(headers.age) ? headers.age[0] : headers.age)
|
|
268
|
+
if (isNaN(age) || age >= 2147483647) {
|
|
269
|
+
return undefined
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return age
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
|
|
284
277
|
* @param {number} now
|
|
285
278
|
* @param {Record<string, string | string[]>} headers
|
|
286
|
-
* @param {import('
|
|
279
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
287
280
|
*
|
|
288
281
|
* @returns {number | undefined} time that the value is stale at or undefined if it shouldn't be cached
|
|
289
282
|
*/
|
|
290
|
-
function determineStaleAt (now, headers, cacheControlDirectives) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (cacheControlDirectives.immutable) {
|
|
300
|
-
// https://www.rfc-editor.org/rfc/rfc8246.html#section-2.2
|
|
301
|
-
return now + 31536000
|
|
283
|
+
function determineStaleAt (cacheType, now, headers, cacheControlDirectives) {
|
|
284
|
+
if (cacheType === 'shared') {
|
|
285
|
+
// Prioritize s-maxage since we're a shared cache
|
|
286
|
+
// s-maxage > max-age > Expire
|
|
287
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10-3
|
|
288
|
+
const sMaxAge = cacheControlDirectives['s-maxage']
|
|
289
|
+
if (sMaxAge) {
|
|
290
|
+
return sMaxAge * 1000
|
|
291
|
+
}
|
|
302
292
|
}
|
|
303
293
|
|
|
304
294
|
const maxAge = cacheControlDirectives['max-age']
|
|
305
295
|
if (maxAge) {
|
|
306
|
-
return
|
|
296
|
+
return maxAge * 1000
|
|
307
297
|
}
|
|
308
298
|
|
|
309
|
-
if (headers.
|
|
299
|
+
if (headers.expires && typeof headers.expires === 'string') {
|
|
310
300
|
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
|
|
311
|
-
const expiresDate = new Date(headers.
|
|
312
|
-
if (expiresDate instanceof Date &&
|
|
313
|
-
|
|
301
|
+
const expiresDate = new Date(headers.expires)
|
|
302
|
+
if (expiresDate instanceof Date && Number.isFinite(expiresDate.valueOf())) {
|
|
303
|
+
if (now >= expiresDate.getTime()) {
|
|
304
|
+
return undefined
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return expiresDate.getTime() - now
|
|
314
308
|
}
|
|
315
309
|
}
|
|
316
310
|
|
|
311
|
+
if (cacheControlDirectives.immutable) {
|
|
312
|
+
// https://www.rfc-editor.org/rfc/rfc8246.html#section-2.2
|
|
313
|
+
return 31536000
|
|
314
|
+
}
|
|
315
|
+
|
|
317
316
|
return undefined
|
|
318
317
|
}
|
|
319
318
|
|
|
320
319
|
/**
|
|
321
|
-
* @param {
|
|
322
|
-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
|
|
320
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
323
321
|
* @param {number} staleAt
|
|
324
322
|
*/
|
|
325
|
-
function determineDeleteAt (
|
|
323
|
+
function determineDeleteAt (cacheControlDirectives, staleAt) {
|
|
324
|
+
let staleWhileRevalidate = -Infinity
|
|
325
|
+
let staleIfError = -Infinity
|
|
326
|
+
let immutable = -Infinity
|
|
327
|
+
|
|
326
328
|
if (cacheControlDirectives['stale-while-revalidate']) {
|
|
327
|
-
|
|
329
|
+
staleWhileRevalidate = staleAt + (cacheControlDirectives['stale-while-revalidate'] * 1000)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (cacheControlDirectives['stale-if-error']) {
|
|
333
|
+
staleIfError = staleAt + (cacheControlDirectives['stale-if-error'] * 1000)
|
|
328
334
|
}
|
|
329
335
|
|
|
330
|
-
|
|
336
|
+
if (staleWhileRevalidate === -Infinity && staleIfError === -Infinity) {
|
|
337
|
+
immutable = 31536000
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return Math.max(staleAt, staleWhileRevalidate, staleIfError, immutable)
|
|
331
341
|
}
|
|
332
342
|
|
|
333
343
|
/**
|
|
334
344
|
* Strips headers required to be removed in cached responses
|
|
335
|
-
* @param {
|
|
336
|
-
* @param {
|
|
337
|
-
* @
|
|
338
|
-
* @returns {Buffer[]}
|
|
345
|
+
* @param {Record<string, string | string[]>} headers
|
|
346
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
347
|
+
* @returns {Record<string, string | string []>}
|
|
339
348
|
*/
|
|
340
|
-
function stripNecessaryHeaders (
|
|
341
|
-
const headersToRemove = [
|
|
349
|
+
function stripNecessaryHeaders (headers, cacheControlDirectives) {
|
|
350
|
+
const headersToRemove = [
|
|
351
|
+
'connection',
|
|
352
|
+
'proxy-authenticate',
|
|
353
|
+
'proxy-authentication-info',
|
|
354
|
+
'proxy-authorization',
|
|
355
|
+
'proxy-connection',
|
|
356
|
+
'te',
|
|
357
|
+
'transfer-encoding',
|
|
358
|
+
'upgrade',
|
|
359
|
+
// We'll add age back when serving it
|
|
360
|
+
'age'
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
if (headers['connection']) {
|
|
364
|
+
if (Array.isArray(headers['connection'])) {
|
|
365
|
+
// connection: a
|
|
366
|
+
// connection: b
|
|
367
|
+
headersToRemove.push(...headers['connection'].map(header => header.trim()))
|
|
368
|
+
} else {
|
|
369
|
+
// connection: a, b
|
|
370
|
+
headersToRemove.push(...headers['connection'].split(',').map(header => header.trim()))
|
|
371
|
+
}
|
|
372
|
+
}
|
|
342
373
|
|
|
343
374
|
if (Array.isArray(cacheControlDirectives['no-cache'])) {
|
|
344
375
|
headersToRemove.push(...cacheControlDirectives['no-cache'])
|
|
@@ -349,51 +380,14 @@ function stripNecessaryHeaders (rawHeaders, parsedRawHeaders, cacheControlDirect
|
|
|
349
380
|
}
|
|
350
381
|
|
|
351
382
|
let strippedHeaders
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (headersToRemove.includes(headerName)) {
|
|
358
|
-
// We have at least one header we want to remove
|
|
359
|
-
if (!strippedHeaders) {
|
|
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]
|
|
383
|
+
for (const headerName of headersToRemove) {
|
|
384
|
+
if (headers[headerName]) {
|
|
385
|
+
strippedHeaders ??= { ...headers }
|
|
386
|
+
delete strippedHeaders[headerName]
|
|
386
387
|
}
|
|
387
388
|
}
|
|
388
389
|
|
|
389
|
-
|
|
390
|
-
// Trim off the empty values at the end
|
|
391
|
-
strippedHeaders.length -= offset
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return strippedHeaders
|
|
395
|
-
? util.encodeRawHeaders(strippedHeaders)
|
|
396
|
-
: rawHeaders
|
|
390
|
+
return strippedHeaders ?? headers
|
|
397
391
|
}
|
|
398
392
|
|
|
399
393
|
module.exports = CacheHandler
|