Skip to content

Commit

Permalink
feat: http caching
Browse files Browse the repository at this point in the history
Implements bare-bones http caching as per rfc9111

Closes #3231
Closes #2760
Closes #2256
Closes #1146

Signed-off-by: flakey5 <[email protected]>

Co-authored-by: Carlos Fuentes <[email protected]>
Co-authored-by: Robert Nagy <[email protected]>

Co-authored-by: Isak Törnros <[email protected]>
  • Loading branch information
flakey5 and IsakT committed Sep 12, 2024
1 parent b66fb4b commit 938fa7a
Show file tree
Hide file tree
Showing 10 changed files with 1,074 additions and 1 deletion.
7 changes: 6 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ module.exports.RedirectHandler = RedirectHandler
module.exports.interceptors = {
redirect: require('./lib/interceptor/redirect'),
retry: require('./lib/interceptor/retry'),
dump: require('./lib/interceptor/dump')
dump: require('./lib/interceptor/dump'),
cache: require('./lib/interceptor/cache')
}

module.exports.cacheStores = {
LruCacheStore: require('./lib/cache/lru-cache-store')
}

module.exports.buildConnector = buildConnector
Expand Down
84 changes: 84 additions & 0 deletions lib/cache/lru-cache-store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict'

const { canServeStale } = require('../util/cache.js')

/**
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheStore} CacheStore
* @implements {CacheStore}
*/
class LruCacheStore {
/**
* @type {Map<string, import('../../types/cache-interceptor.d.ts').default.CacheStoreValue[]>}
*/
#data = new Map()

/**
* @param {import('../../types/dispatcher.d.ts').default.RequestOptions} req
* @returns {Promise<import('../../types/cache-interceptor.d.ts').default.CacheStoreValue | undefined>}
*/
get (req) {
const key = this.#makeKey(req)

const values = this.#data.get(key)
if (!values) {
return
}

let needsFlattening = false
const now = Date.now()
let value
for (let i = 0; i < values.length; i++) {
const current = values[i]
if (now >= current.staleAt && !canServeStale(current)) {
delete values[i]
needsFlattening = true
continue
}

let matches = true
for (const key in current.vary) {
if (current.vary[key] !== req.headers[key]) {
matches = false
break
}
}

if (matches) {
value = current
break
}
}

if (needsFlattening) {
this.#data.set(key, values.filter(() => true))
}

return value
}

/**
* @param {import('../../types/dispatcher.d.ts').default.RequestOptions} req
* @param {import('../../types/cache-interceptor.d.ts').default.CacheStoreValue} value
*/
put (req, value) {
const key = this.#makeKey(req)

let arr = this.#data.get(key)
if (!arr) {
arr = []
this.#data.set(key, arr)
}
arr.push(value)
}

/**
* @param {import('../../types/dispatcher.d.ts').default.RequestOptions} req
* @returns {string}
*/
#makeKey (req) {
// https://www.rfc-editor.org/rfc/rfc9111.html#section-2-3
return `${req.origin}:${req.path}:${req.method}`
}
}

module.exports = LruCacheStore
Loading

0 comments on commit 938fa7a

Please sign in to comment.