mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Cache HTTP headers and status
This commit is contained in:
parent
288a32cc1e
commit
3ff41f2b43
@ -4,7 +4,7 @@ const Database = require('../Database')
|
||||
|
||||
class ApiCacheManager {
|
||||
|
||||
defaultCacheOptions = { max: 1000, maxSize: 10 * 1000 * 1000, sizeCalculation: item => item.length }
|
||||
defaultCacheOptions = { max: 1000, maxSize: 10 * 1000 * 1000, sizeCalculation: item => (item.body.length + JSON.stringify(item.headers).length) }
|
||||
defaultTtlOptions = { ttl: 30 * 60 * 1000 }
|
||||
|
||||
constructor(cache = new LRUCache(this.defaultCacheOptions), ttlOptions = this.defaultTtlOptions) {
|
||||
@ -30,17 +30,20 @@ class ApiCacheManager {
|
||||
const cached = this.cache.get(stringifiedKey)
|
||||
if (cached) {
|
||||
Logger.debug(`[ApiCacheManager] Cache hit: ${stringifiedKey}`)
|
||||
res.send(cached)
|
||||
res.set(cached.headers)
|
||||
res.status(cached.statusCode)
|
||||
res.send(cached.body)
|
||||
return
|
||||
}
|
||||
res.originalSend = res.send
|
||||
res.send = (body) => {
|
||||
Logger.debug(`[ApiCacheManager] Cache miss: ${stringifiedKey}`)
|
||||
const cached = { body, headers: res.getHeaders(), statusCode: res.statusCode }
|
||||
if (key.url.search(/^\/libraries\/.*?\/personalized/) !== -1) {
|
||||
Logger.debug(`[ApiCacheManager] Caching with ${this.ttlOptions.ttl} ms TTL`)
|
||||
this.cache.set(stringifiedKey, body, this.ttlOptions)
|
||||
this.cache.set(stringifiedKey, cached, this.ttlOptions)
|
||||
} else {
|
||||
this.cache.set(stringifiedKey, body)
|
||||
this.cache.set(stringifiedKey, cached)
|
||||
}
|
||||
res.originalSend(body)
|
||||
}
|
||||
|
@ -13,14 +13,14 @@ describe('ApiCacheManager', () => {
|
||||
beforeEach(() => {
|
||||
cache = { get: sinon.stub(), set: sinon.spy() }
|
||||
req = { user: { username: 'testUser' }, url: '/test-url' }
|
||||
res = { send: sinon.spy() }
|
||||
res = { send: sinon.spy(), getHeaders: sinon.stub(), statusCode: 200, status: sinon.spy(), set: sinon.spy() }
|
||||
next = sinon.spy()
|
||||
})
|
||||
|
||||
describe('middleware', () => {
|
||||
it('should send cached data if available', () => {
|
||||
// Arrange
|
||||
const cachedData = { data: 'cached data' }
|
||||
const cachedData = { body: 'cached data', headers: { 'content-type': 'application/json' }, statusCode: 200 }
|
||||
cache.get.returns(cachedData)
|
||||
const key = JSON.stringify({ user: req.user.username, url: req.url })
|
||||
manager = new ApiCacheManager(cache)
|
||||
@ -31,8 +31,12 @@ describe('ApiCacheManager', () => {
|
||||
// Assert
|
||||
expect(cache.get.calledOnce).to.be.true
|
||||
expect(cache.get.calledWith(key)).to.be.true
|
||||
expect(res.set.calledOnce).to.be.true
|
||||
expect(res.set.calledWith(cachedData.headers)).to.be.true
|
||||
expect(res.status.calledOnce).to.be.true
|
||||
expect(res.status.calledWith(cachedData.statusCode)).to.be.true
|
||||
expect(res.send.calledOnce).to.be.true
|
||||
expect(res.send.calledWith(cachedData)).to.be.true
|
||||
expect(res.send.calledWith(cachedData.body)).to.be.true
|
||||
expect(res.originalSend).to.be.undefined
|
||||
expect(next.called).to.be.false
|
||||
expect(cache.set.called).to.be.false
|
||||
@ -41,13 +45,17 @@ describe('ApiCacheManager', () => {
|
||||
it('should cache and send response if data is not cached', () => {
|
||||
// Arrange
|
||||
cache.get.returns(null)
|
||||
const responseData = { data: 'response data' }
|
||||
const headers = { 'content-type': 'application/json' }
|
||||
res.getHeaders.returns(headers)
|
||||
const body = 'response data'
|
||||
const statusCode = 200
|
||||
const responseData = { body, headers, statusCode }
|
||||
const key = JSON.stringify({ user: req.user.username, url: req.url })
|
||||
manager = new ApiCacheManager(cache)
|
||||
|
||||
// Act
|
||||
manager.middleware(req, res, next)
|
||||
res.send(responseData)
|
||||
res.send(body)
|
||||
|
||||
// Assert
|
||||
expect(cache.get.calledOnce).to.be.true
|
||||
@ -56,13 +64,17 @@ describe('ApiCacheManager', () => {
|
||||
expect(cache.set.calledOnce).to.be.true
|
||||
expect(cache.set.calledWith(key, responseData)).to.be.true
|
||||
expect(res.originalSend.calledOnce).to.be.true
|
||||
expect(res.originalSend.calledWith(responseData)).to.be.true
|
||||
expect(res.originalSend.calledWith(body)).to.be.true
|
||||
})
|
||||
|
||||
it('should cache personalized response with 30 minutes TTL', () => {
|
||||
// Arrange
|
||||
cache.get.returns(null)
|
||||
const responseData = { data: 'personalized data' }
|
||||
const headers = { 'content-type': 'application/json' }
|
||||
res.getHeaders.returns(headers)
|
||||
const body = 'personalized data'
|
||||
const statusCode = 200
|
||||
const responseData = { body, headers, statusCode }
|
||||
req.url = '/libraries/id/personalized'
|
||||
const key = JSON.stringify({ user: req.user.username, url: req.url })
|
||||
const ttlOptions = { ttl: 30 * 60 * 1000 }
|
||||
@ -70,7 +82,7 @@ describe('ApiCacheManager', () => {
|
||||
|
||||
// Act
|
||||
manager.middleware(req, res, next)
|
||||
res.send(responseData)
|
||||
res.send(body)
|
||||
|
||||
// Assert
|
||||
expect(cache.get.calledOnce).to.be.true
|
||||
@ -79,7 +91,7 @@ describe('ApiCacheManager', () => {
|
||||
expect(cache.set.calledOnce).to.be.true
|
||||
expect(cache.set.calledWith(key, responseData, ttlOptions)).to.be.true
|
||||
expect(res.originalSend.calledOnce).to.be.true
|
||||
expect(res.originalSend.calledWith(responseData)).to.be.true
|
||||
expect(res.originalSend.calledWith(body)).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user