Skip to content

Commit 1f61576

Browse files
committed
cache: fix stale-while-revalidate and stale-if-error
Closes #3853 Signed-off-by: flakey5 <73616808+flakey5@users.noreply.github.com>
1 parent ac30c29 commit 1f61576

File tree

9 files changed

+599
-250
lines changed

9 files changed

+599
-250
lines changed

lib/cache/memory-cache-store.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class MemoryCacheStore {
9090
headers: entry.headers,
9191
body: entry.body,
9292
etag: entry.etag,
93+
cacheControlDirectives: entry.cacheControlDirectives,
9394
cachedAt: entry.cachedAt,
9495
staleAt: entry.staleAt,
9596
deleteAt: entry.deleteAt

lib/handler/cache-handler.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class CacheHandler {
113113
statusMessage,
114114
headers: strippedHeaders,
115115
vary: varyDirectives,
116+
cacheControlDirectives,
116117
cachedAt: now,
117118
staleAt,
118119
deleteAt
@@ -170,7 +171,7 @@ class CacheHandler {
170171
*
171172
* @param {number} statusCode
172173
* @param {Record<string, string | string[]>} headers
173-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
174+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
174175
*/
175176
function canCacheResponse (statusCode, headers, cacheControlDirectives) {
176177
if (statusCode !== 200 && statusCode !== 307) {
@@ -217,7 +218,7 @@ function canCacheResponse (statusCode, headers, cacheControlDirectives) {
217218
/**
218219
* @param {number} now
219220
* @param {Record<string, string | string[]>} headers
220-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
221+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
221222
*
222223
* @returns {number | undefined} time that the value is stale at or undefined if it shouldn't be cached
223224
*/
@@ -253,22 +254,36 @@ function determineStaleAt (now, headers, cacheControlDirectives) {
253254

254255
/**
255256
* @param {number} now
256-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
257+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
257258
* @param {number} staleAt
258259
*/
259260
function determineDeleteAt (now, cacheControlDirectives, staleAt) {
261+
let staleWhileRevalidate = -Infinity
262+
let staleIfError = -Infinity
263+
260264
if (cacheControlDirectives['stale-while-revalidate']) {
261-
return now + (cacheControlDirectives['stale-while-revalidate'] * 1000)
265+
staleWhileRevalidate = now + (cacheControlDirectives['stale-while-revalidate'] * 1000)
266+
}
267+
268+
if (cacheControlDirectives['stale-if-error']) {
269+
staleIfError = now + (cacheControlDirectives['stale-if-error'] * 1000)
262270
}
263271

264-
return staleAt
272+
return Math.max(staleAt, staleWhileRevalidate, staleIfError)
265273
}
266274

267275
/**
268276
* Strips headers required to be removed in cached responses
277+
<<<<<<< HEAD
269278
* @param {Record<string, string | string[]>} headers
270279
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
271280
* @returns {Record<string, string | string []>}
281+
=======
282+
* @param {Buffer[]} rawHeaders
283+
* @param {string[]} parsedRawHeaders
284+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
285+
* @returns {Buffer[]}
286+
>>>>>>> 45c0793e (cache: fix stale-while-revalidate and stale-if-error)
272287
*/
273288
function stripNecessaryHeaders (headers, cacheControlDirectives) {
274289
const headersToRemove = ['connection']

lib/handler/cache-revalidation-handler.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ const assert = require('node:assert')
1717
*/
1818
class CacheRevalidationHandler {
1919
#successful = false
20+
2021
/**
2122
* @type {((boolean, any) => void) | null}
2223
*/
2324
#callback
25+
2426
/**
2527
* @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)}
2628
*/
@@ -29,19 +31,26 @@ class CacheRevalidationHandler {
2931
#context
3032

3133
/**
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
34+
* @type {boolean}
35+
*/
36+
#allowErrorStatusCodes
37+
38+
/**
39+
* @param {(boolean) => void} callback Function to call if the cached value is valid
40+
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
41+
* @param {boolean} allowErrorStatusCodes
3442
*/
35-
constructor (callback, handler) {
43+
constructor (callback, handler, allowErrorStatusCodes) {
3644
if (typeof callback !== 'function') {
3745
throw new TypeError('callback must be a function')
3846
}
3947

4048
this.#callback = callback
4149
this.#handler = handler
50+
this.#allowErrorStatusCodes = allowErrorStatusCodes
4251
}
4352

44-
onRequestStart (controller, context) {
53+
onRequestStart (_, context) {
4554
this.#successful = false
4655
this.#context = context
4756
}
@@ -59,7 +68,9 @@ class CacheRevalidationHandler {
5968
assert(this.#callback != null)
6069

6170
// https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo
62-
this.#successful = statusCode === 304
71+
// https://datatracker.ietf.org/doc/html/rfc5861#section-4
72+
this.#successful = statusCode === 304 ||
73+
(this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
6374
this.#callback(this.#successful, this.#context)
6475
this.#callback = null
6576

0 commit comments

Comments
 (0)