From 3ff41f2b43a89d4f8365a1002647795c983735f6 Mon Sep 17 00:00:00 2001 From: mikiher Date: Sat, 25 Nov 2023 23:49:56 +0200 Subject: [PATCH] Cache HTTP headers and status --- server/managers/ApiCacheManager.js | 11 ++++--- test/server/managers/ApiCacheManager.test.js | 30 ++++++++++++++------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/server/managers/ApiCacheManager.js b/server/managers/ApiCacheManager.js index b311af53..c6579ab3 100644 --- a/server/managers/ApiCacheManager.js +++ b/server/managers/ApiCacheManager.js @@ -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) } diff --git a/test/server/managers/ApiCacheManager.test.js b/test/server/managers/ApiCacheManager.test.js index 2cfc0dfc..dc1ee1ed 100644 --- a/test/server/managers/ApiCacheManager.test.js +++ b/test/server/managers/ApiCacheManager.test.js @@ -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 }) }) }) \ No newline at end of file