Permalink
Browse files

Added basic maxage and etag caching support on GETs

  • Loading branch information...
1 parent 1035331 commit 28d620dd7f15e4ade52e37f15d2c158a24285ba8 @DukeyToo DukeyToo committed Mar 14, 2012
Showing with 289 additions and 131 deletions.
  1. +1 −0 lib/cligen.js
  2. +8 −9 lib/config.js
  3. +2 −2 lib/memcache.js
  4. +86 −0 lib/requestCache.js
  5. +123 −106 lib/responder.js
  6. +23 −12 lib/server.js
  7. +2 −2 readme.md
  8. +44 −0 test/cache.js
View
@@ -57,6 +57,7 @@ Parser.prototype.parse = function(configPath) {
logger.info('stopping server');
return server.stop();
}
+
var app = server.listen(rawConfig, serverConfig, function(err, newCommandName, newConfig, finalCallback) {
if (err) {
logger.error("Error: " + err);
View
@@ -8,7 +8,7 @@ exports.getCommands = getCommands;
exports.getConfigFromPath = function(configPath) {
var config = JSON.parse(fs.readFileSync(configPath));
- return fixBadConfigs(config);
+ return fixBadConfigs(config);
};
function getCommands(rawConfig) {
@@ -130,6 +130,7 @@ function getConfigCommand() {
cmd.description = "Use this to get information on this API";
cmd.options = [];
cmd.verb = "GET";
+ cmd.maxage = 7200 * 24;
var returnCfg = {name: "config", description: "json config file"}
cmd.returns = [];
cmd.returns.push(returnCfg);
@@ -176,7 +177,7 @@ exports.getMatchingCommandByRequestPath = function(config, requestPath) {
**/
exports.getDefaultConfig = function(config, commandName) {
var commandSpec = getCommandByName(config, commandName);
- var config = {};
+ var result = {};
//environment
var environment = {};
@@ -190,14 +191,14 @@ exports.getDefaultConfig = function(config, commandName) {
environment[env.parameter] = process.env[env.parameter];
};
};
- config.environment = environment;
+ result.environment = environment;
//parameter
commandSpec.parameters.forEach(function(param) {
if (param.type && param.type=='multi') {
- config[param.name] = [];
+ result[param.name] = [];
} else {
- config[param.name] = '';
+ result[param.name] = '';
}
})
@@ -214,11 +215,9 @@ exports.getDefaultConfig = function(config, commandName) {
}
}
};
- config.options = options;
+ result.options = options;
- //console.log(JSON.stringify(config));
-
- return config;
+ return result;
}
//http://stackoverflow.com/questions/7997342/merge-json-objects-without-new-keys
View
@@ -17,8 +17,8 @@ exports.get = function(key) {
if (cache.hasOwnProperty(key)) {
return cache[key];
} else {
- console.verbose('cache miss key:')
- console.verbose(key);
+ logger.verbose('cache miss key:')
+ logger.verbose(key);
}
}
View
@@ -0,0 +1,86 @@
+var dataCache = require('./memcache.js');
+var url = require('url');
+var crypto = require("crypto");
+
+/**********************************
+* Handles caching for the given command. When done, calls either hit() or miss()
+* depending on whether it was a cache hit or miss. No further processing is necessary
+* if it was a hit.
+*
+**/
+exports.handle = function(commandName, config, commandSpec, req, res, hit, miss) {
+ if (commandSpec.verb.toUpperCase() != 'GET') return miss();
+ var key = commandName + JSON.stringify(config);
+
+ var result = dataCache.get(key);
+ if (!result) return miss();
+
+ res.setHeader('Cache-Control', 'must-revalidate, max-age=' + commandSpec.maxage);
+ var etag = getETag(result)
+ res.setHeader('ETag', etag)
+
+ if (req.headers['if-none-match'] && (req.headers['if-none-match'].indexOf(etag) > -1 || req.headers['if-none-match'] == '*')) {
+ //etag matches, send a 304 response
+ res.statusCode = 304;
+ res.end()
+ } else {
+ //send normal 200 response, with caching headers and data
+ res.contentType('application/json');
+ var data = JSON.stringify(result);
+ if (url.parse(req.url, true).query.callback)
+ data = req.query.callback + '(' + data + ');'
+
+ res.setHeader('Content-Length', data.length)
+ res.end(data)
+ }
+
+ return hit();
+}
+
+/************************************
+* Caches the given result and adds necessary cache headers. Embeds all necessary cache rules,
+* so it is safe to call even when the result should not be cached.
+*
+**/
+exports.save = function(commandName, config, commandSpec, req, res, err, result) {
+ if (err) return doNotCache(res);
+ if (commandSpec.verb.toUpperCase() != 'GET') return doNotCache(res);
+ if (!commandSpec.maxage) return doNotCache(res);
+
+ var key = commandName + JSON.stringify(config);
+
+ dataCache.set(key, result, commandSpec.maxage);
+ var etag = getETag(result)
+
+ res.setHeader('Cache-Control', 'must-revalidate, max-age=' + commandSpec.maxage);
+ res.setHeader('ETag', etag)
+
+ return true;
+}
+
+function doNotCache(res) {
+ res.setHeader('Cache-Control', 'no-cache');
+
+ return false;
+}
+
+function getETag(result) {
+ var hash = crypto.createHash('md5'); //md5 is cheaper CPU-wise than sha1
+
+ if (typeof(result) == 'object') result = JSON.stringify(result);
+
+ hash.update(result, 'utf8');
+
+ return 'W/"' + formatWordKey(new Buffer(hash.digest())) + '"';
+}
+
+function formatWordKey(buf) {
+ var key = ""; //buf.toString('hex');
+ var letters = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"; //no I,O,i,l,o because they hard to read
+ for(var i=0;i<buf.length;i++) {
+ var letterNumber = buf[i] % letters.length;
+ key = key + letters[letterNumber];
+ };
+
+ return key;
+}
Oops, something went wrong.

0 comments on commit 28d620d

Please sign in to comment.