Files
tsgl/src/server.js
Damian Krzeminski 6c23d95feb add --cors option to allow for optional CORS handling
we are using `cors` middleware with default options which works for most
applications, but does not allow for fine tuning (whitelisting origins
etc.)

this change keeps CORS handling as default to preserve compatibility but
also allows for specying `--no-cors` option which makes it possible to
handle CORS in an independent proxy (NGINX, another node app etc.)
2017-04-12 09:43:43 -07:00

376 lines
11 KiB
JavaScript

#!/usr/bin/env node
'use strict';
process.env.UV_THREADPOOL_SIZE =
Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
var fs = require('fs'),
path = require('path');
var base64url = require('base64url'),
clone = require('clone'),
cors = require('cors'),
express = require('express'),
handlebars = require('handlebars'),
mercator = new (require('@mapbox/sphericalmercator'))(),
morgan = require('morgan');
var packageJson = require('../package'),
serve_font = require('./serve_font'),
serve_rendered = null,
serve_style = require('./serve_style'),
serve_data = require('./serve_data'),
utils = require('./utils');
var isLight = packageJson.name.slice(-6) == '-light';
if (!isLight) {
// do not require `serve_rendered` in the light package
serve_rendered = require('./serve_rendered');
}
module.exports = function(opts, callback) {
console.log('Starting server');
var app = express().disable('x-powered-by'),
serving = {
styles: {},
rendered: {},
data: {},
fonts: {}
};
app.enable('trust proxy');
callback = callback || function() {};
if (process.env.NODE_ENV == 'production') {
app.use(morgan('tiny'));
} else if (process.env.NODE_ENV !== 'test') {
app.use(morgan('dev'));
}
var config = opts.config || null;
var configPath = null;
if (opts.configPath) {
configPath = path.resolve(opts.configPath);
try {
config = clone(require(configPath));
} catch (e) {
console.log('ERROR: Config file not found or invalid!');
console.log(' See README.md for instructions and sample data.');
process.exit(1);
}
}
if (!config) {
console.log('ERROR: No config file not specified!');
process.exit(1);
}
var options = config.options || {};
var paths = options.paths || {};
options.paths = paths;
paths.root = path.resolve(
configPath ? path.dirname(configPath) : process.cwd(),
paths.root || '');
paths.styles = path.resolve(paths.root, paths.styles || '');
paths.fonts = path.resolve(paths.root, paths.fonts || '');
paths.sprites = path.resolve(paths.root, paths.sprites || '');
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
var checkPath = function(type) {
if (!fs.existsSync(paths[type])) {
console.error('The specified path for "' + type + '" does not exist (' + paths[type] + ').');
process.exit(1);
}
};
checkPath('styles');
checkPath('fonts');
checkPath('sprites');
checkPath('mbtiles');
var data = clone(config.data || {});
if (opts.cors) {
app.use(cors());
}
Object.keys(config.styles || {}).forEach(function(id) {
var item = config.styles[id];
if (!item.style || item.style.length == 0) {
console.log('Missing "style" property for ' + id);
return;
}
if (item.serve_data !== false) {
app.use('/styles/', serve_style(options, serving.styles, item, id,
function(mbtiles, fromData) {
var dataItemId;
Object.keys(data).forEach(function(id) {
if (fromData) {
if (id == mbtiles) {
dataItemId = id;
}
} else {
if (data[id].mbtiles == mbtiles) {
dataItemId = id;
}
}
});
if (dataItemId) { // mbtiles exist in the data config
return dataItemId;
} else if (fromData) {
console.log('ERROR: data "' + mbtiles + '" not found!');
process.exit(1);
} else {
var id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
while (data[id]) id += '_';
data[id] = {
'mbtiles': mbtiles
};
return id;
}
}, function(font) {
serving.fonts[font] = true;
}));
}
if (item.serve_rendered !== false) {
if (serve_rendered) {
app.use('/styles/' + id + '/',
serve_rendered(options, serving.rendered, item, id,
function(mbtiles) {
var mbtilesFile;
Object.keys(data).forEach(function(id) {
if (id == mbtiles) {
mbtilesFile = data[id].mbtiles;
}
});
return mbtilesFile;
}));
} else {
item.serve_rendered = false;
}
}
});
app.use('/', serve_font(options, serving.fonts));
Object.keys(data).forEach(function(id) {
var item = data[id];
if (!item.mbtiles || item.mbtiles.length == 0) {
console.log('Missing "mbtiles" property for ' + id);
return;
}
app.use('/data/', serve_data(options, serving.data, item, id, serving.styles));
});
app.get('/styles.json', function(req, res, next) {
var result = [];
var query = req.query.key ? ('?key=' + req.query.key) : '';
Object.keys(serving.styles).forEach(function(id) {
var styleJSON = serving.styles[id];
result.push({
version: styleJSON.version,
name: styleJSON.name,
id: id,
url: req.protocol + '://' + req.headers.host +
'/styles/' + id + '.json' + query
});
});
res.send(result);
});
var addTileJSONs = function(arr, req, type) {
Object.keys(serving[type]).forEach(function(id) {
var info = clone(serving[type][id]);
var path = '';
if (type == 'rendered') {
path = 'styles/' + id + '/rendered';
} else {
path = type + '/' + id;
}
info.tiles = utils.getTileUrls(req, info.tiles, path, info.format, {
'pbf': options.pbfAlias
});
arr.push(info);
});
return arr;
};
app.get('/rendered.json', function(req, res, next) {
res.send(addTileJSONs([], req, 'rendered'));
});
app.get('/data.json', function(req, res, next) {
res.send(addTileJSONs([], req, 'data'));
});
app.get('/index.json', function(req, res, next) {
res.send(addTileJSONs(addTileJSONs([], req, 'rendered'), req, 'data'));
});
//------------------------------------
// serve web presentations
app.use('/', express.static(path.join(__dirname, '../public/resources')));
var templates = path.join(__dirname, '../public/templates');
var serveTemplate = function(urlPath, template, dataGetter) {
var templateFile = templates + '/' + template + '.tmpl';
if (template == 'index') {
if (options.frontPage === false) {
return;
} else if (options.frontPage &&
options.frontPage.constructor === String) {
templateFile = path.resolve(paths.root, options.frontPage);
}
}
fs.readFile(templateFile, function(err, content) {
if (err) {
console.error('Template not found:', err);
}
var compiled = handlebars.compile(content.toString());
app.use(urlPath, function(req, res, next) {
var data = {};
if (dataGetter) {
data = dataGetter(req);
if (!data) {
return res.status(404).send('Not found');
}
}
data['server_version'] = packageJson.name + ' v' + packageJson.version;
data['is_light'] = isLight;
data['key_query_part'] =
req.query.key ? 'key=' + req.query.key + '&' : '';
data['key_query'] = req.query.key ? '?key=' + req.query.key : '';
return res.status(200).send(compiled(data));
});
});
};
serveTemplate('/$', 'index', function(req) {
var styles = clone(config.styles || {});
Object.keys(styles).forEach(function(id) {
var style = styles[id];
style.name = (serving.styles[id] || serving.rendered[id] || {}).name;
style.serving_data = serving.styles[id];
style.serving_rendered = serving.rendered[id];
if (style.serving_rendered) {
var center = style.serving_rendered.center;
if (center) {
style.viewer_hash = '#' + center[2] + '/' +
center[1].toFixed(5) + '/' +
center[0].toFixed(5);
var centerPx = mercator.px([center[0], center[1]], center[2]);
style.thumbnail = center[2] + '/' +
Math.floor(centerPx[0] / 256) + '/' +
Math.floor(centerPx[1] / 256) + '.png';
}
var query = req.query.key ? ('?key=' + req.query.key) : '';
style.wmts_link = 'http://wmts.maptiler.com/' +
base64url('http://' + req.headers.host +
'/styles/' + id + '/rendered.json' + query) + '/wmts';
var tiles = utils.getTileUrls(
req, style.serving_rendered.tiles,
'styles/' + id + '/rendered', style.serving_rendered.format);
style.xyz_link = tiles[0];
}
});
var data = clone(serving.data || {});
Object.keys(data).forEach(function(id) {
var data_ = data[id];
var center = data_.center;
if (center) {
data_.viewer_hash = '#' + center[2] + '/' +
center[1].toFixed(5) + '/' +
center[0].toFixed(5);
}
data_.is_vector = data_.format == 'pbf';
if (!data_.is_vector) {
if (center) {
var centerPx = mercator.px([center[0], center[1]], center[2]);
data_.thumbnail = center[2] + '/' +
Math.floor(centerPx[0] / 256) + '/' +
Math.floor(centerPx[1] / 256) + '.' + data_.format;
}
var query = req.query.key ? ('?key=' + req.query.key) : '';
data_.wmts_link = 'http://wmts.maptiler.com/' +
base64url('http://' + req.headers.host +
'/data/' + id + '.json' + query) + '/wmts';
var tiles = utils.getTileUrls(
req, data_.tiles, 'data/' + id, data_.format, {
'pbf': options.pbfAlias
});
data_.xyz_link = tiles[0];
}
if (data_.filesize) {
var suffix = 'kB';
var size = parseInt(data_.filesize, 10) / 1024;
if (size > 1024) {
suffix = 'MB';
size /= 1024;
}
if (size > 1024) {
suffix = 'GB';
size /= 1024;
}
data_.formatted_filesize = size.toFixed(2) + ' ' + suffix;
}
});
return {
styles: Object.keys(styles).length ? styles : null,
data: Object.keys(data).length ? data : null
};
});
serveTemplate('/styles/:id/$', 'viewer', function(req) {
var id = req.params.id;
var style = clone((config.styles || {})[id]);
if (!style) {
return null;
}
style.id = id;
style.name = (serving.styles[id] || serving.rendered[id]).name;
style.serving_data = serving.styles[id];
style.serving_rendered = serving.rendered[id];
return style;
});
/*
app.use('/rendered/:id/$', function(req, res, next) {
return res.redirect(301, '/styles/' + req.params.id + '/');
});
*/
serveTemplate('/data/:id/$', 'data', function(req) {
var id = req.params.id;
var data = clone(serving.data[id]);
if (!data) {
return null;
}
data.id = id;
data.is_vector = data.format == 'pbf';
return data;
});
var server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() {
console.log('Listening at http://%s:%d/',
this.address().address, this.address().port);
return callback();
});
process.on('SIGINT', function() {
process.exit();
});
setTimeout(callback, 1000);
return {
app: app,
server: server
};
};