Created
May 31, 2012 12:43
-
-
Save grimen/2843157 to your computer and use it in GitHub Desktop.
node-http_cache
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require('sugar'); | |
var connect = require('connect'); | |
var http_cache = { | |
http_cache_enabled: true, | |
http_cached: function(req, res, resource, callback) { | |
if (this.changed(req, res, resource)) { | |
if (Object.isFunction(callback)) { | |
callback(resource); | |
} else { | |
return resource; | |
} | |
} else { | |
// HTTP 304 "Not Modified" - response by: changed() | |
} | |
}, | |
changed: function(req, res, resource) { | |
if (!this.http_cache_enabled) | |
return true; | |
var _this = this; | |
var last_modified; | |
if (this.is_collection(resource)) { | |
var last_resource = resource.map(function(r) { return _this.is_record(r); }).last(); | |
last_modified = this.last_modified_at(last_resource) | |
} else { | |
last_modified = this.last_modified_at(resource); | |
} | |
var cache_options = {etag: resource}; | |
if (last_modified) { | |
cache_options.last_modified = last_modified; | |
} | |
return this.stale(req, res, cache_options); | |
}, | |
last_modified_at: function(resource, updated_at) { | |
updated_at = updated_at || 'updated_at'; | |
if (this.is_record(resource, updated_at)) { | |
return (new Date(resource[updated_at])).toUTC(); | |
} else { | |
return undefined; | |
} | |
}, | |
is_record: function(resource, updated_at) { | |
updated_at = updated_at || 'updated_at'; | |
return !!(Object.isObject(resource) && Object.has(resource, updated_at)); | |
}, | |
is_collection: function(resource) { | |
return Object.isArray(resource); | |
}, | |
// --------------------------------------------------- | |
stale: function(req, res, options) { | |
var is_fresh = this.fresh_when(req, res, options); | |
return !is_fresh; | |
}, | |
fresh_when: function(req, res, options) { | |
options = options || {}; | |
if (options.etag) | |
this.set_etag(res, options.etag); | |
if (options.last_modified) | |
this.set_last_modified(res, options.last_modified); | |
var is_fresh = this.fresh(req, res); | |
if (is_fresh) { | |
connect.utils.notModified(res); // HTTP 304 "Not Modified" | |
} else { | |
// (nothing) | |
} | |
return is_fresh; | |
}, | |
fresh: function(req, res) { | |
var check_last_modified = this.if_modified_since(req), | |
check_etag = this.if_none_match(req); | |
if (!check_last_modified && !check_etag) | |
return false; | |
var is_fresh = true; | |
if (check_last_modified) | |
is_fresh = is_fresh && this.not_modified(req, res.header('last-modified')); | |
if (check_etag) | |
is_fresh = is_fresh && this.etag_matches(req, res.header('etag')); | |
return is_fresh; | |
}, | |
set_last_modified: function(res, last_modified, callback) { | |
last_modified = (new Date(last_modified)).toUTC().format(Date.RFC1036); | |
res.header('Last-Modified', last_modified); | |
}, | |
set_etag: function(res, etag, callback) { | |
etag = connect.utils.md5(this.expand_cache_key(etag)); | |
res.header('ETag', '"' + etag + '"'); | |
}, | |
expand_cache_key: function(key, namespace) { | |
var _this = this; | |
var expanded_cache_key = Object.isEmpty(namespace) ? '' : namespace + '/', | |
prefix = process.env.HTTP_CACHE_ID; | |
if (!Object.isEmpty(prefix)) { | |
expanded_cache_key = expanded_cache_key + prefix + '/'; | |
} | |
if (key && key.cache_key) { | |
key = Object.isFunction(key.cache_key) ? key.cache_key() : key.cache_key; | |
} else if (key && Object.isObject(key)) { | |
key = connect.utils.md5(JSON.stringify(key)); | |
} else if (key && Object.isArray(key)) { | |
key = key.compact().map (function(v) { return _this.expand_cache_key(v); }).join('-'); | |
} | |
if (key) { | |
key = key.toString() | |
expanded_cache_key = expanded_cache_key + key; | |
} | |
return expanded_cache_key; | |
}, | |
not_modified: function(req, modified_at) { | |
var modified_since = this.if_modified_since(req); | |
modified_at = (new Date(modified_at)).toUTC().format('{yyyy}-{MM}-{dd} {hh}:{mm}:{ss} UTC'); | |
return !!(modified_since && modified_at && modified_since >= modified_at); | |
}, | |
modified: function(req, modified_at) { | |
return !this.not_modified(req, modified_at); | |
}, | |
etag_matches: function(req, etag) { | |
var none_match = this.if_none_match(req); | |
return !!(Object.isString(none_match) && none_match.replace(/\"/g, '') === etag.replace(/\"/g, '')); | |
}, | |
if_modified_since: function(req) { | |
var modified_since = req.headers['if-modified-since']; | |
if (modified_since) { | |
return (new Date(modified_since)).toUTC().format('{yyyy}-{MM}-{dd} {hh}:{mm}:{ss} UTC'); | |
} else { | |
return null; | |
} | |
}, | |
if_none_match: function(req) { | |
return req.headers['if-none-match']; | |
} | |
}; | |
module.exports = http_cache; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var vows = require('vows'), | |
assert = require('assert'), | |
sugar = require('sugar'), | |
connect = require('connect'), | |
http = require('http'), | |
helper = require('../spec_helper.js'), | |
http_cache = require('../../lib/util/http_cache'); | |
vows.describe('util.http_cache') | |
.addBatch({ | |
'.http_cached': { | |
// TODO: Annoying to test currently, should port expresso assert.request to test middleware. | |
}, | |
'.changed': { | |
// TODO: See above. | |
}, | |
'.last_modified_at': { | |
"undefined": function() { | |
assert.equal ( http_cache.last_modified_at(undefined), undefined ); | |
}, | |
"null": function() { | |
assert.equal ( http_cache.last_modified_at(null), null ); | |
}, | |
"Object: {foo: 'bar'}": function() { | |
assert.equal ( http_cache.last_modified_at({foo: 'bar'}), null ); | |
}, | |
"Object: {foo: 'bar', updated_at: '2012-01-01 12:00:00 UTC'}": function() { | |
assert.deepEqual ( http_cache.last_modified_at({foo: 'bar', updated_at: '2012-01-01 12:00:00 UTC'}), new Date('Sun, 01 Jan 2012 11:00:00 GMT') ); | |
}, | |
"Object: {foo: 'bar', updated_at: new Date('2012-01-01 12:00:00 UTC')}": function() { | |
assert.deepEqual ( http_cache.last_modified_at({foo: 'bar', updated_at: new Date('2012-01-01 12:00:00 UTC')}), new Date('Sun, 01 Jan 2012 11:00:00 GMT') ); | |
} | |
}, | |
'.is_record': { | |
"undefined": function() { | |
assert.equal ( http_cache.is_record(undefined), false ); | |
}, | |
"null": function() { | |
assert.equal ( http_cache.is_record(null), false ); | |
}, | |
"Object: {foo: 'bar'}": function() { | |
assert.equal ( http_cache.is_record({foo: 'bar'}), false ); | |
}, | |
"Object: {foo: 'bar', updated_at: '2012-01-01 12:00:00 UTC'}": function() { | |
assert.equal ( http_cache.is_record({foo: 'bar', updated_at: '2012-01-01 12:00:00 UTC'}), true ); | |
}, | |
"Object: {foo: 'bar', updated_at: new Date('2012-01-01 12:00:00 UTC')}": function() { | |
assert.equal ( http_cache.is_record({foo: 'bar', updated_at: new Date('2012-01-01 12:00:00 UTC')}), true ); | |
} | |
}, | |
'.is_collection': { | |
"undefined": function() { | |
assert.equal ( http_cache.is_collection(undefined), false ); | |
}, | |
"null": function() { | |
assert.equal ( http_cache.is_collection(null), false ); | |
}, | |
"String": function() { | |
assert.equal ( http_cache.is_collection("Foo bar"), false ); | |
}, | |
"Object": function() { | |
assert.equal ( http_cache.is_collection({}), false ); | |
}, | |
"Array": function() { | |
assert.equal ( http_cache.is_collection([]), true ); | |
} | |
}, | |
// ------------------------ | |
'.stale': { | |
// TODO: Annoying to test currently, should port expresso assert.request to test middleware. | |
}, | |
'.fresh_when': { | |
// TODO: See above. | |
}, | |
'.fresh': { | |
// TODO: See above. | |
}, | |
'.set_last_modified': { | |
// TODO: See above. | |
}, | |
'.set_etag': { | |
// TODO: See above. | |
}, | |
'.expand_cache_key': { | |
"undefined": function() { | |
assert.equal ( http_cache.expand_cache_key(undefined), "" ); | |
}, | |
"null": function() { | |
assert.equal ( http_cache.expand_cache_key(null), "" ); | |
}, | |
"String: ''": function() { | |
assert.equal ( http_cache.expand_cache_key(''), "" ); | |
}, | |
"String: '123'": function() { | |
assert.equal ( http_cache.expand_cache_key('123'), "123" ); | |
}, | |
"Array: ['1', '2', '3']": function() { | |
assert.equal ( http_cache.expand_cache_key(['1', '2', '3']), "1-2-3" ); | |
}, | |
"Array: [1, 2, 3]": function() { | |
assert.equal ( http_cache.expand_cache_key([1, 2, 3]), "1-2-3" ); | |
}, | |
"Array: ['1', undefined, '3']": function() { | |
assert.equal ( http_cache.expand_cache_key([1, undefined, 3]), "1-3" ); | |
}, | |
"Array: ['1', null, '3']": function() { | |
assert.equal ( http_cache.expand_cache_key([1, null, 3]), "1-3" ); | |
}, | |
"Object: {foo: 'bar'}": function() { | |
assert.equal ( http_cache.expand_cache_key({foo: 'bar'}), connect.utils.md5(JSON.stringify({foo: 'bar'})) ); | |
}, | |
"Object: {foo: 'bar', cache_key: '3-2-1'}": function() { | |
assert.equal ( http_cache.expand_cache_key({foo: 'bar', cache_key: '3-2-1'}), "3-2-1" ); | |
} | |
}, | |
'.not_modified': { | |
"'If-Modified-Since' not set": function() { | |
var req = { | |
headers: {} | |
}; | |
assert.equal ( http_cache.not_modified(req, 'Mon, 1 Jan 2012 12:00:01 GMT'), false ); | |
}, | |
"'If-Modified-Since' set: <non-expired>": function() { | |
var req = { | |
headers: {'if-modified-since': 'Mon, 1 Jan 2012 12:00:00 GMT'} | |
}; | |
assert.equal ( http_cache.not_modified(req, 'Mon, 1 Jan 2012 11:59:59 GMT'), true ); | |
}, | |
"'If-Modified-Since' set: <equal>": function() { | |
var req = { | |
headers: {'if-modified-since': 'Mon, 1 Jan 2012 12:00:00 GMT'} | |
}; | |
assert.equal ( http_cache.not_modified(req, 'Mon, 1 Jan 2012 12:00:00 GMT'), true ); | |
}, | |
"'If-Modified-Since' set: <expired>": function() { | |
var req = { | |
headers: {'if-modified-since': 'Mon, 1 Jan 2012 12:00:00 GMT'} | |
}; | |
assert.equal ( http_cache.not_modified(req, 'Mon, 1 Jan 2012 12:00:01 GMT'), false ); | |
} | |
}, | |
'.etag_matches': { | |
"'If-None-Match' not set": function() { | |
var req = { | |
headers: {} | |
}; | |
assert.equal ( http_cache.etag_matches(req, '13fca1035d7083e4fd2403c9cc36ab8a2d79518e'), false ); | |
}, | |
"'If-None-Match' set: <equal>": function() { | |
var req = { | |
headers: {'if-none-match': '"13fca1035d7083e4fd2403c9cc36ab8a2d79518e"'} | |
}; | |
assert.equal ( http_cache.etag_matches(req, '13fca1035d7083e4fd2403c9cc36ab8a2d79518e'), true ); | |
}, | |
"'If-None-Match' set: <non-equal>": function() { | |
var req = { | |
headers: {'if-none-match': '"13fca1035d7083e4fd2403c9cc36ab8a2d79518e"'} | |
}; | |
assert.equal ( http_cache.etag_matches(req, 'a3fca1035d7083e4fd2403c9cc36ab8a2d79518e'), false ); | |
} | |
}, | |
'.if_modified_since': { | |
"'If-Modified-Since' not set": function() { | |
var req = { | |
headers: {} | |
}; | |
assert.equal ( http_cache.if_modified_since(req), null ); | |
}, | |
"'If-Modified-Since' set": function() { | |
var req = { | |
headers: {'if-modified-since': 'Mon, 1 Jan 2012 12:00:00 GMT'} | |
}; | |
assert.equal ( http_cache.if_modified_since(req), '2012-01-01 12:00:00 UTC' ); | |
} | |
}, | |
'.if_none_match': { | |
"'If-None-Match' not set": function() { | |
var req = { | |
headers: {} | |
}; | |
assert.equal ( http_cache.if_none_match(req), null ); | |
}, | |
"'If-None-Match' set": function() { | |
var req = { | |
headers: {'if-none-match': '"13fca1035d7083e4fd2403c9cc36ab8a2d79518e"'} | |
}; | |
assert.equal ( http_cache.if_none_match(req), '"13fca1035d7083e4fd2403c9cc36ab8a2d79518e"' ); | |
}, | |
} | |
}).export(module); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment