diff --git a/src/apicache.js b/src/apicache.js index bf8658e..221c713 100644 --- a/src/apicache.js +++ b/src/apicache.js @@ -87,6 +87,7 @@ function ApiCache() { } function cacheResponse(key, value, duration) { + console.log('caching response', key, value) var redis = globalOptions.redisClient if (redis) { redis.hset(key, "response", JSON.stringify(value)) @@ -102,28 +103,40 @@ function ApiCache() { function accumulateContent(res, content) { if (content) { - if (typeof(content) == "string") { - res._apicache.content=(res._apicache.content || "") + content; - } - else { - res._apicache.cacheable=false; + if (typeof(content) == 'string') { + res._apicache.content = (res._apicache.content || '') + content; + } else { + res._apicache.content = content + // res._apicache.cacheable = false; } } } function makeResponseCacheable(req, res, next, key, duration, strDuration) { // monkeypatch res.end to create cache object - res._apicache={write:res.write,end:res.end,cacheable:true} + res._apicache = { + write: res.write, + end: res.end, + cacheable: true, + content: undefined + } - res.header('cache-control','max-age=' + (duration / 1000).toFixed(0)) + // add cache control headers + res.header('cache-control', 'max-age=' + (duration / 1000).toFixed(0)) + + // patch res.write res.write = function(content) { - accumulateContent(res,content); - return res._apicache.write.apply(this,arguments); + // console.log('patched write', content) + accumulateContent(res, content); + return res._apicache.write.apply(this, arguments); } - res.end = function(content,encoding) { + + // patch res.end + res.end = function(content, encoding) { + // console.log('patched end', content, encoding) if (shouldCacheResponse(res)) { - accumulateContent(res,content); + accumulateContent(res, content); if (res._apicache.cacheable && res._apicache.content) { addIndexEntries(key, req) @@ -136,7 +149,7 @@ function ApiCache() { } } - return res._apicache.end.apply(this,arguments); + return res._apicache.end.apply(this, arguments); } next() @@ -149,6 +162,8 @@ function ApiCache() { 'apicache-version': pkg.version }) + console.log('building response from', cacheObject) + // unstringify buffers var data = cacheObject.data if (data && data.type === 'Buffer') { @@ -286,12 +301,15 @@ function ApiCache() { // if not forced bypass of cache from client request, attempt cache hit if (!req.headers['x-apicache-force-fetch']) { + console.log('attempting key hits for', key) + // console.log('current cache:', memCache.cache) // attempt cache hit var redis = globalOptions.redisClient var cached = !redis ? memCache.getValue(key) : null // send if cache hit from memory-cache if (cached) { + console.log('cache hit in memory for', key) // console log var elapsed = new Date() - req.apicacheTimer debug('sending cached (memory-cache) version of', key, logDuration(elapsed)) @@ -313,7 +331,7 @@ function ApiCache() { } }) } else { - makeResponseCacheable(req, res, next, key, duration, strDuration) + return makeResponseCacheable(req, res, next, key, duration, strDuration) } } } diff --git a/test/apicache_test.js b/test/apicache_test.js index 44daee3..658b4de 100644 --- a/test/apicache_test.js +++ b/test/apicache_test.js @@ -114,72 +114,6 @@ describe('.resetIndex() {SETTER}', function() { }) -describe('.clear(key?) {SETTER}', function() { - var apicache = require('../src/apicache.js') - - it('is a function', function() { - expect(typeof apicache.clear).to.equal('function') - }) - - it('works when called with group key', function(done) { - var mockAPI = require('./mock_api')('10 seconds') - - request(mockAPI) - .get('/api/testcachegroup') - .end(function(err, res) { - expect(mockAPI.requestsProcessed).to.equal(1) - expect(mockAPI.apicache.getIndex().all.length).to.equal(1) - expect(mockAPI.apicache.getIndex().groups.cachegroup.length).to.equal(1) - expect(Object.keys(mockAPI.apicache.clear('cachegroup').groups).length).to.equal(0) - expect(mockAPI.apicache.getIndex().all.length).to.equal(0) - done() - }) - }) - - it('works when called with specific endpoint (non-group) key', function(done) { - var mockAPI = require('./mock_api')('10 seconds') - - request(mockAPI) - .get('/api/movies') - .end(function(err, res) { - expect(mockAPI.requestsProcessed).to.equal(1) - expect(mockAPI.apicache.getIndex().all.length).to.equal(1) - expect(mockAPI.apicache.clear('/api/movies').all.length).to.equal(0) - done() - }) - }) - - it('clears empty group after removing last specific endpoint', function(done) { - var mockAPI = require('./mock_api')('10 seconds') - - request(mockAPI) - .get('/api/testcachegroup') - .end(function(err, res) { - expect(mockAPI.requestsProcessed).to.equal(1) - expect(mockAPI.apicache.getIndex().all.length).to.equal(1) - expect(mockAPI.apicache.getIndex().groups.cachegroup.length).to.equal(1) - expect(Object.keys(mockAPI.apicache.clear('/api/testcachegroup').groups).length).to.equal(0) - expect(mockAPI.apicache.getIndex().all.length).to.equal(0) - done() - }) - }) - - it('works when called with no key', function(done) { - var mockAPI = require('./mock_api')('10 seconds') - - expect(mockAPI.apicache.getIndex().all.length).to.equal(0) - expect(mockAPI.apicache.clear().all.length).to.equal(0) - request(mockAPI) - .get('/api/movies') - .end(function(err, res) { - expect(mockAPI.requestsProcessed).to.equal(1) - expect(mockAPI.apicache.getIndex().all.length).to.equal(1) - expect(mockAPI.apicache.clear().all.length).to.equal(0) - done() - }) - }) -}) - describe('.middleware {MIDDLEWARE}', function() { var apicache = require('../src/apicache.js') @@ -236,6 +170,28 @@ describe('.middleware {MIDDLEWARE}', function() { }) }) + it('returns cached response from write+end', function(done) { + var mockAPI = require('./mock_api')('10 seconds') + + request(mockAPI) + .get('/api/writeandend') + .end(function(err, res1, body) { + expect(res1.status).to.equal(200) + expect(res1.text).to.equal('abc') + expect(mockAPI.requestsProcessed).to.equal(1) + + request(mockAPI) + .get('/api/writeandend') + .end(function(err, res2) { + expect(res2.status).to.equal(200) + expect(res2.text).to.equal('abc') + + expect(mockAPI.requestsProcessed).to.equal(1) + done() + }) + }) + }) + it('embeds store type and apicache version in cached responses', function(done) { var mockAPI = require('./mock_api')('10 seconds') @@ -453,3 +409,69 @@ describe('Redis support', function() { }) }) }) + +describe('.clear(key?) {SETTER}', function() { + var apicache = require('../src/apicache.js') + + it('is a function', function() { + expect(typeof apicache.clear).to.equal('function') + }) + + it('works when called with group key', function(done) { + var mockAPI = require('./mock_api')('10 seconds') + + request(mockAPI) + .get('/api/testcachegroup') + .end(function(err, res) { + expect(mockAPI.requestsProcessed).to.equal(1) + expect(mockAPI.apicache.getIndex().all.length).to.equal(1) + expect(mockAPI.apicache.getIndex().groups.cachegroup.length).to.equal(1) + expect(Object.keys(mockAPI.apicache.clear('cachegroup').groups).length).to.equal(0) + expect(mockAPI.apicache.getIndex().all.length).to.equal(0) + done() + }) + }) + + it('works when called with specific endpoint (non-group) key', function(done) { + var mockAPI = require('./mock_api')('10 seconds') + + request(mockAPI) + .get('/api/movies') + .end(function(err, res) { + expect(mockAPI.requestsProcessed).to.equal(1) + expect(mockAPI.apicache.getIndex().all.length).to.equal(1) + expect(mockAPI.apicache.clear('/api/movies').all.length).to.equal(0) + done() + }) + }) + + it('clears empty group after removing last specific endpoint', function(done) { + var mockAPI = require('./mock_api')('10 seconds') + + request(mockAPI) + .get('/api/testcachegroup') + .end(function(err, res) { + expect(mockAPI.requestsProcessed).to.equal(1) + expect(mockAPI.apicache.getIndex().all.length).to.equal(1) + expect(mockAPI.apicache.getIndex().groups.cachegroup.length).to.equal(1) + expect(Object.keys(mockAPI.apicache.clear('/api/testcachegroup').groups).length).to.equal(0) + expect(mockAPI.apicache.getIndex().all.length).to.equal(0) + done() + }) + }) + + it('works when called with no key', function(done) { + var mockAPI = require('./mock_api')('10 seconds') + + expect(mockAPI.apicache.getIndex().all.length).to.equal(0) + expect(mockAPI.apicache.clear().all.length).to.equal(0) + request(mockAPI) + .get('/api/movies') + .end(function(err, res) { + expect(mockAPI.requestsProcessed).to.equal(1) + expect(mockAPI.apicache.getIndex().all.length).to.equal(1) + expect(mockAPI.apicache.clear().all.length).to.equal(0) + done() + }) + }) +}) diff --git a/test/mock_api.js b/test/mock_api.js index 3badb99..fb8231a 100644 --- a/test/mock_api.js +++ b/test/mock_api.js @@ -41,6 +41,16 @@ function MockAPI(expiration, options) { res.json(movies) }) + app.get('/api/writeandend', function(req, res) { + app.requestsProcessed++ + + res.write('a') + res.write('b') + res.write('c') + + res.end() + }) + app.get('/api/testcachegroup', function(req, res) { app.requestsProcessed++ req.apicacheGroup = 'cachegroup'