From c65844d614f2f05fe66e0b9c26478ba78302ddfb Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Mon, 14 Oct 2024 21:00:54 +0200 Subject: [PATCH] followup http-cache --- lib/handler/cache-handler.js | 48 ++++++++++++++++++++++-------------- lib/util/cache.js | 21 ++++++++++++++++ types/cache-interceptor.d.ts | 4 ++- 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/lib/handler/cache-handler.js b/lib/handler/cache-handler.js index 2eaf0fa2417..52432026832 100644 --- a/lib/handler/cache-handler.js +++ b/lib/handler/cache-handler.js @@ -2,24 +2,32 @@ const util = require('../core/util') const DecoratorHandler = require('../handler/decorator-handler') -const { parseCacheControlHeader, parseVaryHeader, UNSAFE_METHODS, assertCacheStoreType } = require('../util/cache') +const { + assertCacheStoreType, + parseCacheControlHeader, + parseVaryHeader, + UNSAFE_METHODS +} = require('../util/cache') /** * Writes a response to a CacheStore and then passes it on to the next handler */ class CacheHandler extends DecoratorHandler { /** - * @type {import('../../types/cache-interceptor.d.ts').default.CacheOptions | null} - */ - #opts = null + * @type {import('../../types/cache-interceptor.d.ts').default.CacheStore} + */ + #store + /** - * @type {import('../../types/dispatcher.d.ts').default.RequestOptions | null} + * @type {import('../../types/dispatcher.d.ts').default.RequestOptions} */ - #req = null + #requestOptions + /** - * @type {import('../../types/dispatcher.d.ts').default.DispatchHandlers | null} + * @type {import('../../types/dispatcher.d.ts').default.DispatchHandlers} */ - #handler = null + #handler + /** * @type {import('../../types/cache-interceptor.d.ts').default.CacheStoreWriteable | undefined} */ @@ -27,19 +35,21 @@ class CacheHandler extends DecoratorHandler { /** * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions} opts - * @param {import('../../types/dispatcher.d.ts').default.RequestOptions} req + * @param {import('../../types/dispatcher.d.ts').default.RequestOptions} requestOptions * @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler */ - constructor (opts, req, handler) { + constructor (opts, requestOptions, handler) { super(handler) if (typeof opts !== 'object') { throw new TypeError(`expected opts to be an object, got type ${typeof opts}`) } - assertCacheStoreType(opts.store) + const { store } = opts + + assertCacheStoreType(store) - if (typeof req !== 'object') { + if (typeof requestOptions !== 'object') { throw new TypeError(`expected req to be an object, got type ${typeof opts}`) } @@ -47,8 +57,8 @@ class CacheHandler extends DecoratorHandler { throw new TypeError(`expected handler to be an object, got type ${typeof opts}`) } - this.#opts = opts - this.#req = req + this.#store = store + this.#requestOptions = requestOptions this.#handler = handler } @@ -75,14 +85,14 @@ class CacheHandler extends DecoratorHandler { ) if ( - UNSAFE_METHODS.includes(this.#req.method) && + UNSAFE_METHODS.includes(this.#requestOptions.method) && statusCode >= 200 && statusCode <= 399 ) { // https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-respons // Try/catch for if it's synchronous try { - const result = this.#opts.store.deleteByOrigin(this.#req.origin) + const result = this.#store.deleteByOrigin(this.#requestOptions.origin) if ( result && typeof result.catch === 'function' && @@ -103,7 +113,7 @@ class CacheHandler extends DecoratorHandler { const cacheControlHeader = headers['cache-control'] const contentLengthHeader = headers['content-length'] - if (!cacheControlHeader || !contentLengthHeader || this.#opts.store.isFull) { + if (!cacheControlHeader || !contentLengthHeader || this.#store.isFull) { // Don't have the headers we need, can't cache return downstreamOnHeaders() } @@ -122,7 +132,7 @@ class CacheHandler extends DecoratorHandler { const staleAt = determineStaleAt(now, headers, cacheControlDirectives) if (staleAt) { const varyDirectives = headers.vary - ? parseVaryHeader(headers.vary, this.#req.headers) + ? parseVaryHeader(headers.vary, this.#requestOptions.headers) : undefined const deleteAt = determineDeleteAt(now, cacheControlDirectives, staleAt) @@ -132,7 +142,7 @@ class CacheHandler extends DecoratorHandler { cacheControlDirectives ) - this.#writeStream = this.#opts.store.createWriteStream(this.#req, { + this.#writeStream = this.#store.createWriteStream(this.#requestOptions, { statusCode, statusMessage, rawHeaders: strippedHeaders, diff --git a/lib/util/cache.js b/lib/util/cache.js index 17ba5a31dae..66facc67057 100644 --- a/lib/util/cache.js +++ b/lib/util/cache.js @@ -1,5 +1,9 @@ 'use strict' +const SAFE_METHODS = /** @type {const} */ ([ + 'GET', 'HEAD', 'OPTIONS', 'TRACE' +]) + const UNSAFE_METHODS = /** @type {const} */ ([ 'POST', 'PUT', 'PATCH', 'DELETE' ]) @@ -196,10 +200,27 @@ function assertCacheStoreType (store) { throw new TypeError(`CacheStore needs a isFull getter with type boolean, current type: ${typeof store.isFull}`) } } +/** + * @param {unknown} methods + * @returns {asserts methods is import('../../types/cache-interceptor.d.ts').default.CacheMethods[]} + */ +function assertCacheMethods (methods) { + if (!Array.isArray(methods)) { + throw new TypeError(`expected type to be an array, got ${typeof methods}`) + } + + for (const method of methods) { + if (!UNSAFE_METHODS.includes(method)) { + throw new TypeError(`CacheMethods needs to be one of ${UNSAFE_METHODS.join(', ')}, got ${method}`) + } + } +} module.exports = { + SAFE_METHODS, UNSAFE_METHODS, parseCacheControlHeader, parseVaryHeader, + assertCacheMethods, assertCacheStoreType } diff --git a/types/cache-interceptor.d.ts b/types/cache-interceptor.d.ts index 45af1a8a5f5..7fa1aeab0ed 100644 --- a/types/cache-interceptor.d.ts +++ b/types/cache-interceptor.d.ts @@ -4,6 +4,8 @@ import Dispatcher from './dispatcher' export default CacheHandler declare namespace CacheHandler { + export type CacheMethods = 'GET' | 'HEAD' | 'OPTIONS' | 'TRACE' + export interface CacheOptions { store?: CacheStore @@ -14,7 +16,7 @@ declare namespace CacheHandler { * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-respons * @see https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1 */ - methods?: ('GET' | 'HEAD' | 'OPTIONS' | 'TRACE')[] + methods?: CacheMethods[] } /**