2018-10-23 23:45:42 +08:00
|
|
|
'use strict';
|
|
|
|
|
2018-04-07 00:20:33 +08:00
|
|
|
const layergroupToken = require('../middlewares/layergroup-token');
|
2018-04-17 22:07:47 +08:00
|
|
|
const coordinates = require('../middlewares/coordinates');
|
2018-04-07 00:20:33 +08:00
|
|
|
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
|
|
|
|
const credentials = require('../middlewares/credentials');
|
|
|
|
const dbConnSetup = require('../middlewares/db-conn-setup');
|
|
|
|
const authorize = require('../middlewares/authorize');
|
|
|
|
const rateLimit = require('../middlewares/rate-limit');
|
2018-03-24 04:20:37 +08:00
|
|
|
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
|
2018-04-09 22:18:30 +08:00
|
|
|
const createMapStoreMapConfigProvider = require('../middlewares/map-store-map-config-provider');
|
2018-04-07 00:20:33 +08:00
|
|
|
const cacheControlHeader = require('../middlewares/cache-control-header');
|
|
|
|
const cacheChannelHeader = require('../middlewares/cache-channel-header');
|
|
|
|
const surrogateKeyHeader = require('../middlewares/surrogate-key-header');
|
|
|
|
const lastModifiedHeader = require('../middlewares/last-modified-header');
|
|
|
|
const vectorError = require('../middlewares/vector-error');
|
2018-03-24 04:20:37 +08:00
|
|
|
|
|
|
|
const SUPPORTED_FORMATS = {
|
|
|
|
grid_json: true,
|
|
|
|
json_torque: true,
|
|
|
|
torque_json: true,
|
|
|
|
png: true,
|
|
|
|
png32: true,
|
|
|
|
mvt: true
|
|
|
|
};
|
|
|
|
|
2018-03-29 01:37:31 +08:00
|
|
|
module.exports = class TileLayergroupController {
|
2018-03-24 04:20:37 +08:00
|
|
|
constructor (
|
|
|
|
tileBackend,
|
|
|
|
pgConnection,
|
|
|
|
mapStore,
|
2018-04-10 16:16:07 +08:00
|
|
|
userLimitsBackend,
|
2018-03-24 04:20:37 +08:00
|
|
|
layergroupAffectedTablesCache,
|
2018-04-10 00:08:56 +08:00
|
|
|
authBackend,
|
2018-03-24 04:20:37 +08:00
|
|
|
surrogateKeysCache
|
|
|
|
) {
|
|
|
|
this.tileBackend = tileBackend;
|
|
|
|
this.pgConnection = pgConnection;
|
|
|
|
this.mapStore = mapStore;
|
2018-04-10 16:16:07 +08:00
|
|
|
this.userLimitsBackend = userLimitsBackend;
|
2018-03-24 04:20:37 +08:00
|
|
|
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
|
2018-04-10 00:08:56 +08:00
|
|
|
this.authBackend = authBackend;
|
2018-03-24 04:20:37 +08:00
|
|
|
this.surrogateKeysCache = surrogateKeysCache;
|
|
|
|
}
|
|
|
|
|
2019-10-04 18:22:23 +08:00
|
|
|
route (mapRouter) {
|
2018-05-11 22:37:02 +08:00
|
|
|
// REGEXP: doesn't match with `val`
|
2019-10-25 01:18:47 +08:00
|
|
|
const not = (val) => `(?!${val})([^\/]+?)`; // eslint-disable-line no-useless-escape
|
2018-04-05 01:15:51 +08:00
|
|
|
|
2018-05-11 22:37:02 +08:00
|
|
|
// Sadly the path that matches 1 also matches with 2 so we need to tell to express
|
|
|
|
// that performs only the middlewares of the first path that matches
|
|
|
|
// for that we use one array to group all paths.
|
2018-04-19 01:07:38 +08:00
|
|
|
mapRouter.get([
|
2019-10-22 01:07:24 +08:00
|
|
|
'/:token/:z/:x/:y@:scale_factor?x.:format', // 1
|
|
|
|
'/:token/:z/:x/:y.:format', // 2
|
2018-05-11 21:34:10 +08:00
|
|
|
`/:token${not('static')}/:layer/:z/:x/:y.(:format)`
|
|
|
|
], this.middlewares());
|
|
|
|
}
|
|
|
|
|
|
|
|
middlewares () {
|
|
|
|
return [
|
2018-03-24 04:20:37 +08:00
|
|
|
layergroupToken(),
|
2018-04-17 00:55:42 +08:00
|
|
|
coordinates(),
|
2018-03-24 04:20:37 +08:00
|
|
|
credentials(),
|
2018-04-10 00:08:56 +08:00
|
|
|
authorize(this.authBackend),
|
2018-03-24 04:20:37 +08:00
|
|
|
dbConnSetup(this.pgConnection),
|
2018-04-10 16:16:07 +08:00
|
|
|
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.TILE),
|
2018-03-24 04:20:37 +08:00
|
|
|
cleanUpQueryParams(),
|
|
|
|
createMapStoreMapConfigProvider(
|
|
|
|
this.mapStore,
|
2018-04-10 16:16:07 +08:00
|
|
|
this.userLimitsBackend,
|
2018-03-24 04:20:37 +08:00
|
|
|
this.pgConnection,
|
|
|
|
this.layergroupAffectedTablesCache
|
|
|
|
),
|
2018-04-19 01:07:38 +08:00
|
|
|
getTile(this.tileBackend),
|
2018-03-24 04:20:37 +08:00
|
|
|
cacheControlHeader(),
|
|
|
|
cacheChannelHeader(),
|
|
|
|
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
|
|
|
lastModifiedHeader(),
|
|
|
|
incrementSuccessMetrics(global.statsClient),
|
|
|
|
incrementErrorMetrics(global.statsClient),
|
|
|
|
tileError(),
|
2018-04-05 01:15:51 +08:00
|
|
|
vectorError()
|
2018-05-11 21:34:10 +08:00
|
|
|
];
|
2018-03-24 04:20:37 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function parseFormat (format = '') {
|
|
|
|
const prettyFormat = format.replace('.', '_');
|
|
|
|
return SUPPORTED_FORMATS[prettyFormat] ? prettyFormat : 'invalid';
|
|
|
|
}
|
|
|
|
|
2019-10-22 01:07:24 +08:00
|
|
|
function getStatusCode (tile, format) {
|
2018-03-24 04:20:37 +08:00
|
|
|
return tile.length === 0 && format === 'mvt' ? 204 : 200;
|
|
|
|
}
|
|
|
|
|
2018-04-19 01:07:38 +08:00
|
|
|
function getTile (tileBackend) {
|
2018-03-24 04:20:37 +08:00
|
|
|
return function getTileMiddleware (req, res, next) {
|
2018-04-19 01:07:38 +08:00
|
|
|
req.profiler.start(`windshaft.${req.params.layer ? 'maplayer_tile' : 'map_tile'}`);
|
2018-03-24 04:20:37 +08:00
|
|
|
|
|
|
|
const { mapConfigProvider } = res.locals;
|
|
|
|
const { token } = res.locals;
|
|
|
|
const { layer, z, x, y, format } = req.params;
|
|
|
|
|
|
|
|
const params = { token, layer, z, x, y, format };
|
|
|
|
|
|
|
|
tileBackend.getTile(mapConfigProvider, params, (err, tile, headers, stats = {}) => {
|
|
|
|
req.profiler.add(stats);
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
return next(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (headers) {
|
|
|
|
res.set(headers);
|
|
|
|
}
|
|
|
|
|
|
|
|
const formatStat = parseFormat(req.params.format);
|
|
|
|
|
|
|
|
res.statusCode = getStatusCode(tile, formatStat);
|
|
|
|
res.body = tile;
|
|
|
|
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function incrementSuccessMetrics (statsClient) {
|
|
|
|
return function incrementSuccessMetricsMiddleware (req, res, next) {
|
|
|
|
const formatStat = parseFormat(req.params.format);
|
|
|
|
|
|
|
|
statsClient.increment('windshaft.tiles.success');
|
|
|
|
statsClient.increment(`windshaft.tiles.${formatStat}.success`);
|
|
|
|
|
|
|
|
next();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function incrementErrorMetrics (statsClient) {
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function tileError () {
|
|
|
|
return function tileErrorMiddleware (err, req, res, next) {
|
|
|
|
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
|
2019-10-22 01:07:24 +08:00
|
|
|
let errMsg = err.message ? ('' + err.message) : ('' + err);
|
2018-03-24 04:20:37 +08:00
|
|
|
|
|
|
|
// Rewrite mapnik parsing errors to start with layer number
|
|
|
|
const matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
|
|
|
|
|
|
|
|
if (matches) {
|
|
|
|
errMsg = `style${matches[2]}: ${matches[1]}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
err.message = errMsg;
|
|
|
|
err.label = 'TILE RENDER';
|
|
|
|
|
|
|
|
next(err);
|
|
|
|
};
|
|
|
|
}
|