included LRU cache with redis invalidation
This commit is contained in:
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
@@ -18,7 +18,8 @@ if (ENV != 'development' && ENV != 'production'){
|
||||
|
||||
var _ = require('underscore')
|
||||
, Step = require('step')
|
||||
, cartoData = require('./lib/cartodb/carto_data');
|
||||
, cartoData = require('./lib/cartodb/carto_data')
|
||||
, CartodbWindshaft = require('./lib/cartodb/cartodb_windshaft');
|
||||
|
||||
|
||||
|
||||
@@ -29,47 +30,9 @@ _.extend(global.settings, global.environment);
|
||||
|
||||
var Windshaft = require('windshaft');
|
||||
var serverOptions = require('./lib/cartodb/server_options');
|
||||
var Cache = require('./lib/cartodb/tile_cache');
|
||||
|
||||
// boot
|
||||
var ws = new Windshaft.Server(serverOptions);
|
||||
|
||||
/**
|
||||
* Helper to allow access to the layer to be used in the maps infowindow popup.
|
||||
*/
|
||||
ws.get(serverOptions.base_url + '/infowindow', function(req, res){
|
||||
Step(
|
||||
function(){
|
||||
serverOptions.getInfowindow(req, this);
|
||||
},
|
||||
function(err, data){
|
||||
if (err){
|
||||
res.send(err.message, 400);
|
||||
} else {
|
||||
res.send({infowindow: data}, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to allow access to metadata to be used in embedded maps.
|
||||
*/
|
||||
ws.get(serverOptions.base_url + '/map_metadata', function(req, res){
|
||||
Step(
|
||||
function(){
|
||||
serverOptions.getMapMetadata(req, this);
|
||||
},
|
||||
function(err, data){
|
||||
if (err){
|
||||
res.send(err.message, 400);
|
||||
} else {
|
||||
res.send({map_metadata: data}, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
ws = CartodbWindshaft(serverOptions);
|
||||
ws.listen(global.environment.windshaft_port);
|
||||
console.log("Windshaft tileserver started on port " + global.environment.windshaft_port);
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// this module allows to invalidate cache for a table or check if the cache for a table
|
||||
// is valid
|
||||
//
|
||||
// usage:
|
||||
//
|
||||
// Cache.getTimestamp('table_name', function(err, timestamp) {
|
||||
// });
|
||||
//
|
||||
// Cache.setTimestamp('table_name', timestamp, function(err, result) {
|
||||
// });
|
||||
//
|
||||
//
|
||||
|
||||
var _ = require('underscore'),
|
||||
RedisPool = require('./redis_pool'),
|
||||
Step = require('step')
|
||||
|
||||
|
||||
function Cache(redis_opts) {
|
||||
|
||||
var redis_pool = new RedisPool(redis_opts);
|
||||
|
||||
var me = {}
|
||||
|
||||
var redis_options = {
|
||||
db: 0
|
||||
}
|
||||
|
||||
var redisCommand = function(fn, callback) {
|
||||
var redis_client;
|
||||
Step(
|
||||
function getRedisClient(){
|
||||
redis_pool.acquire(redis_options.db, this);
|
||||
},
|
||||
function getDataForTable(err, redis) {
|
||||
if (err) throw err;
|
||||
redis_client = redis;
|
||||
fn(redis_client, this);
|
||||
},
|
||||
function exit(err, data) {
|
||||
if (!_.isUndefined(redis_client))
|
||||
redis_pool.release(redis_options.db, redis_client);
|
||||
if (callback) {
|
||||
callback(err, data);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// get the timestamp for the table
|
||||
me.getTimestamp = function(database, table, callback) {
|
||||
redisCommand(function(redis, step) {
|
||||
redis.GET("cache:" + database + ":" + table + ":last_updated_at", step);
|
||||
}, function(err, t) {
|
||||
if(t != null)
|
||||
callback(err, parseFloat(t));
|
||||
else
|
||||
callback(err, t);
|
||||
});
|
||||
}
|
||||
|
||||
me.setTimestamp = function(database, table, timestamp, callback) {
|
||||
timestamp = timestamp || new Date().getTime();
|
||||
redisCommand(function(redis, step) {
|
||||
redis.SET("cache:" + database + ":" + table + ":last_updated_at", timestamp, step);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
return me;
|
||||
};
|
||||
|
||||
|
||||
module.exports = function(redis_opts) {
|
||||
return new Cache(redis_opts);
|
||||
}
|
||||
@@ -203,4 +203,4 @@ module.exports = function() {
|
||||
};
|
||||
|
||||
return me;
|
||||
}();
|
||||
}();
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
var _ = require('underscore')
|
||||
, Step = require('step')
|
||||
, Windshaft = require('windshaft')
|
||||
, Cache = require('./tile_cache');
|
||||
|
||||
var CartodbWindshaft = function(serverOptions) {
|
||||
|
||||
// set cache if requested
|
||||
if(serverOptions.lru_cache) {
|
||||
var lru_cache = Cache.LRUcache(serverOptions.lru_cache_size || 10000, serverOptions.redis);
|
||||
_.extend(serverOptions, {
|
||||
beforeTileRender: lru_cache.beforeTileRender,
|
||||
afterTileRender: lru_cache.afterTileRender,
|
||||
cacheStats: lru_cache.getStats,
|
||||
afterStateChange: lru_cache.afterStateChange
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// boot
|
||||
var ws = new Windshaft.Server(serverOptions);
|
||||
/**
|
||||
* Helper to allow access to the layer to be used in the maps infowindow popup.
|
||||
*/
|
||||
ws.get(serverOptions.base_url + '/infowindow', function(req, res){
|
||||
Step(
|
||||
function(){
|
||||
serverOptions.getInfowindow(req, this);
|
||||
},
|
||||
function(err, data){
|
||||
if (err){
|
||||
res.send(err.message, 400);
|
||||
} else {
|
||||
res.send({infowindow: data}, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to allow access to metadata to be used in embedded maps.
|
||||
*/
|
||||
ws.get(serverOptions.base_url + '/map_metadata', function(req, res){
|
||||
Step(
|
||||
function(){
|
||||
serverOptions.getMapMetadata(req, this);
|
||||
},
|
||||
function(err, data){
|
||||
if (err){
|
||||
res.send(err.message, 400);
|
||||
} else {
|
||||
res.send({map_metadata: data}, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* tile cache stats
|
||||
*/
|
||||
ws.get('/cache', function(req, res){
|
||||
if(serverOptions.cacheStats) {
|
||||
res.send(serverOptions.cacheStats());
|
||||
} else {
|
||||
res.send("Cache no enabled")
|
||||
}
|
||||
});
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
module.exports = CartodbWindshaft;
|
||||
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* see original file: https://github.com/rsms/js-lru
|
||||
*
|
||||
* A doubly linked list-based Least Recently Used (LRU) cache. Will keep most
|
||||
* recently used items while discarding least recently used items when its limit
|
||||
* is reached.
|
||||
*
|
||||
* Licensed under MIT. Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
|
||||
* See README.md for details.
|
||||
*
|
||||
* Illustration of the design:
|
||||
*
|
||||
* entry entry entry entry
|
||||
* ______ ______ ______ ______
|
||||
* | head |.newer => | |.newer => | |.newer => | tail |
|
||||
* | A | | B | | C | | D |
|
||||
* |______| <= older.|______| <= older.|______| <= older.|______|
|
||||
*
|
||||
* removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added
|
||||
*/
|
||||
function LRUCache (limit) {
|
||||
// Current size of the cache. (Read-only).
|
||||
this.size = 0;
|
||||
// Maximum number of items this cache can hold.
|
||||
this.limit = limit;
|
||||
this._keymap = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Put <value> into the cache associated with <key>. Returns the entry which was
|
||||
* removed to make room for the new entry. Otherwise undefined is returned
|
||||
* (i.e. if there was enough room already).
|
||||
*/
|
||||
LRUCache.prototype.put = function(key, value) {
|
||||
var entry = {key:key, value:value};
|
||||
// Note: No protection agains replacing, and thus orphan entries. By design.
|
||||
this._keymap[key] = entry;
|
||||
if (this.tail) {
|
||||
// link previous tail to the new tail (entry)
|
||||
this.tail.newer = entry;
|
||||
entry.older = this.tail;
|
||||
} else {
|
||||
// we're first in -- yay
|
||||
this.head = entry;
|
||||
}
|
||||
// add new entry to the end of the linked list -- it's now the freshest entry.
|
||||
this.tail = entry;
|
||||
if (this.size === this.limit) {
|
||||
// we hit the limit -- remove the head
|
||||
return this.shift();
|
||||
} else {
|
||||
// increase the size counter
|
||||
this.size++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the least recently used (oldest) entry from the cache. Returns the
|
||||
* removed entry or undefined if the cache was empty.
|
||||
*
|
||||
* If you need to perform any form of finalization of purged items, this is a
|
||||
* good place to do it. Simply override/replace this function:
|
||||
*
|
||||
* var c = new LRUCache(123);
|
||||
* c.shift = function() {
|
||||
* var entry = LRUCache.prototype.shift.call(this);
|
||||
* doSomethingWith(entry);
|
||||
* return entry;
|
||||
* }
|
||||
*/
|
||||
LRUCache.prototype.shift = function() {
|
||||
// todo: handle special case when limit == 1
|
||||
var entry = this.head;
|
||||
if (entry) {
|
||||
if (this.head.newer) {
|
||||
this.head = this.head.newer;
|
||||
this.head.older = undefined;
|
||||
} else {
|
||||
this.head = undefined;
|
||||
}
|
||||
// Remove last strong reference to <entry> and remove links from the purged
|
||||
// entry being returned:
|
||||
entry.newer = entry.older = undefined;
|
||||
// delete is slow, but we need to do this to avoid uncontrollable growth:
|
||||
delete this._keymap[entry.key];
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and register recent use of <key>. Returns the value associated with <key>
|
||||
* or undefined if not in cache.
|
||||
*/
|
||||
LRUCache.prototype.get = function(key, returnEntry) {
|
||||
// First, find our cache entry
|
||||
var entry = this._keymap[key];
|
||||
if (entry === undefined) return; // Not cached. Sorry.
|
||||
// As <key> was found in the cache, register it as being requested recently
|
||||
if (entry === this.tail) {
|
||||
// Already the most recenlty used entry, so no need to update the list
|
||||
return entry.value;
|
||||
}
|
||||
// HEAD--------------TAIL
|
||||
// <.older .newer>
|
||||
// <--- add direction --
|
||||
// A B C <D> E
|
||||
if (entry.newer) {
|
||||
if (entry === this.head)
|
||||
this.head = entry.newer;
|
||||
entry.newer.older = entry.older; // C <-- E.
|
||||
}
|
||||
if (entry.older)
|
||||
entry.older.newer = entry.newer; // C. --> E
|
||||
entry.newer = undefined; // D --x
|
||||
entry.older = this.tail; // D. --> E
|
||||
if (this.tail)
|
||||
this.tail.newer = entry; // E. <-- D
|
||||
this.tail = entry;
|
||||
return returnEntry ? entry : entry.value;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Following code is optional and can be removed without breaking the core
|
||||
// functionality.
|
||||
|
||||
/**
|
||||
* Check if <key> is in the cache without registering recent use. Feasible if
|
||||
* you do not want to chage the state of the cache, but only "peek" at it.
|
||||
* Returns the entry associated with <key> if found, or undefined if not found.
|
||||
*/
|
||||
LRUCache.prototype.find = function(key) {
|
||||
return this._keymap[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the value of entry with <key>. Returns the old value, or undefined if
|
||||
* entry was not in the cache.
|
||||
*/
|
||||
LRUCache.prototype.set = function(key, value) {
|
||||
var oldvalue, entry = this.get(key, true);
|
||||
if (entry) {
|
||||
oldvalue = entry.value;
|
||||
entry.value = value;
|
||||
} else {
|
||||
oldvalue = this.put(key, value);
|
||||
if (oldvalue) oldvalue = oldvalue.value;
|
||||
}
|
||||
return oldvalue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entry <key> from cache and return its value. Returns undefined if not
|
||||
* found.
|
||||
*/
|
||||
LRUCache.prototype.remove = function(key) {
|
||||
var entry = this._keymap[key];
|
||||
if (!entry) return;
|
||||
delete this._keymap[entry.key]; // need to do delete unfortunately
|
||||
if (entry.newer && entry.older) {
|
||||
// relink the older entry with the newer entry
|
||||
entry.older.newer = entry.newer;
|
||||
entry.newer.older = entry.older;
|
||||
} else if (entry.newer) {
|
||||
// remove the link to us
|
||||
entry.newer.older = undefined;
|
||||
// link the newer entry to head
|
||||
this.head = entry.newer;
|
||||
} else if (entry.older) {
|
||||
// remove the link to us
|
||||
entry.older.newer = undefined;
|
||||
// link the newer entry to head
|
||||
this.tail = entry.older;
|
||||
}
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
/** Removes all entries */
|
||||
LRUCache.prototype.removeAll = function() {
|
||||
// This should be safe, as we never expose strong refrences to the outside
|
||||
this.head = this.tail = undefined;
|
||||
this.size = 0;
|
||||
this._keymap = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array containing all keys of entries stored in the cache object, in
|
||||
* arbitrary order.
|
||||
*/
|
||||
if (typeof Object.keys === 'function') {
|
||||
LRUCache.prototype.keys = function() { return Object.keys(this._keymap); }
|
||||
} else {
|
||||
LRUCache.prototype.keys = function() {
|
||||
var keys = [];
|
||||
for (var k in this._keymap) keys.push(k);
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call `fun` for each entry. Starting with the newest entry if `desc` is a true
|
||||
* value, otherwise starts with the oldest (head) enrty and moves towards the
|
||||
* tail.
|
||||
*
|
||||
* `fun` is called with 3 arguments in the context `context`:
|
||||
* `fun.call(context, Object key, Object value, LRUCache self)`
|
||||
*/
|
||||
LRUCache.prototype.forEach = function(fun, context, desc) {
|
||||
if (context === true) { desc = true; context = undefined; }
|
||||
else if (typeof context !== 'object') context = this;
|
||||
if (desc) {
|
||||
var entry = this.tail;
|
||||
while (entry) {
|
||||
fun.call(context, entry.key, entry.value, this);
|
||||
entry = entry.older;
|
||||
}
|
||||
} else {
|
||||
var entry = this.head;
|
||||
while (entry) {
|
||||
fun.call(context, entry.key, entry.value, this);
|
||||
entry = entry.newer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a JSON (array) representation */
|
||||
LRUCache.prototype.toJSON = function() {
|
||||
var s = [], entry = this.head;
|
||||
while (entry) {
|
||||
s.push({key:entry.key.toJSON(), value:entry.value.toJSON()});
|
||||
entry = entry.newer;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/** Returns a String representation */
|
||||
LRUCache.prototype.toString = function() {
|
||||
var s = '', entry = this.head;
|
||||
while (entry) {
|
||||
s += String(entry.key)+':'+entry.value;
|
||||
if (entry = entry.newer)
|
||||
s += ' < ';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Export ourselves
|
||||
//if (typeof this === 'object') this.LRUCache = LRUCache;
|
||||
|
||||
module.exports = LRUCache;
|
||||
@@ -0,0 +1,111 @@
|
||||
// tile cache policies
|
||||
// it exports two cache types:
|
||||
// 'nocache' implements a pass-troguth cache
|
||||
// 'lru' implements a LRU cache
|
||||
|
||||
var LRUCache = require('./lru'),
|
||||
CacheValidator = require('./cache_validator')
|
||||
|
||||
module.exports.NoCache = function() {
|
||||
|
||||
var me = {}
|
||||
|
||||
me.beforeTileRender = function(req, res, callback) {
|
||||
callback(null);
|
||||
}
|
||||
|
||||
me.afterTileRender = function(req, res, tile, headers, callback) {
|
||||
callback(null, tile, headers);
|
||||
}
|
||||
|
||||
return me;
|
||||
|
||||
}
|
||||
|
||||
module.exports.LRUcache = function(max_items, redis_opts) {
|
||||
var cache_validator = CacheValidator(redis_opts);
|
||||
return GenericCache(new LRUCache(max_items), cache_validator);
|
||||
}
|
||||
|
||||
// implements a generic cache for tile
|
||||
// cache_policy should implement set and get methods and optionally getStats
|
||||
function GenericCache (cache_policy, cache_validator) {
|
||||
|
||||
var me = {
|
||||
cache: cache_policy,
|
||||
cache_validator: cache_validator,
|
||||
cache_hits: 0,
|
||||
cache_misses: 0,
|
||||
current_items: 0,
|
||||
cache_invalidated: 0,
|
||||
max_items: 0
|
||||
}
|
||||
|
||||
function cache_key(req) {
|
||||
return req.url;
|
||||
}
|
||||
|
||||
function update_items(n) {
|
||||
me.current_items = n;
|
||||
if(n > me.max_items) {
|
||||
me.max_items = n;
|
||||
}
|
||||
}
|
||||
|
||||
me.beforeTileRender = function(req, res, callback) {
|
||||
var key = cache_key(req);
|
||||
var tile = me.cache.get(key);
|
||||
if(tile) {
|
||||
// validate the cache
|
||||
me.cache_validator.getTimestamp(req.params.dbname, req.params.table, function(err, t) {
|
||||
if(t != null && tile.timestamp < t) {
|
||||
me.cache_misses++;
|
||||
me.cache_invalidated++;
|
||||
callback(null);
|
||||
} else {
|
||||
me.cache_hits++;
|
||||
res.header('X-Cache-hit', 'true');
|
||||
res.send(tile.tile, tile.headers, 200);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
me.cache_misses++;
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
me.afterTileRender = function(req, res, tile, headers, callback) {
|
||||
var timestamp = new Date().getTime()/1000.0;
|
||||
me.cache.put(cache_key(req), { tile: tile, headers: headers, timestamp: timestamp});
|
||||
update_items(me.cache.size || 0);
|
||||
callback(null, tile, headers);
|
||||
}
|
||||
|
||||
me.afterStateChange = function(req, data, callback) {
|
||||
console.log("invalidating", req.params.dbname,", ", req.params.table);
|
||||
me.cache_validator.setTimestamp(req.params.dbname, req.params.table, new Date().getTime()/1000.0, function(err, t) {
|
||||
callback(err, data);
|
||||
});
|
||||
}
|
||||
|
||||
me.getStats = function() {
|
||||
var total = me.cache_hits + me.cache_misses;
|
||||
var mem = 0;
|
||||
me.cache.forEach(function(key, value) {
|
||||
if(value.tile.length !== undefined) {
|
||||
mem += value.tile.length;
|
||||
}
|
||||
});
|
||||
return {
|
||||
cache_hits: me.cache_hits,
|
||||
cache_misses: me.cache_misses,
|
||||
current_items: me.current_items,
|
||||
max_items: me.max_items,
|
||||
memory: mem,
|
||||
memory_per_item: total ? mem/total: 0,
|
||||
ratio: total ? me.cache_hits/total: 0
|
||||
};
|
||||
}
|
||||
|
||||
return me;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
|
||||
var assert = require('assert')
|
||||
, tests = module.exports = {}
|
||||
, _ = require('underscore')
|
||||
, querystring = require('querystring')
|
||||
, fs = require('fs')
|
||||
, th = require(__dirname + '/../test_helper')
|
||||
, CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft')
|
||||
, http = require('http')
|
||||
, Step = require('step')
|
||||
, CacheValidator = require(__dirname + '/../../lib/cartodb/cache_validator')
|
||||
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
|
||||
var cached_server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
tests["first time a tile is request should not be cached"] = function() {
|
||||
assert.response(cached_server, {
|
||||
url: '/tiles/gadm4/6/31/24.png?geom_type=polygon',
|
||||
headers: {host: 'vizzuality.localhost.lan'},
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200
|
||||
|
||||
}, function(res) {
|
||||
assert.ok(res.header('X-Cache-hit') === undefined);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
tests["second time a tile is request should be cached"] = function() {
|
||||
|
||||
var cached_server2 = new CartodbWindshaft(serverOptions);
|
||||
var url= '/tiles/gadm4/6/31/24.png';
|
||||
assert.response(cached_server2, {
|
||||
url: url,
|
||||
headers: {host: 'vizzuality.localhost.lan'},
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200
|
||||
}, function(res) {
|
||||
assert.response(cached_server2, {
|
||||
url: url,
|
||||
headers: {host: 'vizzuality.localhost.lan'},
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200
|
||||
}, function(res) {
|
||||
assert.ok(res.header('X-Cache-hit') !== undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
tests["LRU tile should be removed"] = function() {
|
||||
|
||||
var urls = ['/tiles/gadm4/6/31/24.png',
|
||||
'/tiles/gadm4/6/31/25.png',
|
||||
'/tiles/gadm4/6/31/26.png',
|
||||
'/tiles/gadm4/6/31/27.png'];
|
||||
|
||||
//create another server to not take previos test stats into account
|
||||
var so = _.clone(serverOptions);
|
||||
_(so).extend({lru_cache: true, lru_cache_size: 3});
|
||||
|
||||
var _cached_server = new CartodbWindshaft(so);
|
||||
|
||||
function makeReq(url, callback) {
|
||||
assert.response(_cached_server, {
|
||||
url: url,
|
||||
headers: {host: 'vizzuality.localhost.lan'},
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200
|
||||
}, callback);
|
||||
}
|
||||
|
||||
Step(
|
||||
function() {
|
||||
makeReq(urls[0], this);
|
||||
},
|
||||
function() {
|
||||
makeReq(urls[1], this);
|
||||
},
|
||||
function() {
|
||||
makeReq(urls[2], this);
|
||||
},
|
||||
function() {
|
||||
makeReq(urls[3], this);
|
||||
}, function() {
|
||||
assert.response(_cached_server, {
|
||||
url: urls[0],
|
||||
headers: {host: 'vizzuality.localhost.lan'},
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200
|
||||
|
||||
}, function(res) {
|
||||
assert.ok(res.header('X-Cache-hit') === undefined);
|
||||
var st = so.cacheStats()
|
||||
assert.eql(st.cache_hits, 0);
|
||||
assert.eql(st.cache_misses, 5);
|
||||
assert.eql(st.current_items, 3);
|
||||
});
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
tests["cache should be invalidated"] = function() {
|
||||
|
||||
var url = '/tiles/gadm4/6/29/27.png';
|
||||
var cache = CacheValidator(global.environment.redis);
|
||||
assert.response(cached_server, {
|
||||
url: url,
|
||||
headers: {host: 'vizzuality.localhost.lan'},
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200
|
||||
}, function(res) {
|
||||
cache.setTimestamp('cartodb_dev_user_3_db', 'gadm4', (new Date().getTime()/1000.0)+100, function() {
|
||||
assert.response(cached_server, {
|
||||
url: url,
|
||||
headers: {host: 'vizzuality.localhost.lan'},
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200
|
||||
}, function(res) {
|
||||
assert.ok(res.header('X-Cache-hit') === undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
|
||||
var assert = require('assert'),
|
||||
tests = module.exports = {},
|
||||
th = require(__dirname + '/../test_helper'),
|
||||
CacheValidator = require(__dirname + '/../../lib/cartodb/cache_validator')
|
||||
|
||||
var cache = CacheValidator(global.environment.redis);
|
||||
|
||||
tests["should get the timestamp it sets"] = function() {
|
||||
var d = new Date().getTime();
|
||||
cache.setTimestamp('mydatabase', 'mytable', d, function() {
|
||||
cache.getTimestamp('mydatabase', 'mytable', function(err, t) {
|
||||
assert.eql(t, d);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
tests["should get null when timestamp is not set"] = function() {
|
||||
var d = new Date().getTime();
|
||||
cache.getTimestamp('mydatabase', 'mytable2', function(err, t) {
|
||||
assert.ok(t === null);
|
||||
})
|
||||
}
|
||||
@@ -4,9 +4,9 @@ var _ = require('underscore');
|
||||
var querystring = require('querystring');
|
||||
require(__dirname + '/../test_helper');
|
||||
|
||||
var Windshaft = require(__dirname + '/../../lib/windshaft');
|
||||
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
var server = new Windshaft.Server(serverOptions);
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
tests['true'] = function() {
|
||||
assert.ok(true);
|
||||
@@ -41,7 +41,7 @@ tests["post'ing no style returns 400 with errors"] = function(){
|
||||
method: 'POST'
|
||||
},{
|
||||
status: 400,
|
||||
body: '{"error":"must sent style information"}'
|
||||
body: '{"error":"must send style information"}'
|
||||
});
|
||||
};
|
||||
|
||||
@@ -149,8 +149,6 @@ tests["get'ing completed infowindow with callback returns information with callb
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
tests["get'ing a tile with default style should return an image"] = function(){
|
||||
assert.response(server, {
|
||||
headers: {host: 'vizzuality.localhost.lan'},
|
||||
@@ -162,6 +160,7 @@ tests["get'ing a tile with default style should return an image"] = function(){
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
tests["get'ing a json with default style should return an grid"] = function(){
|
||||
assert.response(server, {
|
||||
headers: {host: 'vizzuality.localhost.lan'},
|
||||
@@ -211,4 +210,4 @@ tests["get'ing a tile with default style and complex sql should return a constra
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'image/png' }
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user