Navigation Menu

Skip to content

Commit

Permalink
Split cache and middlewares that use cache
Browse files Browse the repository at this point in the history
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
        }
      ]
    }));
  • Loading branch information
kou committed Apr 7, 2014
1 parent 7dd99d1 commit ab2834c
Show file tree
Hide file tree
Showing 14 changed files with 400 additions and 339 deletions.
6 changes: 5 additions & 1 deletion index.js
Expand Up @@ -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')
};
50 changes: 50 additions & 0 deletions lib/cache/index.js
@@ -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;
5 changes: 5 additions & 0 deletions lib/middleware/cache-statistics/index.js
@@ -0,0 +1,5 @@
module.exports = function middleware(cache) {
return function(request, response, next) {
response.jsonp(200, cache.getStatistics());
}
}
File renamed without changes.
92 changes: 92 additions & 0 deletions lib/middleware/cache/index.js
@@ -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();
}
});
};
};
6 changes: 2 additions & 4 deletions lib/response-cache/rule.js → lib/middleware/cache/rule.js
@@ -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) {
Expand Down
87 changes: 0 additions & 87 deletions lib/response-cache/cache.js

This file was deleted.

53 changes: 0 additions & 53 deletions lib/response-cache/index.js

This file was deleted.

49 changes: 49 additions & 0 deletions test/cache.test.js
@@ -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);
});
});
});
});

0 comments on commit ab2834c

Please sign in to comment.