diff --git a/lib/handler/cache-handler.js b/lib/handler/cache-handler.js index e02ff9c9d72..44b82c4a9bb 100644 --- a/lib/handler/cache-handler.js +++ b/lib/handler/cache-handler.js @@ -4,7 +4,8 @@ const util = require('../core/util') const { parseCacheControlHeader, parseVaryHeader, - isEtagUsable + isEtagUsable, + makeHeaderNamesLowercase } = require('../util/cache') const { parseHttpDate } = require('../util/date.js') @@ -96,7 +97,7 @@ class CacheHandler { statusMessage ) - if ( + if ( !util.safeHTTPMethods.includes(this.#cacheKey.method) && statusCode >= 200 && statusCode <= 399 @@ -115,7 +116,7 @@ class CacheHandler { const heuristicallyCacheable = resHeaders['last-modified'] && HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode) if ( !cacheControlHeader && - !resHeaders['expires'] && + !resHeaders.expires && !heuristicallyCacheable && !this.#cacheByDefault ) { @@ -157,6 +158,7 @@ class CacheHandler { let varyDirectives if (this.#cacheKey.headers && resHeaders.vary) { varyDirectives = parseVaryHeader(resHeaders.vary, this.#cacheKey.headers) + if (!varyDirectives) { // Parse error return downstreamOnHeaders() diff --git a/lib/interceptor/cache.js b/lib/interceptor/cache.js index 6d1225680e7..84ae3646cbc 100644 --- a/lib/interceptor/cache.js +++ b/lib/interceptor/cache.js @@ -318,7 +318,7 @@ module.exports = (opts = {}) => { // Not a method we want to cache or we don't have the origin, skip return dispatch(opts, handler) } - +console.log('reqHeaders=', opts.headers) const reqCacheControl = opts.headers?.['cache-control'] ? parseCacheControlHeader(opts.headers['cache-control']) : undefined diff --git a/lib/util/cache.js b/lib/util/cache.js index 35c53512b2a..175f9cfe49a 100644 --- a/lib/util/cache.js +++ b/lib/util/cache.js @@ -38,7 +38,7 @@ function makeCacheKey (opts) { origin: opts.origin.toString(), method: opts.method, path: opts.path, - headers + headers: makeHeaderNamesLowercase(headers) } } @@ -347,6 +347,20 @@ function assertCacheMethods (methods, name = 'CacheMethods') { } } +/** + * @param {import('../../types/header.d.ts').IncomingHttpHeaders} headers + * @returns {import('../../types/header.d.ts').IncomingHttpHeaders} + */ +function makeHeaderNamesLowercase (headers) { + const lowercased = {} + + for (const header of Object.keys(headers)) { + lowercased[header.toLowerCase()] = headers[header] + } + + return lowercased +} + module.exports = { makeCacheKey, assertCacheKey, @@ -355,5 +369,6 @@ module.exports = { parseVaryHeader, isEtagUsable, assertCacheMethods, - assertCacheStore + assertCacheStore, + makeHeaderNamesLowercase } diff --git a/test/interceptors/cache.js b/test/interceptors/cache.js index dc120c60b7b..9a77fa1600f 100644 --- a/test/interceptors/cache.js +++ b/test/interceptors/cache.js @@ -128,6 +128,51 @@ describe('Cache Interceptor', () => { } }) + test('vary directives are case-insensitive', async () => { + let requestsToOrigin = 0 + const server = createServer((_, res) => { + requestsToOrigin++ + + res.setHeader('date', 0) + res.setHeader('cache-control', 'max-age=5000') + res.setHeader('vary', 'FoO, bar, bAZ') + + res.end('asd') + }).listen(0) + + const client = new Client(`http://localhost:${server.address().port}`) + .compose(interceptors.cache()) + + after(async () => { + server.close() + await client.close() + }) + + await once(server, 'listening') + + strictEqual(requestsToOrigin, 0) + + /** + * @type {import('../../types/dispatcher').default.RequestOptions} + */ + const request = { + origin: 'localhost', + method: 'GET', + path: '/', + headers: { + Foo: '1', + BAr: 'abc', + BAZ: '789' + } + } + + await client.request(request) + equal(requestsToOrigin, 1) + + await client.request(request) + equal(requestsToOrigin, 1) + }) + test('stale responses are revalidated before deleteAt (if-modified-since)', async () => { const clock = FakeTimers.install({ shouldClearNativeTimers: true diff --git a/tmp.mjs b/tmp.mjs new file mode 100644 index 00000000000..534c8035a17 --- /dev/null +++ b/tmp.mjs @@ -0,0 +1,5 @@ +import { Client, interceptors } from './index.js' + +const client = new Client('https://google.com').compose(interceptors.cache()) + +await client.request({ path: '/', method: 'GET', origin: 'google.com' })