Handle SQL API errors by logging them and requesting NO cache

SQL api is used to determine the list of source tables affected
by a query. Before this commit, the X-Cache-Channel header set
on sql api error was an arbitrary 'table' string, now the header
is omitted, the error logged and Cache-Control and Pragma headers
are sent as an attempt to request no caching.

The code includes test for this mechanism.
This commit is contained in:
Sandro Santilli
2013-03-13 10:39:00 +01:00
parent 49aad435b9
commit e8cbc666e2
5 changed files with 121 additions and 26 deletions
+16 -9
View File
@@ -36,7 +36,7 @@ function generateCacheChannel(req, callback){
// use cache if present
if (!_.isNull(channelCache[sql_md5]) && !_.isUndefined(channelCache[sql_md5])) {
callback(channelCache[sql_md5]);
callback(null, channelCache[sql_md5]);
} else{
// strip out windshaft/mapnik inserted sql if present
var sql = req.params.sql.match(/^\((.*)\)\sas\scdbq$/);
@@ -54,21 +54,28 @@ function generateCacheChannel(req, callback){
}
// call sql api
request.get({url:sqlapi, qs:qs, json:true}, function(err, response, body){
if (!err && response.statusCode == 200) {
tableNames = body.rows[0].cdb_querytables.split(/^\{(.*)\}$/)[1];
} else {
//oops, no SQL API. Just cache using fallback 'table' key
tableNames = 'table';
request.get({url:sqlapi, qs:qs, json:true}, function(err, res, body){
var epref = 'could not detect source tables using SQL api at ' + sqlapi;
if (err){
var msg = err.message ? err.message : err;
callback(new Error(epref + ': ' + msg));
return;
}
if (res.statusCode != 200) {
var msg = res.body.error ? res.body.error : res.body;
callback(new Error(epref + ': ' + msg));
return;
}
var qtables = body.rows[0].cdb_querytables;
tableNames = qtables.split(/^\{(.*)\}$/)[1];
cacheChannel = buildCacheChannel(dbName,tableNames);
channelCache[sql_md5] = cacheChannel; // store for caching
callback(cacheChannel);
callback(null, cacheChannel);
});
}
} else {
cacheChannel = buildCacheChannel(dbName,tableNames);
callback(cacheChannel);
callback(null, cacheChannel);
}
}
+20 -10
View File
@@ -57,17 +57,27 @@ module.exports = function(){
// skip non-GET requests, or requests for which there's no response
if ( req.method != 'GET' || ! req.res ) { cb(null, null); return; }
var res = req.res;
var ttl = global.environment.varnish.ttl || 86400;
Cache.generateCacheChannel(req, function(channel){
res.header('X-Cache-Channel', channel);
var cache_policy = req.query.cache_policy;
if ( cache_policy == 'persist' ) {
res.header('Cache-Control', 'public,max-age=31536000'); // 1 year
var cache_policy = req.query.cache_policy;
if ( cache_policy == 'persist' ) {
res.header('Cache-Control', 'public,max-age=31536000'); // 1 year
} else {
var ttl = global.environment.varnish.ttl || 86400;
res.header('Last-Modified', new Date().toUTCString());
res.header('Cache-Control', 'no-cache,max-age='+ttl+',must-revalidate, public');
}
Cache.generateCacheChannel(req, function(err, channel){
if ( ! err ) {
res.header('X-Cache-Channel', channel);
cb(null, channel);
} else {
res.header('Last-Modified', new Date().toUTCString());
res.header('Cache-Control', 'no-cache,max-age='+ttl+',must-revalidate, public');
// avoid caching this result
// (temptative, what Varnish does is out of our control)
res.header('Cache-Control', 'no-cache,no-store,max-age=0,must-revalidate');
res.header('Pragma', 'no-cache');
console.log('ERROR generating cache channel: ' + ( err.message ? err.message : err ));
// TODO: evaluate if we should bubble up the error instead
cb(null, 'ERROR');
}
cb(null, channel); // add last-modified too ?
});
}
@@ -127,7 +137,7 @@ module.exports = function(){
if (!_.isNull(data))
_.extend(req.params, {geom_type: data});
that.addCacheChannel(req, function(err, chan) {
that.addCacheChannel(req, function(err) {
callback(err, req);
});
}