Skip to content

Commit c1bd909

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 a427e4b commit c1bd909

File tree

8 files changed

+568
-230
lines changed

8 files changed

+568
-230
lines changed

lib/cache/memory-cache-store.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class MemoryCacheStore {
9191
rawHeaders: entry.rawHeaders,
9292
body: entry.body,
9393
etag: entry.etag,
94+
cacheControlDirectives: entry.cacheControlDirectives,
9495
cachedAt: entry.cachedAt,
9596
staleAt: entry.staleAt,
9697
deleteAt: entry.deleteAt

lib/handler/cache-handler.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class CacheHandler extends DecoratorHandler {
144144
statusMessage,
145145
rawHeaders: strippedHeaders,
146146
vary: varyDirectives,
147+
cacheControlDirectives,
147148
cachedAt: now,
148149
staleAt,
149150
deleteAt
@@ -233,7 +234,7 @@ class CacheHandler extends DecoratorHandler {
233234
*
234235
* @param {number} statusCode
235236
* @param {Record<string, string | string[]>} headers
236-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
237+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
237238
*/
238239
function canCacheResponse (statusCode, headers, cacheControlDirectives) {
239240
if (
@@ -283,7 +284,7 @@ function canCacheResponse (statusCode, headers, cacheControlDirectives) {
283284
/**
284285
* @param {number} now
285286
* @param {Record<string, string | string[]>} headers
286-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
287+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
287288
*
288289
* @returns {number | undefined} time that the value is stale at or undefined if it shouldn't be cached
289290
*/
@@ -319,22 +320,29 @@ function determineStaleAt (now, headers, cacheControlDirectives) {
319320

320321
/**
321322
* @param {number} now
322-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
323+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
323324
* @param {number} staleAt
324325
*/
325326
function determineDeleteAt (now, cacheControlDirectives, staleAt) {
327+
let staleWhileRevalidate = -Infinity
328+
let staleIfError = -Infinity
329+
326330
if (cacheControlDirectives['stale-while-revalidate']) {
327-
return now + (cacheControlDirectives['stale-while-revalidate'] * 1000)
331+
staleWhileRevalidate = now + (cacheControlDirectives['stale-while-revalidate'] * 1000)
332+
}
333+
334+
if (cacheControlDirectives['stale-if-error']) {
335+
staleIfError = now + (cacheControlDirectives['stale-if-error'] * 1000)
328336
}
329337

330-
return staleAt
338+
return Math.max(staleAt, staleWhileRevalidate, staleIfError)
331339
}
332340

333341
/**
334342
* Strips headers required to be removed in cached responses
335343
* @param {Buffer[]} rawHeaders
336344
* @param {string[]} parsedRawHeaders
337-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
345+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
338346
* @returns {Buffer[]}
339347
*/
340348
function stripNecessaryHeaders (rawHeaders, parsedRawHeaders, cacheControlDirectives) {

lib/handler/cache-revalidation-handler.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,30 @@ const DecoratorHandler = require('../handler/decorator-handler')
1919
*/
2020
class CacheRevalidationHandler extends DecoratorHandler {
2121
#successful = false
22+
2223
/**
2324
* @type {((boolean) => void) | null}
2425
*/
2526
#callback
27+
2628
/**
2729
* @type {(import('../../types/dispatcher.d.ts').default.DispatchHandlers)}
2830
*/
2931
#handler
3032

33+
/**
34+
* @type {boolean}
35+
*/
36+
#allowErrorStatusCodes
37+
3138
#abort
3239

3340
/**
3441
* @param {(boolean) => void} callback Function to call if the cached value is valid
3542
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
43+
* @param {boolean} allowErrorStatusCodes
3644
*/
37-
constructor (callback, handler) {
45+
constructor (callback, handler, allowErrorStatusCodes) {
3846
if (typeof callback !== 'function') {
3947
throw new TypeError('callback must be a function')
4048
}
@@ -43,6 +51,7 @@ class CacheRevalidationHandler extends DecoratorHandler {
4351

4452
this.#callback = callback
4553
this.#handler = handler
54+
this.#allowErrorStatusCodes = allowErrorStatusCodes
4655
}
4756

4857
onConnect (abort) {
@@ -68,7 +77,9 @@ class CacheRevalidationHandler extends DecoratorHandler {
6877
assert(this.#callback != null)
6978

7079
// https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo
71-
this.#successful = statusCode === 304
80+
// https://datatracker.ietf.org/doc/html/rfc5861#section-4
81+
this.#successful = statusCode === 304 ||
82+
(this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
7283
this.#callback(this.#successful)
7384
this.#callback = null
7485

0 commit comments

Comments
 (0)