2015-07-05 02:41:22 +08:00
|
|
|
var express = require('express');
|
2015-09-17 06:19:00 +08:00
|
|
|
var bodyParser = require('body-parser');
|
2015-07-05 02:41:22 +08:00
|
|
|
var RedisPool = require('redis-mpool');
|
2015-07-05 05:44:39 +08:00
|
|
|
var cartodbRedis = require('cartodb-redis');
|
2015-07-05 02:41:22 +08:00
|
|
|
var _ = require('underscore');
|
2016-05-05 18:17:51 +08:00
|
|
|
var debug = require('debug')('windshaft:cartodb');
|
2015-07-05 02:41:22 +08:00
|
|
|
|
2015-07-09 02:51:36 +08:00
|
|
|
var controller = require('./controllers');
|
2015-07-05 02:41:22 +08:00
|
|
|
|
|
|
|
var SurrogateKeysCache = require('./cache/surrogate_keys_cache');
|
|
|
|
var NamedMapsCacheEntry = require('./cache/model/named_maps_entry');
|
|
|
|
var VarnishHttpCacheBackend = require('./cache/backend/varnish_http');
|
|
|
|
var FastlyCacheBackend = require('./cache/backend/fastly');
|
|
|
|
|
2015-09-15 00:47:01 +08:00
|
|
|
var StatsClient = require('./stats/client');
|
|
|
|
var Profiler = require('./stats/profiler_proxy');
|
|
|
|
var RendererStatsReporter = require('./stats/reporter/renderer');
|
|
|
|
|
2015-07-05 02:41:22 +08:00
|
|
|
var windshaft = require('windshaft');
|
|
|
|
var mapnik = windshaft.mapnik;
|
|
|
|
|
|
|
|
var TemplateMaps = require('./backends/template_maps.js');
|
2016-02-04 17:26:31 +08:00
|
|
|
var OverviewsMetadataApi = require('./api/overviews_metadata_api');
|
2016-05-17 21:41:31 +08:00
|
|
|
var FilterStatsApi = require('./api/filter_stats_api');
|
2015-07-11 01:10:55 +08:00
|
|
|
var UserLimitsApi = require('./api/user_limits_api');
|
2015-07-13 21:05:03 +08:00
|
|
|
var AuthApi = require('./api/auth_api');
|
2015-07-14 19:40:41 +08:00
|
|
|
var LayergroupAffectedTablesCache = require('./cache/layergroup_affected_tables');
|
2015-07-15 02:53:06 +08:00
|
|
|
var NamedMapProviderCache = require('./cache/named_map_provider_cache');
|
2015-07-05 02:41:22 +08:00
|
|
|
var PgQueryRunner = require('./backends/pg_query_runner');
|
|
|
|
var PgConnection = require('./backends/pg_connection');
|
|
|
|
|
2016-04-14 23:09:07 +08:00
|
|
|
var AnalysisBackend = require('./backends/analysis');
|
|
|
|
|
2015-07-05 02:41:22 +08:00
|
|
|
var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
|
|
|
|
var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
|
|
|
|
|
2016-05-23 19:22:46 +08:00
|
|
|
var MapConfigOverviewsAdapter = require('./models/mapconfig/adapter/mapconfig-overviews-adapter');
|
2016-04-01 00:33:41 +08:00
|
|
|
|
2016-04-26 21:59:41 +08:00
|
|
|
var TurboCartoParser = require('./utils/style/turbo-carto-parser');
|
|
|
|
var TurboCartoAdapter = require('./utils/style/turbo-carto-adapter');
|
2015-07-05 02:41:22 +08:00
|
|
|
|
|
|
|
module.exports = function(serverOptions) {
|
2015-07-05 05:09:00 +08:00
|
|
|
// Make stats client globally accessible
|
2015-09-15 00:47:01 +08:00
|
|
|
global.statsClient = StatsClient.getInstance(serverOptions.statsd);
|
2015-07-05 02:41:22 +08:00
|
|
|
|
2015-07-07 18:36:39 +08:00
|
|
|
var redisPool = new RedisPool(_.defaults(global.environment.redis, {
|
2016-01-19 20:00:02 +08:00
|
|
|
name: 'windshaft-server',
|
2015-07-07 18:36:39 +08:00
|
|
|
unwatchOnRelease: false,
|
|
|
|
noReadyCheck: true
|
|
|
|
}));
|
2015-07-05 05:09:00 +08:00
|
|
|
|
|
|
|
redisPool.on('status', function(status) {
|
|
|
|
var keyPrefix = 'windshaft.redis-pool.' + status.name + '.db' + status.db + '.';
|
|
|
|
global.statsClient.gauge(keyPrefix + 'count', status.count);
|
|
|
|
global.statsClient.gauge(keyPrefix + 'unused', status.unused);
|
|
|
|
global.statsClient.gauge(keyPrefix + 'waiting', status.waiting);
|
|
|
|
});
|
2015-07-05 02:41:22 +08:00
|
|
|
|
2015-07-05 05:44:39 +08:00
|
|
|
var metadataBackend = cartodbRedis({pool: redisPool});
|
|
|
|
var pgConnection = new PgConnection(metadataBackend);
|
2015-07-05 02:41:22 +08:00
|
|
|
var pgQueryRunner = new PgQueryRunner(pgConnection);
|
2016-02-04 17:26:31 +08:00
|
|
|
var overviewsMetadataApi = new OverviewsMetadataApi(pgQueryRunner);
|
2016-05-17 21:41:31 +08:00
|
|
|
var filterStatsApi = new FilterStatsApi(pgQueryRunner);
|
2015-07-11 01:10:55 +08:00
|
|
|
var userLimitsApi = new UserLimitsApi(metadataBackend, {
|
|
|
|
limits: {
|
|
|
|
cacheOnTimeout: serverOptions.renderer.mapnik.limits.cacheOnTimeout || false,
|
|
|
|
render: serverOptions.renderer.mapnik.limits.render || 0
|
|
|
|
}
|
|
|
|
});
|
2015-07-05 02:41:22 +08:00
|
|
|
|
|
|
|
var templateMaps = new TemplateMaps(redisPool, {
|
|
|
|
max_user_templates: global.environment.maxUserTemplates
|
|
|
|
});
|
|
|
|
|
2015-09-16 01:01:34 +08:00
|
|
|
var surrogateKeysCache = new SurrogateKeysCache(surrogateKeysCacheBackends(serverOptions));
|
2015-07-05 02:41:22 +08:00
|
|
|
|
|
|
|
function invalidateNamedMap (owner, templateName) {
|
|
|
|
var startTime = Date.now();
|
|
|
|
surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
|
|
|
|
var logMessage = JSON.stringify({
|
|
|
|
username: owner,
|
|
|
|
type: 'named_map_invalidation',
|
|
|
|
elapsed: Date.now() - startTime,
|
|
|
|
error: !!err ? JSON.stringify(err.message) : undefined
|
|
|
|
});
|
|
|
|
if (err) {
|
2015-09-18 23:12:53 +08:00
|
|
|
global.logger.warn(logMessage);
|
2015-07-05 02:41:22 +08:00
|
|
|
} else {
|
2015-09-18 23:12:53 +08:00
|
|
|
global.logger.info(logMessage);
|
2015-07-05 02:41:22 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
['update', 'delete'].forEach(function(eventType) {
|
|
|
|
templateMaps.on(eventType, invalidateNamedMap);
|
|
|
|
});
|
|
|
|
|
|
|
|
serverOptions.grainstore.mapnik_version = mapnikVersion(serverOptions);
|
|
|
|
|
|
|
|
validateOptions(serverOptions);
|
|
|
|
|
|
|
|
bootstrapFonts(serverOptions);
|
|
|
|
|
|
|
|
// initialize express server
|
|
|
|
var app = bootstrap(serverOptions);
|
2015-07-05 03:33:31 +08:00
|
|
|
// Extend windshaft with all the elements of the options object
|
|
|
|
_.extend(app, serverOptions);
|
2015-07-05 02:41:22 +08:00
|
|
|
|
2015-07-05 05:18:09 +08:00
|
|
|
var mapStore = new windshaft.storage.MapStore({
|
2015-07-05 02:41:22 +08:00
|
|
|
pool: redisPool,
|
|
|
|
expire_time: serverOptions.grainstore.default_layergroup_ttl
|
|
|
|
});
|
|
|
|
|
|
|
|
var onTileErrorStrategy;
|
|
|
|
if (global.environment.enabledFeatures.onTileErrorStrategy !== false) {
|
|
|
|
onTileErrorStrategy = function onTileErrorStrategy$TimeoutTile(err, tile, headers, stats, format, callback) {
|
|
|
|
if (err && err.message === 'Render timed out' && format === 'png') {
|
|
|
|
return callback(null, timeoutErrorTile, { 'Content-Type': 'image/png' }, {});
|
|
|
|
} else {
|
|
|
|
return callback(err, tile, headers, stats);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
var rendererFactory = new windshaft.renderer.Factory({
|
|
|
|
onTileErrorStrategy: onTileErrorStrategy,
|
|
|
|
mapnik: {
|
|
|
|
redisPool: redisPool,
|
|
|
|
grainstore: serverOptions.grainstore,
|
2015-07-05 05:44:39 +08:00
|
|
|
mapnik: serverOptions.renderer.mapnik
|
2015-07-05 02:41:22 +08:00
|
|
|
},
|
|
|
|
http: serverOptions.renderer.http
|
|
|
|
});
|
|
|
|
|
|
|
|
// initialize render cache
|
|
|
|
var rendererCacheOpts = _.defaults(serverOptions.renderCache || {}, {
|
|
|
|
ttl: 60000, // 60 seconds TTL by default
|
2015-07-10 18:30:52 +08:00
|
|
|
statsInterval: 60000 // reports stats every milliseconds defined here
|
2015-07-05 02:41:22 +08:00
|
|
|
});
|
2015-07-10 18:30:52 +08:00
|
|
|
var rendererCache = new windshaft.cache.RendererCache(rendererFactory, rendererCacheOpts);
|
2015-09-15 00:47:01 +08:00
|
|
|
var rendererStatsReporter = new RendererStatsReporter(rendererCache, rendererCacheOpts.statsInterval);
|
|
|
|
rendererStatsReporter.start();
|
2015-07-09 02:51:36 +08:00
|
|
|
|
2015-10-07 01:46:52 +08:00
|
|
|
var attributesBackend = new windshaft.backend.Attributes();
|
2015-07-09 02:51:36 +08:00
|
|
|
var previewBackend = new windshaft.backend.Preview(rendererCache);
|
|
|
|
var tileBackend = new windshaft.backend.Tile(rendererCache);
|
|
|
|
var mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
|
|
|
|
var mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend);
|
2016-04-14 23:09:07 +08:00
|
|
|
var analysisBackend = new AnalysisBackend(serverOptions.analysis);
|
2015-07-05 02:41:22 +08:00
|
|
|
|
2015-07-14 19:40:41 +08:00
|
|
|
var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
|
|
|
|
app.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
|
|
|
|
|
2016-05-17 21:41:31 +08:00
|
|
|
var overviewsAdapter = new MapConfigOverviewsAdapter(overviewsMetadataApi, filterStatsApi);
|
2016-04-01 00:33:41 +08:00
|
|
|
|
2016-04-26 21:59:41 +08:00
|
|
|
var turboCartoParser = new TurboCartoParser(pgQueryRunner);
|
|
|
|
var turboCartoAdapter = new TurboCartoAdapter(turboCartoParser);
|
2016-03-12 01:28:14 +08:00
|
|
|
|
|
|
|
var namedMapProviderCache = new NamedMapProviderCache(
|
|
|
|
templateMaps,
|
|
|
|
pgConnection,
|
2016-04-07 22:18:48 +08:00
|
|
|
metadataBackend,
|
2016-05-13 22:56:52 +08:00
|
|
|
analysisBackend,
|
2016-03-12 01:28:14 +08:00
|
|
|
userLimitsApi,
|
2016-04-01 00:33:41 +08:00
|
|
|
overviewsAdapter,
|
2016-04-26 21:59:41 +08:00
|
|
|
turboCartoAdapter
|
2016-03-12 01:28:14 +08:00
|
|
|
);
|
|
|
|
|
2015-07-15 03:18:10 +08:00
|
|
|
['update', 'delete'].forEach(function(eventType) {
|
|
|
|
templateMaps.on(eventType, namedMapProviderCache.invalidate.bind(namedMapProviderCache));
|
|
|
|
});
|
2015-07-15 02:53:06 +08:00
|
|
|
|
2015-07-13 21:05:03 +08:00
|
|
|
var authApi = new AuthApi(pgConnection, metadataBackend, mapStore, templateMaps);
|
|
|
|
|
2015-07-08 21:50:59 +08:00
|
|
|
var TablesExtentApi = require('./api/tables_extent_api');
|
|
|
|
var tablesExtentApi = new TablesExtentApi(pgQueryRunner);
|
|
|
|
|
2015-07-09 02:51:36 +08:00
|
|
|
/*******************************************************************************************************************
|
|
|
|
* Routing
|
|
|
|
******************************************************************************************************************/
|
|
|
|
|
2015-07-10 17:24:32 +08:00
|
|
|
new controller.Layergroup(
|
2015-09-16 22:18:26 +08:00
|
|
|
authApi,
|
|
|
|
pgConnection,
|
2015-07-09 02:51:36 +08:00
|
|
|
mapStore,
|
|
|
|
tileBackend,
|
|
|
|
previewBackend,
|
2015-07-11 01:10:55 +08:00
|
|
|
attributesBackend,
|
2015-10-26 17:23:56 +08:00
|
|
|
new windshaft.backend.Widget(),
|
2015-07-14 19:40:41 +08:00
|
|
|
surrogateKeysCache,
|
2015-07-14 17:55:49 +08:00
|
|
|
userLimitsApi,
|
2016-04-14 23:09:07 +08:00
|
|
|
layergroupAffectedTablesCache,
|
|
|
|
analysisBackend
|
2015-07-09 02:51:36 +08:00
|
|
|
).register(app);
|
|
|
|
|
2015-07-10 17:24:32 +08:00
|
|
|
new controller.Map(
|
2015-09-16 22:18:26 +08:00
|
|
|
authApi,
|
2015-07-09 20:39:25 +08:00
|
|
|
pgConnection,
|
2015-07-09 02:51:36 +08:00
|
|
|
templateMaps,
|
|
|
|
mapBackend,
|
2015-07-10 17:24:32 +08:00
|
|
|
metadataBackend,
|
2015-07-11 01:10:55 +08:00
|
|
|
surrogateKeysCache,
|
2015-07-14 19:40:41 +08:00
|
|
|
userLimitsApi,
|
2016-03-08 21:34:57 +08:00
|
|
|
layergroupAffectedTablesCache,
|
2016-04-01 00:33:41 +08:00
|
|
|
overviewsAdapter,
|
2016-04-26 21:59:41 +08:00
|
|
|
turboCartoAdapter,
|
2016-04-14 23:09:07 +08:00
|
|
|
analysisBackend
|
2015-07-10 17:24:32 +08:00
|
|
|
).register(app);
|
|
|
|
|
|
|
|
new controller.NamedMaps(
|
2015-09-16 22:18:26 +08:00
|
|
|
authApi,
|
|
|
|
pgConnection,
|
2015-07-15 02:53:06 +08:00
|
|
|
namedMapProviderCache,
|
2015-07-09 19:37:00 +08:00
|
|
|
tileBackend,
|
2015-07-09 02:51:36 +08:00
|
|
|
previewBackend,
|
|
|
|
surrogateKeysCache,
|
2015-09-29 18:21:11 +08:00
|
|
|
tablesExtentApi,
|
|
|
|
metadataBackend
|
2015-07-09 02:51:36 +08:00
|
|
|
).register(app);
|
|
|
|
|
2016-03-12 01:28:14 +08:00
|
|
|
new controller.NamedMapsAdmin(authApi, pgConnection, templateMaps).register(app);
|
2015-07-09 02:51:36 +08:00
|
|
|
|
|
|
|
new controller.ServerInfo().register(app);
|
2015-07-05 02:41:22 +08:00
|
|
|
|
|
|
|
/*******************************************************************************************************************
|
|
|
|
* END Routing
|
|
|
|
******************************************************************************************************************/
|
|
|
|
|
|
|
|
return app;
|
|
|
|
};
|
|
|
|
|
|
|
|
function validateOptions(opts) {
|
2015-07-10 17:24:32 +08:00
|
|
|
if (!_.isString(opts.base_url) || !_.isString(opts.base_url_mapconfig) || !_.isString(opts.base_url_templated)) {
|
|
|
|
throw new Error("Must initialise server with: 'base_url'/'base_url_mapconfig'/'base_url_templated' URLs");
|
2015-07-05 02:41:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Be nice and warn if configured mapnik version is != instaled mapnik version
|
|
|
|
if (mapnik.versions.mapnik !== opts.grainstore.mapnik_version) {
|
2016-05-05 18:17:51 +08:00
|
|
|
debug('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
|
2015-07-05 02:41:22 +08:00
|
|
|
' != configured mapnik version (' + opts.grainstore.mapnik_version + ')');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function bootstrapFonts(opts) {
|
|
|
|
// Set carto renderer configuration for MMLStore
|
|
|
|
opts.grainstore.carto_env = opts.grainstore.carto_env || {};
|
|
|
|
var cenv = opts.grainstore.carto_env;
|
|
|
|
cenv.validation_data = cenv.validation_data || {};
|
|
|
|
if ( ! cenv.validation_data.fonts ) {
|
|
|
|
mapnik.register_system_fonts();
|
|
|
|
mapnik.register_default_fonts();
|
|
|
|
cenv.validation_data.fonts = _.keys(mapnik.fontFiles());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function bootstrap(opts) {
|
|
|
|
var app;
|
|
|
|
if (_.isObject(opts.https)) {
|
|
|
|
// use https if possible
|
|
|
|
app = express.createServer(opts.https);
|
|
|
|
} else {
|
|
|
|
// fall back to http by default
|
2015-09-17 06:19:00 +08:00
|
|
|
app = express();
|
2015-07-05 02:41:22 +08:00
|
|
|
}
|
|
|
|
app.enable('jsonp callback');
|
2015-09-17 06:19:00 +08:00
|
|
|
app.disable('x-powered-by');
|
2015-09-17 08:04:10 +08:00
|
|
|
app.disable('etag');
|
2015-09-17 06:19:00 +08:00
|
|
|
app.use(bodyParser.json());
|
2015-07-05 02:41:22 +08:00
|
|
|
|
2015-07-10 17:25:20 +08:00
|
|
|
app.use(function bootstrap$prepareRequestResponse(req, res, next) {
|
2015-07-05 02:41:22 +08:00
|
|
|
req.context = req.context || {};
|
2015-09-15 00:47:01 +08:00
|
|
|
req.profiler = new Profiler({
|
2015-07-05 02:41:22 +08:00
|
|
|
statsd_client: global.statsClient,
|
|
|
|
profile: opts.useProfiler
|
|
|
|
});
|
2015-09-16 01:28:02 +08:00
|
|
|
|
2015-09-17 06:19:00 +08:00
|
|
|
if (global.environment && global.environment.api_hostname) {
|
2015-09-17 08:03:09 +08:00
|
|
|
res.set('X-Served-By-Host', global.environment.api_hostname);
|
2015-09-17 06:19:00 +08:00
|
|
|
}
|
2015-09-16 01:28:02 +08:00
|
|
|
|
2015-07-05 02:41:22 +08:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
2015-09-16 01:28:02 +08:00
|
|
|
// temporary measure until we upgrade to newer version expressjs so we can check err.status
|
|
|
|
app.use(function(err, req, res, next) {
|
|
|
|
if (err) {
|
|
|
|
if (err.name === 'SyntaxError') {
|
2015-09-17 17:07:02 +08:00
|
|
|
res.status(400).json({ errors: [err.name + ': ' + err.message] });
|
2015-09-16 01:28:02 +08:00
|
|
|
} else {
|
|
|
|
next(err);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-07-05 02:41:22 +08:00
|
|
|
setupLogger(app, opts);
|
|
|
|
|
|
|
|
return app;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setupLogger(app, opts) {
|
2015-09-17 06:19:00 +08:00
|
|
|
if (global.log4js && opts.log_format) {
|
2015-07-05 02:41:22 +08:00
|
|
|
var loggerOpts = {
|
|
|
|
// Allowing for unbuffered logging is mainly
|
|
|
|
// used to avoid hanging during unit testing.
|
|
|
|
// TODO: provide an explicit teardown function instead,
|
|
|
|
// releasing any event handler or timer set by
|
|
|
|
// this component.
|
|
|
|
buffer: !opts.unbuffered_logging,
|
|
|
|
// optional log format
|
|
|
|
format: opts.log_format
|
|
|
|
};
|
2015-09-17 06:19:00 +08:00
|
|
|
app.use(global.log4js.connectLogger(global.log4js.getLogger(), _.defaults(loggerOpts, {level: 'info'})));
|
2015-07-05 02:41:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-16 01:01:34 +08:00
|
|
|
function surrogateKeysCacheBackends(serverOptions) {
|
|
|
|
var cacheBackends = [];
|
|
|
|
|
|
|
|
if (serverOptions.varnish_purge_enabled) {
|
|
|
|
cacheBackends.push(
|
|
|
|
new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (serverOptions.fastly &&
|
|
|
|
!!serverOptions.fastly.enabled && !!serverOptions.fastly.apiKey && !!serverOptions.fastly.serviceId) {
|
|
|
|
cacheBackends.push(
|
|
|
|
new FastlyCacheBackend(serverOptions.fastly.apiKey, serverOptions.fastly.serviceId)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return cacheBackends;
|
|
|
|
}
|
|
|
|
|
2015-07-05 02:41:22 +08:00
|
|
|
function mapnikVersion(opts) {
|
|
|
|
return opts.grainstore.mapnik_version || mapnik.versions.mapnik;
|
|
|
|
}
|