Kouhei Sutou
null+****@clear*****
Mon Apr 7 13:08:36 JST 2014
Kouhei Sutou 2014-04-07 13:08:36 +0900 (Mon, 07 Apr 2014) New Revision: ab2834c0c07980ae82062f527f1d062459f2604c https://github.com/droonga/express-droonga/commit/ab2834c0c07980ae82062f527f1d062459f2604c Message: Split cache and middlewares that use cache API is changed. Before: application.use(droonga.cache({ size: cacheSize, rules: [ { regex: /^\//, ttlInMilliSeconds: 1000 } ] })); After: var cache = new droonga.cache({ size: cache }); application.use('/cache/statistics', droonga.middleware.cacheStatistics(cache)); application.use(droonga.middleware.cache(cache, { rules: [ { regex: /^\//, ttlInMilliSeconds: 1000 } ] })); Added files: lib/cache/index.js lib/middleware/cache-statistics/index.js lib/middleware/cache/index.js test/cache.test.js test/middleware/cache-statistics/middleware.test.js Removed files: lib/response-cache/cache.js lib/response-cache/index.js test/response-cache/cache.test.js Modified files: index.js Renamed files: lib/middleware/cache/entry.js (from lib/response-cache/entry.js) lib/middleware/cache/rule.js (from lib/response-cache/rule.js) test/middleware/cache/entry.test.js (from test/response-cache/entry.test.js) test/middleware/cache/middleware.test.js (from test/response-cache/middleware.test.js) test/middleware/cache/rule.test.js (from test/response-cache/rule.test.js) Modified: index.js (+5 -1) =================================================================== --- index.js 2014-04-07 11:52:30 +0900 (da3fcf1) +++ index.js 2014-04-07 13:08:36 +0900 (4f50f3e) @@ -39,4 +39,8 @@ express.application.droonga = function(params) { require('./lib/adapter/api').exportTo(exports); exports.command = require('./lib/adapter/command'); -exports.cache = require('./lib/response-cache'); +exports.cache = require('./lib/cache'); +exports.middleware = { + cacheStatistics: require('./lib/middleware/cache-statistics'), + cache: require('./lib/middleware/cache') +}; Added: lib/cache/index.js (+50 -0) 100644 =================================================================== --- /dev/null +++ lib/cache/index.js 2014-04-07 13:08:36 +0900 (2c676a2) @@ -0,0 +1,50 @@ +var createCache = require('uber-cache'); + +var defaultSize = 100; + +function normalizeOptions(options) { + options = options || {}; + + options.size = options.size || defaultSize; + + return options; +} + +function Cache(options) { + options = normalizeOptions(options); + this.cache = createCache({ + size: options.size + }); + this.nGets = 0; + this.nHits = 0; +} +Cache.prototype = { + 'get': function(key, callback) { + return this.cache.get(key, function(error, cachedResponse) { + this.nGets++; + if (cachedResponse) { + this.nHits++; + } + callback(error, cachedResponse); + }.bind(this)); + }, + + 'set': function(key, value, ttl, callback) { + return this.cache.set(key, value, ttl, callback); + }, + + getStatistics: function() { + var hitRatio; + if (this.nGets == 0) { + hitRatio = 0.0; + } else { + hitRatio = (this.nHits / this.nGets) * 100; + } + return { + "nGets": this.nGets, + "nHits": this.nHits, + "hitRatio": hitRatio + }; + } +}; +module.exports = Cache; Added: lib/middleware/cache-statistics/index.js (+5 -0) 100644 =================================================================== --- /dev/null +++ lib/middleware/cache-statistics/index.js 2014-04-07 13:08:36 +0900 (50a8c71) @@ -0,0 +1,5 @@ +module.exports = function middleware(cache) { + return function(request, response, next) { + response.jsonp(200, cache.getStatistics()); + } +} Renamed: lib/middleware/cache/entry.js (+0 -0) 100% =================================================================== Added: lib/middleware/cache/index.js (+92 -0) 100644 =================================================================== --- /dev/null +++ lib/middleware/cache/index.js 2014-04-07 13:08:36 +0900 (fd02f0f) @@ -0,0 +1,92 @@ +var Entry = require('./entry'); +var Rule = require('./rule'); + +var defaultTTLInMilliSeconds = 60 * 1000; + +function getNormalizedTTLInMilliSeconds(options) { + var ttlInSeconds = options.ttlInSeconds || 0; + return options.ttl || + options.ttlInMilliSeconds || + (ttlInSeconds * 1000) || + 0; +} + +function validateRules(rules) { + if (!Array.isArray(rules)) + throw new Error('rules must be an array'); + + if (!rules.length) + throw new Error('you must specify one or more rules'); +} + +function createRules(options) { + var ttlInMilliSeconds = + getNormalizedTTLInMilliSeconds(options) || + defaultTTLInMilliSeconds; + + validateRules(options.rules); + + return options.rules.map(function(rule) { + rule.ttlInMilliSeconds = getNormalizedTTLInMilliSeconds(rule) || + ttlInMilliSeconds; + return new Rule(rule); + }); +} + +function findRule(rules, request) { + if (request.method != 'GET') + return null; + + var foundRule = null; + rules.some(function(rule) { + if (rule.match(request)) + return foundRule = rule; + }); + return foundRule; +} + +function generateKey(request) { + return request.method + '\n' + request.url; +} + +function sendCachedResponse(response, cached) { + response.statusCode = cached.status; + Object.keys(cached.headers).forEach(function(key) { + response.setHeader(key, cached.headers[key]); + }); + response.setHeader('X-Droonga-Cached', 'yes'); + cached.body.forEach(function(chunk) { + response.write(chunk.data, chunk.encoding); + }); + response.end(); +} + +module.exports = function cacheMiddleware(cache, options) { + var rules = createRules(options); + + return function(request, response, next) { + var rule = findRule(rules, request); + if (!rule) { + next(); + return; + } + + var cacheKey = generateKey(request); + cache.get(cacheKey, function(error, cachedResponse) { + if (error) { + console.error(error); + return; + } + + if (cachedResponse) { + sendCachedResponse(response, cachedResponse); + } else { + var entry = new Entry(); + entry.hook(response, function(cachedResponse) { + cache.set(cacheKey, cachedResponse, rule.ttlInMilliSeconds); + }); + next(); + } + }); + }; +}; Renamed: lib/middleware/cache/rule.js (+2 -4) 65% =================================================================== --- lib/response-cache/rule.js 2014-04-07 11:52:30 +0900 (a177458) +++ lib/middleware/cache/rule.js 2014-04-07 13:08:36 +0900 (0511975) @@ -1,13 +1,11 @@ -function Rule(rule, options) { - options = options || {}; - +function Rule(rule) { if (!rule) throw new Error('no rule is given'); if (!rule.regex) throw new Error('rule must have "regex"'); this.regex = rule.regex; - this.ttlInMilliSeconds = rule.ttlInMilliSeconds || options.ttlInMilliSeconds || 0; + this.ttlInMilliSeconds = rule.ttlInMilliSeconds || 0; } Rule.prototype = { match: function(request) { Deleted: lib/response-cache/cache.js (+0 -87) 100644 =================================================================== --- lib/response-cache/cache.js 2014-04-07 11:52:30 +0900 (6a913d7) +++ /dev/null @@ -1,87 +0,0 @@ -var createCache = require('uber-cache'); -var Rule = require('./rule'); - -var defaultSize = 100; -var defaultTTLInMilliSeconds = 60 * 1000; - -function getNormalizedTTLInMilliSeconds(options) { - var ttlInSeconds = options.ttlInSeconds || 0; - return options.ttl || - options.ttlInMilliSeconds || - (ttlInSeconds * 1000) || - 0; -} - -function normalizeOptions(options) { - options = options || {}; - - if (!Array.isArray(options.rules)) - throw new Error('rules must be an array'); - - if (!options.rules.length) - throw new Error('you must specify one or more rules'); - - options.size = options.size || defaultSize; - options.ttlInMilliSeconds = getNormalizedTTLInMilliSeconds(options) || - defaultTTLInMilliSeconds; - - options.rules = options.rules.map(function(rule) { - rule.ttlInMilliSeconds = getNormalizedTTLInMilliSeconds(rule) || - options.ttlInMilliSeconds; - return new Rule(rule, options); - }); - - return options; -} - -function Cache(options) { - options = normalizeOptions(options); - this.cache = createCache({ - size: options.size - }); - this.rules = options.rules; - this.nGets = 0; - this.nHits = 0; -} -Cache.prototype = { - 'get': function(key, callback) { - return this.cache.get(key, function(error, cachedResponse) { - this.nGets++; - if (cachedResponse) { - this.nHits++; - } - callback(error, cachedResponse); - }.bind(this)); - }, - - 'set': function(key, value, ttl, callback) { - return this.cache.set(key, value, ttl, callback); - }, - - getRule: function(request) { - if (request.method != 'GET') - return null; - - var foundRule = null; - this.rules.some(function(rule) { - if (rule.match(request)) - return foundRule = rule; - }); - return foundRule; - }, - - getStatistics: function() { - var hitRatio; - if (this.nGets == 0) { - hitRatio = 0.0; - } else { - hitRatio = (this.nHits / this.nGets) * 100; - } - return { - "nGets": this.nGets, - "nHits": this.nHits, - "hitRatio": hitRatio - }; - } -}; -module.exports = Cache; Deleted: lib/response-cache/index.js (+0 -53) 100644 =================================================================== --- lib/response-cache/index.js 2014-04-07 11:52:30 +0900 (1e9c23d) +++ /dev/null @@ -1,53 +0,0 @@ -var Entry = require('./entry'); - -function generateKey(request) { - return request.method + '\n' + request.url; -} - -function sendCachedResponse(response, cached) { - response.statusCode = cached.status; - Object.keys(cached.headers).forEach(function(key) { - response.setHeader(key, cached.headers[key]); - }); - response.setHeader('X-Droonga-Cached', 'yes'); - cached.body.forEach(function(chunk) { - response.write(chunk.data, chunk.encoding); - }); - response.end(); -} - -module.exports.Cache = require('./cache'); - -module.exports.statisticsMiddleware = function statisticsMiddleware(cache) { - return function(request, response, next) { - response.jsonp(200, cache.getStatistics()); - } -} - -module.exports.middleware = function middleware(cache) { - return function(request, response, next) { - var rule = cache.getRule(request); - if (!rule) { - next(); - return; - } - - var cacheKey = generateKey(request); - cache.get(cacheKey, function(error, cachedResponse) { - if (error) { - console.error(error); - return; - } - - if (cachedResponse) { - sendCachedResponse(response, cachedResponse); - } else { - var entry = new Entry(); - entry.hook(response, function(cachedResponse) { - cache.set(cacheKey, cachedResponse, rule.ttlInMilliSeconds); - }); - next(); - } - }); - }; -}; Added: test/cache.test.js (+49 -0) 100644 =================================================================== --- /dev/null +++ test/cache.test.js 2014-04-07 13:08:36 +0900 (740ae3d) @@ -0,0 +1,49 @@ +var assert = require('chai').assert; + +var Cache = require('../lib/cache'); + +suite('Cache', function() { + suite('statistics', function() { + var cache; + setup(function() { + cache = new Cache(); + }); + + test('nGets', function() { + assert.equal(cache.getStatistics().nGets, 0); + cache.get('key', function(error, cachedResponse) { + }); + assert.equal(cache.getStatistics().nGets, 1); + }); + + test('nHits', function() { + cache.set('key', 'value'); + assert.equal(cache.getStatistics().nHits, 0); + cache.get('key', function(error, cachedResponse) { + }); + assert.equal(cache.getStatistics().nHits, 1); + }); + + suite('hitRatio', function() { + test('0 gets', function() { + assert.equal(cache.getStatistics().hitRatio, 0.0); + }); + + test('0 hits', function() { + cache.get('key', function(error, cachedResponse) { + }); + assert.equal(cache.getStatistics().hitRatio, 0.0); + }); + + test('1/2 hits', function() { + cache.get('key', function(error, cachedResponse) { + }); + cache.set('key', 'value'); + cache.get('key', function(error, cachedResponse) { + }); + assert.equal(cache.getStatistics().hitRatio, 50.0); + }); + }); + }); +}); + Added: test/middleware/cache-statistics/middleware.test.js (+39 -0) 100644 =================================================================== --- /dev/null +++ test/middleware/cache-statistics/middleware.test.js 2014-04-07 13:08:36 +0900 (8ee1c55) @@ -0,0 +1,39 @@ +var client = require('supertest'); +var express = require('express'); + +var assert = require('chai').assert; + +var Cache = require('../../../lib/cache'); +var middleware = require('../../../lib/middleware/cache-statistics'); + +suite('middleware - cache statistics -', function() { + var application; + var cache; + setup(function() { + cache = new Cache(); + application = express(); + application.use('/cache/statistics', middleware(cache)); + }); + + test('json', function(done) { + client(application) + .get('/cache/statistics') + .expect(200) + .end(function(error, response) { + if (error) + return done(error); + + var statistics = { + nGets: 0, + nHits: 0, + hitRatio: 0.0 + }; + try { + assert.deepEqual(response.body, statistics); + } catch (error) { + return done(error); + } + done(); + }); + }); +}); Renamed: test/middleware/cache/entry.test.js (+2 -2) 90% =================================================================== --- test/response-cache/entry.test.js 2014-04-07 11:52:30 +0900 (7d6ba21) +++ test/middleware/cache/entry.test.js 2014-04-07 13:08:36 +0900 (4d8d50d) @@ -1,8 +1,8 @@ var assert = require('chai').assert; -var Entry = require('../../lib/response-cache/entry'); +var Entry = require('../../../lib/middleware/cache/entry'); -suite('Response Cache Entry', function() { +suite('middleware - cache - Entry', function() { suite('isCachable', function() { test('not processed', function() { var entry = new Entry(); Renamed: test/middleware/cache/middleware.test.js (+152 -45) 68% =================================================================== --- test/response-cache/middleware.test.js 2014-04-07 11:52:30 +0900 (55f2409) +++ test/middleware/cache/middleware.test.js 2014-04-07 13:08:36 +0900 (4288bb3) @@ -3,19 +3,158 @@ var express = require('express'); var assert = require('chai').assert; -var responseCache = require('../../lib/response-cache'); +var Cache = require('../../../lib/cache'); +var middleware = require('../../../lib/middleware/cache'); -suite('Response Cache Middleware', function() { +suite('middleware - cache -', function() { var application; var cache; setup(function() { - cache = new responseCache.Cache({ + cache = new Cache(); + application = express(); + application.use(middleware(cache, { rules: [ { regex: /cached/ } ] + })); + }); + + suite('required parameters', function() { + test('missing rules', function() { + assert.throw(function() { + middleware(cache, { + }); + }, Error); + }); + + test('not-array rules', function() { + assert.throw(function() { + middleware(cache, { + rules: { + 'foo' : { + ttl: 10 + } + } + }); + }, Error); + }); + }); + + suite('findRule -', function() { + suite('a rule -', function() { + setup(function() { + application = express(); + application.use(middleware(cache, { + rules: [ + { regex: /^\/cache-target\// } + ] + })); + }); + + test('non-GET requests', function(done) { + application.post('/cache-target/path', function(request, response) { + response.send(200, 'POST - success'); + }); + client(application) + .post('/cache-target/path') + .end(function(error, response) { + if (error) + return done(error); + + var nGets = cache.getStatistics().nGets; + try { + assert.deepEqual(nGets, 0); + } catch (error) { + return done(error); + } + done(); + }); + }); + + test('not matched', function(done) { + application.get('/not-cache-target/path', function(request, response) { + response.send(200, 'GET - success'); + }); + client(application) + .get('/not-cache-target/path') + .end(function(error, response) { + if (error) + return done(error); + + var nGets = cache.getStatistics().nGets; + try { + assert.deepEqual(nGets, 0); + } catch (error) { + return done(error); + } + done(); + }); + }); + + test('matched to a rule', function(done) { + application.get('/cache-target/path', function(request, response) { + response.send(200, 'GET - success'); + }); + client(application) + .get('/cache-target/path') + .end(function(error, response) { + if (error) + return done(error); + + var nGets = cache.getStatistics().nGets; + try { + assert.deepEqual(nGets, 1); + } catch (error) { + return done(error); + } + done(); + }); + }); + }); + + suite('multiple rules', function() { + setup(function() { + application = express(); + }); + + test('matched to multiple rules', function(done) { + var notUsedTtlInMilliSeconds = 1; + application.use(middleware(cache, { + rules: [ + { + regex: /^\/cache-target/, + ttlInMilliSeconds: 1000 + }, + { + regex: /^\/cache-target-not-used/, + ttlInMilliSeconds: notUsedTtlInMilliSeconds + } + ] + })); + application.get('/cache-target-not-used', function(request, response) { + response.send(200, 'GET - success'); + }); + + client(application) + .get('/cache-target-not-used') + .end(function(error, response) { + if (error) + return done(error); + + setTimeout(function() { + client(application) + .get('/cache-target-not-used') + .expect(200) + .expect('X-Droonga-Cached', 'yes') + .end(function(error, response) { + if (error) + return done(error); + done(); + }); + }, notUsedTtlInMilliSeconds + 1); + }); + }); }); - application = express(); - application.use(responseCache.middleware(cache)); }); test('cached', function(done) { @@ -227,13 +366,14 @@ suite('Response Cache Middleware', function() { test('size over', function(done) { application = express(); - cache = new responseCache.Cache({ + cache = new Cache({ size: 1, + }); + application.use(middleware(cache, { rules: [ { regex: /cached/ } ] - }); - application.use(responseCache.middleware(cache)); + })); application.get('/cached/first', function(request, response) { response.json(200, 'OK'); }); @@ -278,13 +418,12 @@ suite('Response Cache Middleware', function() { test('expired by global TTL', function(done) { application = express(); - cache = new responseCache.Cache({ + application.use(middleware(cache, { ttlInMilliSeconds: 10, rules: [ { regex: /cached/ } ] - }); - application.use(responseCache.middleware(cache)); + })); application.get('/cached/expired', function(request, response) { response.json(200, 'OK'); }); @@ -311,12 +450,11 @@ suite('Response Cache Middleware', function() { test('expired by TTL for a rule', function(done) { application = express(); - cache = new responseCache.Cache({ + application.use(middleware(cache, { rules: [ { regex: /cached/, ttlInMilliSeconds: 10 } ] - }); - application.use(responseCache.middleware(cache)); + })); application.get('/cached/expired', function(request, response) { response.json(200, 'OK'); }); @@ -341,36 +479,5 @@ suite('Response Cache Middleware', function() { }); }); }); - - suite('statistics', function() { - setup(function() { - application = express(); - application.use("/cache/statistics", - responseCache.statisticsMiddleware(cache)); - application.use(responseCache.middleware(cache)); - }); - - test('json', function(done) { - client(application) - .get('/cache/statistics') - .expect(200) - .end(function(error, response) { - if (error) - return done(error); - - var statistics = { - nGets: 0, - nHits: 0, - hitRatio: 0.0 - }; - try { - assert.deepEqual(response.body, statistics); - } catch (error) { - return done(error); - } - done(); - }); - }); - }); }); Renamed: test/middleware/cache/rule.test.js (+4 -11) 59% =================================================================== --- test/response-cache/rule.test.js 2014-04-07 11:52:30 +0900 (aa85572) +++ test/middleware/cache/rule.test.js 2014-04-07 13:08:36 +0900 (aa2183d) @@ -1,23 +1,16 @@ var assert = require('chai').assert; -var Rule = require('../../lib/response-cache/rule'); +var Rule = require('../../../lib/middleware/cache/rule'); -suite('Response Cache Rule', function() { +suite('middleware - cache - Rule', function() { suite('ttlInMilliSeconds', function() { test('default', function() { var rule = new Rule({ regex: /./, ttlInMilliSeconds: null }); assert.equal(rule.ttlInMilliSeconds, 0); }); - test('global', function() { - var rule = new Rule({ regex: /./, ttlInMilliSeconds: null }, - { ttlInMilliSeconds: 10 }); - assert.equal(rule.ttlInMilliSeconds, 10); - }); - - test('local', function() { - var rule = new Rule({ regex: /./, ttlInMilliSeconds: 20 }, - { ttlInMilliSeconds: 10 }); + test('specified', function() { + var rule = new Rule({ regex: /./, ttlInMilliSeconds: 20 }); assert.equal(rule.ttlInMilliSeconds, 20); }); }); Deleted: test/response-cache/cache.test.js (+0 -136) 100644 =================================================================== --- test/response-cache/cache.test.js 2014-04-07 11:52:30 +0900 (7880fea) +++ /dev/null @@ -1,136 +0,0 @@ -var assert = require('chai').assert; - -var Cache = require('../../lib/response-cache/cache'); - -suite('Response Cache', function() { - suite('required parameters', function() { - test('missing rules', function() { - assert.throw(function() { - var cache = new Cache({ - }); - }, Error); - }); - - test('not-array rules', function() { - assert.throw(function() { - var cache = new Cache({ - rules: { - 'foo' : { - ttl: 10 - } - } - }); - }, Error); - }); - }); - - suite('getRule', function() { - test('non-GET requests', function() { - var cache = new Cache({ - rules: [ - { regex: /foo/ } - ] - }); - var stubRequest = { - method: 'POST' - }; - var rule = cache.getRule(stubRequest); - assert.isNull(rule); - }); - - test('not mached', function() { - var cache = new Cache({ - rules: [ - { regex: /foo/ } - ] - }); - var stubRequest = { - method: 'GET', - url: 'bar' - }; - var rule = cache.getRule(stubRequest); - assert.isNull(rule); - }); - - test('mached to a rule', function() { - var cache = new Cache({ - rules: [ - { regex: /foo/ } - ] - }); - var stubRequest = { - method: 'GET', - url: 'fooooo' - }; - var rule = cache.getRule(stubRequest); - assert.isNotNull(rule); - assert.deepEqual(rule.regex, /foo/); - }); - - test('mached to multiple rules', function() { - var primaryRegex = /foo/; - var secondaryRegex = /foobar/; - var cache = new Cache({ - rules: [ - { regex: primaryRegex }, - { regex: secondaryRegex }, - ] - }); - var stubRequest = { - method: 'GET', - url: 'foobar' - }; - var rule = cache.getRule(stubRequest); - assert.isNotNull(rule); - assert.deepEqual(rule.regex, primaryRegex); - }); - }); - - suite('statistics', function() { - var cache; - setup(function() { - cache = new Cache({ - rules: [ - { regex: /.*/ } - ] - }); - }); - - test('nGets', function() { - assert.equal(cache.getStatistics().nGets, 0); - cache.get('key', function(error, cachedResponse) { - }); - assert.equal(cache.getStatistics().nGets, 1); - }); - - test('nHits', function() { - cache.set('key', 'value'); - assert.equal(cache.getStatistics().nHits, 0); - cache.get('key', function(error, cachedResponse) { - }); - assert.equal(cache.getStatistics().nHits, 1); - }); - - suite('hitRatio', function() { - test('0 gets', function() { - assert.equal(cache.getStatistics().hitRatio, 0.0); - }); - - test('0 hits', function() { - cache.get('key', function(error, cachedResponse) { - }); - assert.equal(cache.getStatistics().hitRatio, 0.0); - }); - - test('1/2 hits', function() { - cache.get('key', function(error, cachedResponse) { - }); - cache.set('key', 'value'); - cache.get('key', function(error, cachedResponse) { - }); - assert.equal(cache.getStatistics().hitRatio, 50.0); - }); - }); - }); -}); - -------------- next part -------------- HTML����������������������������... Download