Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,7 @@ This is your repo ;)
> Note: We aim to be 100% code coverage, please consider it on your pull requests.

## Related projects
- fast-gateway (https://www.npmjs.com/package/fast-gateway)
- fast-gateway (https://www.npmjs.com/package/fast-gateway)

## Sponsors
- Kindly sponsored by [ShareNow](https://www.share-now.com/), a company that promotes innovation!
144 changes: 72 additions & 72 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,96 +13,96 @@ const CACHE_CONTROL = 'cache-control'
const CACHE_IF_NONE_MATCH = 'if-none-match'

const middleware = (opts) => async (req, res, next) => {
opts = Object.assign({
stores: [CacheManager.caching({ store: 'memory', max: 1000, ttl: 30 })]
}, opts)
try {
opts = Object.assign({
stores: [CacheManager.caching({ store: 'memory', max: 1000, ttl: 30 })]
}, opts)

// creating multi-cache instance
const mcache = CacheManager.multiCaching(opts.stores)
// creating multi-cache instance
const mcache = CacheManager.multiCaching(opts.stores)

if (req.cacheDisabled) return next()
if (req.cacheDisabled) return next()

let { url, cacheAppendKey = req => '' } = req
cacheAppendKey = await cacheAppendKey(req)
let { url, cacheAppendKey = req => '' } = req
cacheAppendKey = await cacheAppendKey(req)

const key = req.method + url + cacheAppendKey
// ref cache key on req object
req.cacheKey = key
const key = req.method + url + cacheAppendKey
// ref cache key on req object
req.cacheKey = key

// try to retrieve cached response
const cached = await get(mcache, key)
// try to retrieve cached response
const cached = await get(mcache, key)

if (cached) {
// respond from cache if there is a hit
let { status, headers, data } = JSON.parse(cached)
if (cached) {
// respond from cache if there is a hit
let { status, headers, data } = JSON.parse(cached)

// pre-checking If-None-Match header
if (req.headers[CACHE_IF_NONE_MATCH] && req.headers[CACHE_IF_NONE_MATCH] === headers[CACHE_ETAG]) {
res.setHeader('content-length', '0')
res.statusCode = 304
res.end()
// pre-checking If-None-Match header
if (req.headers[CACHE_IF_NONE_MATCH] && req.headers[CACHE_IF_NONE_MATCH] === headers[CACHE_ETAG]) {
res.setHeader('content-length', '0')
res.statusCode = 304
res.end()

return // exit because client cache state matches
}
return // exit because client cache state matches
}

if (typeof data === 'object' && data.type === 'Buffer') {
data = Buffer.from(data.data)
}
headers[X_CACHE_HIT] = '1'
if (typeof data === 'object' && data.type === 'Buffer') {
data = Buffer.from(data.data)
}
headers[X_CACHE_HIT] = '1'

// set cached response headers
Object.keys(headers).forEach(header => res.setHeader(header, headers[header]))
// set cached response headers
Object.keys(headers).forEach(header => res.setHeader(header, headers[header]))

// send cached payload
req.cacheHit = true
res.statusCode = status
res.end(data)
// send cached payload
req.cacheHit = true
res.statusCode = status
res.end(data)

return
}
return
}

onEnd(res, (payload) => {
if (payload.headers[X_CACHE_EXPIRE]) {
// support service level expiration
const keysPattern = payload.headers[X_CACHE_EXPIRE].replace(/\s/g, '')
const patterns = keysPattern.split(',')
// delete keys on all cache tiers
patterns.forEach(pattern => opts.stores.forEach(store => getKeys(store, pattern).then(keys => mcache.del(keys))))
} else if (payload.headers[X_CACHE_TIMEOUT] || payload.headers[CACHE_CONTROL]) {
// extract cache ttl
let ttl = 0
if (payload.headers[CACHE_CONTROL]) {
ttl = cacheControl(payload.headers[CACHE_CONTROL]).maxAge
}
if (!ttl) {
if (payload.headers[X_CACHE_TIMEOUT]) {
ttl = Math.max(ms(payload.headers[X_CACHE_TIMEOUT]), 1000) / 1000 // min value: 1 second
} else {
return // no TTL found, we don't cache
onEnd(res, (payload) => {
if (payload.headers[X_CACHE_EXPIRE]) {
// support service level expiration
const keysPattern = payload.headers[X_CACHE_EXPIRE].replace(/\s/g, '')
const patterns = keysPattern.split(',')
// delete keys on all cache tiers
patterns.forEach(pattern => opts.stores.forEach(store => getKeys(store, pattern).then(keys => mcache.del(keys))))
} else if (payload.headers[X_CACHE_TIMEOUT] || payload.headers[CACHE_CONTROL]) {
// extract cache ttl
let ttl = 0
if (payload.headers[CACHE_CONTROL]) {
ttl = cacheControl(payload.headers[CACHE_CONTROL]).maxAge
}
if (!ttl) {
if (payload.headers[X_CACHE_TIMEOUT]) {
ttl = Math.max(ms(payload.headers[X_CACHE_TIMEOUT]), 1000) / 1000 // min value: 1 second
} else {
return // no TTL found, we don't cache
}
}
}

// setting cache-control header if absent
if (!payload.headers[CACHE_CONTROL]) {
payload.headers[CACHE_CONTROL] = `private, no-cache, max-age=${ttl}`
}
// setting ETag if absent
if (!payload.headers[CACHE_ETAG]) {
payload.headers[CACHE_ETAG] = Math.random().toString(36).substring(2, 16)
}
// setting cache-control header if absent
if (!payload.headers[CACHE_CONTROL]) {
payload.headers[CACHE_CONTROL] = `private, no-cache, max-age=${ttl}`
}
// setting ETag if absent
if (!payload.headers[CACHE_ETAG]) {
payload.headers[CACHE_ETAG] = Math.random().toString(36).substring(2, 16)
}

// cache response
mcache.set(req.cacheKey, JSON.stringify(payload), { ttl })
}
})
// cache response
mcache.set(req.cacheKey, JSON.stringify(payload), { ttl })
}
})

return next()
return next()
} catch (err) {
return next(err)
}
}

const get = (cache, key) => new Promise((resolve) => {
cache.getAndPassUp(key, (_, res) => {
resolve(res)
})
})
const get = (cache, key) => cache.getAndPassUp(key)

module.exports = iu(middleware)
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "http-cache-middleware",
"version": "1.2.4",
"version": "1.2.5",
"description": "HTTP Cache Middleware",
"main": "index.js",
"scripts": {
Expand Down