Windshaft-cartodb/lib/cartodb/controllers/layergroup.js

661 lines
20 KiB
JavaScript
Raw Normal View History

2018-03-07 22:01:04 +08:00
const cors = require('../middleware/cors');
2018-03-16 23:28:50 +08:00
const user = require('../middleware/user');
2018-03-07 22:01:04 +08:00
const vectorError = require('../middleware/vector-error');
const locals = require('../middleware/locals');
const cleanUpQueryParams = require('../middleware/clean-up-query-params');
const layergroupToken = require('../middleware/layergroup-token');
const credentials = require('../middleware/credentials');
const dbConnSetup = require('../middleware/db-conn-setup');
const authorize = require('../middleware/authorize');
const rateLimit = require('../middleware/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const cacheChannelHeader = require('../middleware/cache-channel-header');
const surrogateKeyHeader = require('../middleware/surrogate-key-header');
const lastModifiedHeader = require('../middleware/last-modified-header');
2018-03-20 02:48:14 +08:00
const sendResponse = require('../middleware/send-response');
2018-03-07 22:01:04 +08:00
const DataviewBackend = require('../backends/dataview');
const AnalysisStatusBackend = require('../backends/analysis-status');
const MapStoreMapConfigProvider = require('../models/mapconfig/provider/map-store-provider');
2018-03-08 19:23:00 +08:00
const SUPPORTED_FORMATS = {
grid_json: true,
json_torque: true,
torque_json: true,
png: true,
png32: true,
mvt: true
};
/**
2018-03-07 21:54:09 +08:00
* @param {prepareContext} prepareContext
* @param {PgConnection} pgConnection
* @param {MapStore} mapStore
* @param {TileBackend} tileBackend
* @param {PreviewBackend} previewBackend
* @param {AttributesBackend} attributesBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsApi} userLimitsApi
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {AnalysisBackend} analysisBackend
* @constructor
*/
function LayergroupController(
pgConnection,
mapStore,
tileBackend,
previewBackend,
attributesBackend,
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache,
analysisBackend,
authApi
) {
2016-02-22 18:40:25 +08:00
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
this.attributesBackend = attributesBackend;
this.surrogateKeysCache = surrogateKeysCache;
2015-07-11 01:10:55 +08:00
this.userLimitsApi = userLimitsApi;
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
this.dataviewBackend = new DataviewBackend(analysisBackend);
this.analysisStatusBackend = new AnalysisStatusBackend();
this.authApi = authApi;
}
module.exports = LayergroupController;
2017-10-05 18:12:21 +08:00
LayergroupController.prototype.register = function(app) {
const { base_url_mapconfig: mapConfigBasePath } = app;
2018-03-07 19:05:53 +08:00
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/:token/:z/:x/:y@:scale_factor?x.:format`,
cors(),
cleanUpQueryParams(),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.TILE),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
2018-03-07 22:39:59 +08:00
getTile(this.tileBackend, 'map_tile'),
setCacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
incrementSuccessMetrics(global.statsClient),
sendResponse(),
incrementErrorMetrics(global.statsClient),
tileError(),
2017-11-07 18:07:38 +08:00
vectorError()
);
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/:token/:z/:x/:y.:format`,
2017-10-05 18:12:21 +08:00
cors(),
cleanUpQueryParams(),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.TILE),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
2018-03-07 22:39:59 +08:00
getTile(this.tileBackend, 'map_tile'),
setCacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
incrementSuccessMetrics(global.statsClient),
sendResponse(),
incrementErrorMetrics(global.statsClient),
tileError(),
2017-11-07 18:07:38 +08:00
vectorError()
);
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/:token/:layer/:z/:x/:y.(:format)`,
2018-03-07 22:56:16 +08:00
distinguishLayergroupFromStaticRoute(),
2017-10-05 18:12:21 +08:00
cors(),
cleanUpQueryParams(),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.TILE),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
2018-03-07 22:39:59 +08:00
getTile(this.tileBackend, 'maplayer_tile'),
setCacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
incrementSuccessMetrics(global.statsClient),
sendResponse(),
incrementErrorMetrics(global.statsClient),
tileError(),
2017-11-07 18:07:38 +08:00
vectorError()
);
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/:token/:layer/attributes/:fid`,
2017-10-05 18:12:21 +08:00
cors(),
cleanUpQueryParams(),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.ATTRIBUTES),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
2018-03-08 01:41:41 +08:00
getFeatureAttributes(this.attributesBackend),
setCacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
);
const forcedFormat = 'png';
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/static/center/:token/:z/:lat/:lng/:width/:height.:format`,
2017-10-05 18:12:21 +08:00
cors(),
cleanUpQueryParams(['layer']),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache,
forcedFormat
),
2018-03-08 01:51:43 +08:00
getPreviewImageByCenter(this.previewBackend),
setCacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
);
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format`,
2017-10-05 18:12:21 +08:00
cors(),
cleanUpQueryParams(['layer']),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache,
forcedFormat
),
2018-03-08 01:51:43 +08:00
getPreviewImageByBoundingBox(this.previewBackend),
setCacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
);
2015-10-07 01:47:44 +08:00
// Undocumented/non-supported API endpoint methods.
// Use at your own peril.
2018-03-07 19:09:41 +08:00
const allowedDataviewQueryParams = [
'filters', // json
'own_filter', // 0, 1
2017-12-12 18:54:09 +08:00
'no_filters', // 0, 1
'bbox', // w,s,e,n
'start', // number
'end', // number
'column_type', // string
'bins', // number
'aggregation', //string
2017-07-15 00:30:36 +08:00
'offset', // number
'q', // widgets search
'categories', // number
];
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/:token/dataview/:dataviewName`,
2017-10-05 18:12:21 +08:00
cors(),
cleanUpQueryParams(allowedDataviewQueryParams),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
getDataview(this.dataviewBackend),
setCacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
);
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/:token/:layer/widget/:dataviewName`,
2017-10-05 18:12:21 +08:00
cors(),
cleanUpQueryParams(allowedDataviewQueryParams),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
getDataview(this.dataviewBackend),
setCacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
);
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/:token/dataview/:dataviewName/search`,
2017-10-05 18:12:21 +08:00
cors(),
cleanUpQueryParams(allowedDataviewQueryParams),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
dataviewSearch(this.dataviewBackend),
setCacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
);
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/:token/:layer/widget/:dataviewName/search`,
2017-10-05 18:12:21 +08:00
cors(),
cleanUpQueryParams(allowedDataviewQueryParams),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
dataviewSearch(this.dataviewBackend),
setCacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
);
2016-04-12 00:49:56 +08:00
2017-10-05 18:12:21 +08:00
app.get(
`${mapConfigBasePath}/:token/analysis/node/:nodeId`,
2017-10-05 18:12:21 +08:00
cors(),
cleanUpQueryParams(),
locals(),
2018-03-16 23:28:50 +08:00
user(),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.ANALYSIS),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
analysisNodeStatus(this.analysisStatusBackend),
sendResponse()
);
2016-04-12 00:49:56 +08:00
};
2018-03-07 22:56:16 +08:00
function distinguishLayergroupFromStaticRoute () {
return function distinguishLayergroupFromStaticRouteMiddleware(req, res, next) {
2018-03-07 21:48:21 +08:00
if (req.params.token === 'static') {
return next('route');
}
2018-03-07 21:48:21 +08:00
next();
};
}
function analysisNodeStatus (analysisStatusBackend) {
return function analysisNodeStatusMiddleware(req, res, next) {
2018-03-07 19:46:18 +08:00
analysisStatusBackend.getNodeStatus(res.locals, (err, nodeStatus, stats = {}) => {
req.profiler.add(stats);
if (err) {
err.label = 'GET NODE STATUS';
return next(err);
}
res.set({
'Cache-Control': 'public,max-age=5',
'Last-Modified': new Date().toUTCString()
});
res.body = nodeStatus;
next();
});
2018-03-07 03:05:55 +08:00
};
}
function getRequestParams(locals) {
const params = Object.assign({}, locals);
delete params.mapConfigProvider;
delete params.allowedQueryParams;
return params;
}
function createMapStoreMapConfigProvider (
mapStore,
userLimitsApi,
pgConnection,
affectedTablesCache,
forcedFormat = null
) {
return function createMapStoreMapConfigProviderMiddleware (req, res, next) {
2018-03-06 00:44:04 +08:00
const { user } = res.locals;
const params = getRequestParams(res.locals);
if (forcedFormat) {
params.format = forcedFormat;
params.layer = params.layer || 'all';
}
res.locals.mapConfigProvider = new MapStoreMapConfigProvider(
mapStore,
user,
userLimitsApi,
pgConnection,
affectedTablesCache,
params
);
2018-03-06 00:44:04 +08:00
2018-03-12 20:29:40 +08:00
next();
2018-03-06 00:44:04 +08:00
};
}
2018-03-06 00:44:04 +08:00
function getDataview (dataviewBackend) {
2018-03-06 00:44:04 +08:00
return function getDataviewMiddleware (req, res, next) {
const { user, mapConfigProvider } = res.locals;
const params = getRequestParams(res.locals);
2018-03-06 00:44:04 +08:00
dataviewBackend.getDataview(mapConfigProvider, user, params, (err, dataview, stats = {}) => {
2018-03-07 19:46:18 +08:00
req.profiler.add(stats);
if (err) {
err.label = 'GET DATAVIEW';
2018-03-06 00:44:04 +08:00
return next(err);
}
2018-03-06 00:44:04 +08:00
res.body = dataview;
next();
2018-03-06 00:44:04 +08:00
});
2018-03-07 03:05:55 +08:00
};
}
function dataviewSearch (dataviewBackend) {
2018-03-07 22:05:36 +08:00
return function dataviewSearchMiddleware (req, res, next) {
2018-03-06 01:05:42 +08:00
const { user, dataviewName, mapConfigProvider } = res.locals;
const params = getRequestParams(res.locals);
dataviewBackend.search(mapConfigProvider, user, dataviewName, params, (err, searchResult, stats = {}) => {
2018-03-07 19:46:18 +08:00
req.profiler.add(stats);
if (err) {
err.label = 'GET DATAVIEW SEARCH';
2018-03-06 01:05:42 +08:00
return next(err);
}
res.body = searchResult;
next();
2018-03-06 01:05:42 +08:00
});
2018-03-07 03:05:55 +08:00
};
}
2018-03-08 01:41:41 +08:00
function getFeatureAttributes (attributesBackend) {
return function getFeatureAttributesMiddleware (req, res, next) {
2018-03-06 01:28:52 +08:00
req.profiler.start('windshaft.maplayer_attribute');
2018-03-06 01:28:52 +08:00
const { mapConfigProvider } = res.locals;
const params = getRequestParams(res.locals);
attributesBackend.getFeatureAttributes(mapConfigProvider, params, false, (err, tile, stats = {}) => {
2018-03-07 19:46:18 +08:00
req.profiler.add(stats);
if (err) {
err.label = 'GET ATTRIBUTES';
2018-03-06 01:28:52 +08:00
return next(err);
}
res.body = tile;
next();
2018-03-06 01:28:52 +08:00
});
2018-03-07 03:05:55 +08:00
};
}
2018-03-06 23:19:53 +08:00
function getStatusCode(tile, format){
return tile.length === 0 && format === 'mvt'? 204 : 200;
2018-03-06 23:19:53 +08:00
}
function parseFormat (format = '') {
2018-03-06 23:19:53 +08:00
const prettyFormat = format.replace('.', '_');
2018-03-08 19:27:49 +08:00
return SUPPORTED_FORMATS[prettyFormat] ? prettyFormat : 'invalid';
}
2018-03-07 22:39:59 +08:00
function getTile (tileBackend, profileLabel = 'tile') {
return function getTileMiddleware (req, res, next) {
req.profiler.start(`windshaft.${profileLabel}`);
2018-03-06 23:19:53 +08:00
const { mapConfigProvider } = res.locals;
const params = getRequestParams(res.locals);
2018-03-07 19:46:18 +08:00
tileBackend.getTile(mapConfigProvider, params, (err, tile, headers, stats = {}) => {
2018-03-06 23:19:53 +08:00
req.profiler.add(stats);
if (err) {
return next(err);
2018-03-06 23:19:53 +08:00
}
if (headers) {
res.set(headers);
}
const formatStat = parseFormat(req.params.format);
res.statusCode = getStatusCode(tile, formatStat);
res.body = tile;
next();
2018-03-06 23:19:53 +08:00
});
2018-03-07 03:05:55 +08:00
};
}
2018-03-08 01:51:43 +08:00
function getPreviewImageByCenter (previewBackend) {
return function getPreviewImageByCenterMiddleware (req, res, next) {
const width = +req.params.width;
const height = +req.params.height;
const zoom = +req.params.z;
const center = {
lng: +req.params.lng,
lat: +req.params.lat
};
const format = req.params.format === 'jpg' ? 'jpeg' : 'png';
2018-03-07 19:46:18 +08:00
const { mapConfigProvider: provider } = res.locals;
2018-03-07 19:46:18 +08:00
previewBackend.getImage(provider, format, width, height, zoom, center, (err, image, headers, stats = {}) => {
2018-03-08 19:23:43 +08:00
req.profiler.done(`render-${format}`);
2018-03-07 19:46:18 +08:00
req.profiler.add(stats);
if (err) {
err.label = 'STATIC_MAP';
return next(err);
}
if (headers) {
res.set(headers);
}
2018-03-08 19:35:54 +08:00
res.set('Content-Type', headers['Content-Type'] || `image/${format}`);
res.body = image;
next();
});
2018-03-07 03:05:55 +08:00
};
}
2018-03-08 01:51:43 +08:00
function getPreviewImageByBoundingBox (previewBackend) {
return function getPreviewImageByBoundingBoxMiddleware (req, res, next) {
const width = +req.params.width;
const height = +req.params.height;
const bounds = {
west: +req.params.west,
north: +req.params.north,
east: +req.params.east,
south: +req.params.south
};
const format = req.params.format === 'jpg' ? 'jpeg' : 'png';
2018-03-07 19:46:18 +08:00
const { mapConfigProvider: provider } = res.locals;
2018-03-07 19:46:18 +08:00
previewBackend.getImage(provider, format, width, height, bounds, (err, image, headers, stats = {}) => {
2018-03-08 19:35:54 +08:00
req.profiler.done(`render-${format}`);
2018-03-07 19:46:18 +08:00
req.profiler.add(stats);
if (err) {
err.label = 'STATIC_MAP';
return next(err);
}
if (headers) {
res.set(headers);
}
2018-03-08 19:35:54 +08:00
res.set('Content-Type', headers['Content-Type'] || `image/${format}`);
res.body = image;
next();
});
2018-03-07 03:05:55 +08:00
};
}
function setCacheControlHeader () {
return function setCacheControlHeaderMiddleware (req, res, next) {
res.set('Cache-Control', 'public,max-age=31536000');
2018-03-06 23:55:27 +08:00
next();
};
}
2018-03-06 23:55:27 +08:00
function incrementSuccessMetrics (statsClient) {
2018-03-07 21:53:13 +08:00
return function incrementSuccessMetricsMiddleware (req, res, next) {
const formatStat = parseFormat(req.params.format);
statsClient.increment('windshaft.tiles.success');
statsClient.increment(`windshaft.tiles.${formatStat}.success`);
next();
};
}
2018-03-07 21:53:13 +08:00
function incrementErrorMetrics (statsClient) {
2018-03-07 21:53:13 +08:00
return function incrementErrorMetricsMiddleware (err, req, res, next) {
const formatStat = parseFormat(req.params.format);
statsClient.increment('windshaft.tiles.error');
statsClient.increment(`windshaft.tiles.${formatStat}.error`);
next(err);
};
2018-03-07 22:39:59 +08:00
}
2018-03-07 21:53:13 +08:00
function tileError () {
2018-03-07 21:53:13 +08:00
return function tileErrorMiddleware (err, req, res, next) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
let errMsg = err.message ? ( '' + err.message ) : ( '' + err );
// Rewrite mapnik parsing errors to start with layer number
const matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
if (matches) {
2018-03-08 19:35:54 +08:00
errMsg = `style${matches[2]}: ${matches[1]}`;
2018-03-07 21:53:13 +08:00
}
err.message = errMsg;
err.label = 'TILE RENDER';
next(err);
};
}