Skip to content

Commit 45c0793

Browse files
committed
cache: fix stale-while-revalidate and stale-if-error
Closes #3853 Signed-off-by: flakey5 <[email protected]>
1 parent 43bfeb4 commit 45c0793

File tree

9 files changed

+571
-233
lines changed

9 files changed

+571
-233
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
rawHeaders: entry.rawHeaders,
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: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ class CacheHandler extends DecoratorHandler {
138138
statusMessage,
139139
rawHeaders: strippedHeaders,
140140
vary: varyDirectives,
141+
cacheControlDirectives,
141142
cachedAt: now,
142143
staleAt,
143144
deleteAt
@@ -227,7 +228,7 @@ class CacheHandler extends DecoratorHandler {
227228
*
228229
* @param {number} statusCode
229230
* @param {Record<string, string | string[]>} headers
230-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
231+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
231232
*/
232233
function canCacheResponse (statusCode, headers, cacheControlDirectives) {
233234
if (statusCode !== 200 && statusCode !== 307) {
@@ -274,7 +275,7 @@ function canCacheResponse (statusCode, headers, cacheControlDirectives) {
274275
/**
275276
* @param {number} now
276277
* @param {Record<string, string | string[]>} headers
277-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
278+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
278279
*
279280
* @returns {number | undefined} time that the value is stale at or undefined if it shouldn't be cached
280281
*/
@@ -310,22 +311,29 @@ function determineStaleAt (now, headers, cacheControlDirectives) {
310311

311312
/**
312313
* @param {number} now
313-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
314+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
314315
* @param {number} staleAt
315316
*/
316317
function determineDeleteAt (now, cacheControlDirectives, staleAt) {
318+
let staleWhileRevalidate = -Infinity
319+
let staleIfError = -Infinity
320+
317321
if (cacheControlDirectives['stale-while-revalidate']) {
318-
return now + (cacheControlDirectives['stale-while-revalidate'] * 1000)
322+
staleWhileRevalidate = now + (cacheControlDirectives['stale-while-revalidate'] * 1000)
323+
}
324+
325+
if (cacheControlDirectives['stale-if-error']) {
326+
staleIfError = now + (cacheControlDirectives['stale-if-error'] * 1000)
319327
}
320328

321-
return staleAt
329+
return Math.max(staleAt, staleWhileRevalidate, staleIfError)
322330
}
323331

324332
/**
325333
* Strips headers required to be removed in cached responses
326334
* @param {Buffer[]} rawHeaders
327335
* @param {string[]} parsedRawHeaders
328-
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
336+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
329337
* @returns {Buffer[]}
330338
*/
331339
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)