Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local middleware options #68

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,11 @@ app.get('/api/found', cacheSuccesses, (req, res) => {

## API

- `apicache.clear([target])` - clears cache target (key or group), or entire cache if no value passed, returns new index.
- `apicache.options([globalOptions])` - getter/setter for global options. If used as a setter, this function is chainable, allowing you to do things such as... say... return the middleware.
- `apicache.middleware([duration], [toggleMiddleware], [localOptions])` - the actual middleware that will be used in your routes. `duration` is in the following format "[length] [unit]", as in `"10 minutes"` or `"1 day"`. A second param is a middleware toggle function, accepting request and response params, and must return truthy to enable cache for the request. Third param is the options that will override global ones and affect this middleware only.
- `middleware.options([localOptions])` - getter/setter for middleware-specific options that will override global ones.
- `apicache.getIndex()` - returns current cache index [of keys]
- `apicache.middleware([duration], [toggleMiddleware])` - the actual middleware that will be used in your routes. `duration` is in the following format "[length] [unit]", as in `"10 minutes"` or `"1 day"`. A second param is a middleware toggle function, accepting request and response params, and must return truthy to enable cache for the request.
- `apicache.options([options])` - getter/setter for options. If used as a setter, this function is chainable, allowing you to do things such as... say... return the middleware.
- `apicache.clear([target])` - clears cache target (key or group), or entire cache if no value passed, returns new index.
- `apicache.newInstance([options])` - used to create a new ApiCache instance (by default, simply requiring this library shares a common instance)
- `apicache.clone()` - used to create a new ApiCache instance with the same options as the current one

Expand Down
47 changes: 39 additions & 8 deletions src/apicache.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function ApiCache() {
}
}

var middlewareOptions = []
var instance = this
var index = null

Expand Down Expand Up @@ -174,6 +175,12 @@ function ApiCache() {
return response.end(data, cacheObject.encoding)
}

function syncOptions() {
for (var i in middlewareOptions) {
Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions)
}
}

this.clear = function(target, isAutomatic) {
var group = index.groups[target]
var redis = globalOptions.redisClient
Expand Down Expand Up @@ -260,17 +267,36 @@ function ApiCache() {
}
}

this.middleware = function cache(strDuration, middlewareToggle) {
this.middleware = function cache(strDuration, middlewareToggle, localOptions) {
var duration = instance.getDuration(strDuration)
var opt = {}

middlewareOptions.push({
options: opt
})

var options = function (localOptions) {
if (localOptions) {
middlewareOptions.find(function (middleware) {
return middleware.options === opt
}).localOptions = localOptions
}

syncOptions()

return opt
}

options(localOptions)

return function cache(req, res, next) {
var cache = function(req, res, next) {
function bypass() {
debug('bypass detected, skipping cache.')
return next()
}

// initial bypass chances
if (!globalOptions.enabled) return bypass()
if (!opt.enabled) return bypass()
if (req.headers['x-apicache-bypass'] || req.headers['x-apicache-force-fetch']) return bypass()
if (typeof middlewareToggle === 'function') {
if (!middlewareToggle(req, res)) return bypass()
Expand All @@ -285,21 +311,21 @@ function ApiCache() {
var key = req.originalUrl || req.url

// Remove querystring from key if jsonp option is enabled
if (globalOptions.jsonp) {
if (opt.jsonp) {
key = url.parse(key).pathname
}

if (globalOptions.appendKey.length > 0) {
if (opt.appendKey.length > 0) {
var appendKey = req

for (var i = 0; i < globalOptions.appendKey.length; i++) {
appendKey = appendKey[globalOptions.appendKey[i]]
for (var i = 0; i < opt.appendKey.length; i++) {
appendKey = appendKey[opt.appendKey[i]]
}
key += '$$appendKey=' + appendKey
}

// attempt cache hit
var redis = globalOptions.redisClient
var redis = opt.redisClient
var cached = !redis ? memCache.getValue(key) : null

// send if cache hit from memory-cache
Expand All @@ -326,11 +352,16 @@ function ApiCache() {
return makeResponseCacheable(req, res, next, key, duration, strDuration)
}
}

cache.options = options

return cache
}

this.options = function(options) {
if (options) {
Object.assign(globalOptions, options)
syncOptions()

return this
} else {
Expand Down
148 changes: 147 additions & 1 deletion test/apicache_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ describe('.middleware {MIDDLEWARE}', function() {
it('is a function', function() {
var apicache = require('../src/apicache')
expect(typeof apicache.middleware).to.equal('function')
expect(apicache.middleware.length).to.equal(2)
expect(apicache.middleware.length).to.equal(3)
})

it('returns the middleware function', function() {
Expand All @@ -142,6 +142,152 @@ describe('.middleware {MIDDLEWARE}', function() {
expect(middleware.length).to.equal(3)
})

describe('options', function() {
var apicache = require('../src/apicache').newInstance()

it('uses global options if local ones not provided', function() {
apicache.options({
appendKey: ['test']
})
var middleware1 = apicache.middleware('10 seconds')
var middleware2 = apicache.middleware('20 seconds')
expect(middleware1.options()).to.eql({
debug: false,
defaultDuration: 3600000,
enabled: true,
appendKey: [ 'test' ],
jsonp: false,
redisClient: false,
statusCodes: { include: [], exclude: [] }
})
expect(middleware2.options()).to.eql({
debug: false,
defaultDuration: 3600000,
enabled: true,
appendKey: [ 'test' ],
jsonp: false,
redisClient: false,
statusCodes: { include: [], exclude: [] }
})
})

it('uses local options if they provided', function() {
apicache.options({
appendKey: ['test']
})
var middleware1 = apicache.middleware('10 seconds', null, {
debug: true,
defaultDuration: 7200000,
appendKey: ['bar'],
statusCodes: { include: [], exclude: ['400'] }
})
var middleware2 = apicache.middleware('20 seconds', null, {
debug: false,
defaultDuration: 1800000,
appendKey: ['foo'],
statusCodes: { include: [], exclude: ['200'] }
})
expect(middleware1.options()).to.eql({
debug: true,
defaultDuration: 7200000,
enabled: true,
appendKey: [ 'bar' ],
jsonp: false,
redisClient: false,
statusCodes: { include: [], exclude: ['400'] }
})
expect(middleware2.options()).to.eql({
debug: false,
defaultDuration: 1800000,
enabled: true,
appendKey: [ 'foo' ],
jsonp: false,
redisClient: false,
statusCodes: { include: [], exclude: ['200'] }
})
})

it('updates options if global ones changed', function() {
apicache.options({
debug: true,
appendKey: ['test']
})
var middleware1 = apicache.middleware('10 seconds', null, {
defaultDuration: 7200000,
statusCodes: { include: [], exclude: ['400'] }
})
var middleware2 = apicache.middleware('20 seconds', null, {
defaultDuration: 1800000,
statusCodes: { include: [], exclude: ['200'] }
})
apicache.options({
debug: false,
appendKey: ['foo']
})
expect(middleware1.options()).to.eql({
debug: false,
defaultDuration: 7200000,
enabled: true,
appendKey: [ 'foo' ],
jsonp: false,
redisClient: false,
statusCodes: { include: [], exclude: ['400'] }
})
expect(middleware2.options()).to.eql({
debug: false,
defaultDuration: 1800000,
enabled: true,
appendKey: [ 'foo' ],
jsonp: false,
redisClient: false,
statusCodes: { include: [], exclude: ['200'] }
})
})

it('updates options if local ones changed', function() {
apicache.options({
debug: true,
appendKey: ['test']
})
var middleware1 = apicache.middleware('10 seconds', null, {
defaultDuration: 7200000,
statusCodes: { include: [], exclude: ['400'] }
})
var middleware2 = apicache.middleware('20 seconds', null, {
defaultDuration: 900000,
statusCodes: { include: [], exclude: ['404'] }
})
middleware1.options({
debug: false,
defaultDuration: 1800000,
appendKey: ['foo']
})
middleware2.options({
defaultDuration: 450000,
enabled: false,
appendKey: ['foo']
})
expect(middleware1.options()).to.eql({
debug: false,
defaultDuration: 1800000,
enabled: true,
appendKey: [ 'foo' ],
jsonp: false,
redisClient: false,
statusCodes: { include: [], exclude: [] }
})
expect(middleware2.options()).to.eql({
debug: true,
defaultDuration: 450000,
enabled: false,
appendKey: [ 'foo' ],
jsonp: false,
redisClient: false,
statusCodes: { include: [], exclude: [] }
})
})
})

apis.forEach(api => {
describe(api.name + ' tests', function() {
var mockAPI = api.server
Expand Down