add verbose logging, improve headers

This commit is contained in:
acalcutt 2025-01-02 22:08:12 -05:00
parent 71335872bc
commit 0d72d5796a
6 changed files with 207 additions and 43 deletions

View file

@ -17,11 +17,7 @@ import {
isValidHttpUrl,
fetchTileData,
} from './utils.js';
import {
getPMtilesInfo,
getPMtilesTile,
openPMtiles,
} from './pmtiles_adapter.js';
import { getPMtilesInfo, openPMtiles } from './pmtiles_adapter.js';
import { gunzipP, gzipP } from './promises.js';
import { openMbTilesWrapper } from './mbtiles_wrapper.js';
@ -30,12 +26,28 @@ export const serve_data = {
* Initializes the serve_data module.
* @param {object} options Configuration options.
* @param {object} repo Repository object.
* @param {object} programOpts - An object containing the program options
* @returns {express.Application} The initialized Express application.
*/
init: function (options, repo) {
init: function (options, repo, programOpts) {
const { verbose } = programOpts;
const app = express().disable('x-powered-by');
/**
* Handles requests for tile data, responding with the tile image.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the tile.
* @param {string} req.params.z - Z coordinate of the tile.
* @param {string} req.params.x - X coordinate of the tile.
* @param {string} req.params.y - Y coordinate of the tile.
* @param {string} req.params.format - Format of the tile.
* @returns {Promise<void>}
*/
app.get('/:id/:z/:x/:y.:format', async (req, res) => {
if (verbose) {
console.log(req.params);
}
const item = repo[req.params.id];
if (!item) {
return res.sendStatus(404);
@ -88,7 +100,14 @@ export const serve_data = {
data = await gunzipP(data);
isGzipped = false;
}
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
data = options.dataDecoratorFunc(
req.params.id,
'data',
data,
z,
x,
y,
);
}
}
@ -112,7 +131,9 @@ export const serve_data = {
}
data = JSON.stringify(geojson);
}
delete headers['ETag']; // do not trust the tile ETag -- regenerate
if (headers) {
delete headers['ETag'];
}
headers['Content-Encoding'] = 'gzip';
res.set(headers);
@ -123,6 +144,16 @@ export const serve_data = {
return res.status(200).send(data);
});
/**
* Handles requests for elevation data.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the elevation data.
* @param {string} req.params.z - Z coordinate of the tile.
* @param {string} req.params.x - X coordinate of the tile (either integer or float).
* @param {string} req.params.y - Y coordinate of the tile (either integer or float).
* @returns {Promise<void>}
*/
app.get('/:id/elevation/:z/:x/:y', async (req, res, next) => {
try {
const item = repo?.[req.params.id];
@ -189,6 +220,7 @@ export const serve_data = {
const { minX, minY } = new SphericalMercator().xyz(tileCenter, z);
xy = [minX, minY];
}
const fetchTile = await fetchTileData(source, sourceType, z, x, y);
if (fetchTile == null) return res.status(204).send();
@ -259,6 +291,13 @@ export const serve_data = {
}
});
/**
* Handles requests for metadata for the tiles.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the data source.
* @returns {Promise<void>}
*/
app.get('/:id.json', (req, res) => {
const item = repo[req.params.id];
if (!item) {
@ -289,6 +328,9 @@ export const serve_data = {
* @param {object} params Parameters object.
* @param {string} id ID of the data source.
* @param {object} programOpts - An object containing the program options
* @param {string} programOpts.publicUrl Public URL for the data.
* @param {boolean} programOpts.verbose Whether verbose logging should be used.
* @param {Function} dataResolver Function to resolve data.
* @returns {Promise<void>}
*/
add: async function (options, repo, params, id, programOpts) {

View file

@ -8,9 +8,11 @@ import { getFontsPbf, listFonts } from './utils.js';
* Initializes and returns an Express app that serves font files.
* @param {object} options - Configuration options for the server.
* @param {object} allowedFonts - An object containing allowed fonts.
* @param {object} programOpts - An object containing the program options.
* @returns {Promise<express.Application>} - A promise that resolves to the Express app.
*/
export async function serve_font(options, allowedFonts) {
export async function serve_font(options, allowedFonts, programOpts) {
const { verbose } = programOpts;
const app = express().disable('x-powered-by');
const lastModified = new Date().toUTCString();
@ -19,7 +21,18 @@ export async function serve_font(options, allowedFonts) {
const existingFonts = {};
/**
* Handles requests for a font file.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.fontstack - Name of the font stack.
* @param {string} req.params.range - The range of the font (e.g. 0-255).
* @returns {Promise<void>}
*/
app.get('/fonts/:fontstack/:range.pbf', async (req, res) => {
if (verbose) {
console.log(req.params);
}
const fontstack = decodeURI(req.params.fontstack);
const range = req.params.range;
@ -41,6 +54,12 @@ export async function serve_font(options, allowedFonts) {
}
});
/**
* Handles requests for a list of all available fonts.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @returns {void}
*/
app.get('/fonts.json', (req, res) => {
res.header('Content-type', 'application/json');
return res.send(

View file

@ -3,7 +3,7 @@
'use strict';
export const serve_rendered = {
init: (options, repo) => {},
add: (options, repo, params, id, publicUrl, dataResolver) => {},
init: (options, repo, programOpts) => {},
add: (options, repo, params, id, programOpts, dataResolver) => {},
remove: (repo, id) => {},
};

View file

@ -267,7 +267,6 @@ function extractPathsFromQuery(query, transformer) {
}
return paths;
}
/**
* Parses marker options provided via query and sets corresponding attributes
* on marker object.
@ -626,6 +625,13 @@ const respondImage = async (
* @param {object} options - Configuration options for the server.
* @param {object} repo - The repository object holding style data.
* @param {object} req - Express request object.
* @param {string} req.params.id - The id of the style.
* @param {string} req.params.p1 - The tile size parameter, if available.
* @param {string} req.params.p2 - The z parameter.
* @param {string} req.params.p3 - The x parameter.
* @param {string} req.params.p4 - The y parameter.
* @param {string} req.params.scale - The scale parameter.
* @param {string} req.params.format - The format of the image.
* @param {object} res - Express response object.
* @param {Function} next - Express next middleware function.
* @param {number} maxScaleFactor - The maximum scale factor allowed.
@ -641,12 +647,12 @@ async function handleTileRequest(
) {
const {
id,
p1: tileSize,
p2: zParam,
p3: xParam,
p4: yParam,
scale: scaleParam,
format,
p1: tileSize,
} = req.params;
const item = repo[id];
if (!item) {
@ -694,6 +700,12 @@ async function handleTileRequest(
* @param {object} repo - The repository object holding style data.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.p2 - The raw or static parameter.
* @param {string} req.params.p3 - The staticType parameter.
* @param {string} req.params.p4 - The width parameter.
* @param {string} req.params.p5 - The height parameter.
* @param {string} req.params.scale - The scale parameter.
* @param {string} req.params.format - The format of the image.
* @param {Function} next - Express next middleware function.
* @param {number} maxScaleFactor - The maximum scale factor allowed.
* @returns {Promise<void>}
@ -708,32 +720,24 @@ async function handleStaticRequest(
) {
const {
id,
scale: scaleParam,
format,
p2: raw,
p3: type,
p3: staticType,
p4: width,
p5: height,
scale: scaleParam,
format,
} = req.params;
const item = repo[id];
const parsedWidth = parseInt(width) || 512;
const parsedHeight = parseInt(height) || 512;
const scale = parseScale(scaleParam, maxScaleFactor);
let isRaw = raw !== undefined;
let staticType = type;
if (!staticType) {
//workaround for type when raw is not set
staticType = raw;
isRaw = false;
}
let isRaw = raw === 'raw';
if (!item || !staticType || !format || !scale) {
return res.sendStatus(404);
}
const staticTypeMatch = staticType.match(staticTypeRegex);
console.log(staticTypeMatch);
if (staticTypeMatch.groups.lon) {
// Center Based Static Image
const z = parseFloat(staticTypeMatch.groups.zoom) || 0;
@ -879,18 +883,37 @@ export const serve_rendered = {
* Initializes the serve_rendered module.
* @param {object} options Configuration options.
* @param {object} repo Repository object.
* @param {object} programOpts - An object containing the program options.
* @returns {Promise<express.Application>} A promise that resolves to the Express app.
*/
init: async function (options, repo) {
init: async function (options, repo, programOpts) {
const { verbose } = programOpts;
maxScaleFactor = Math.min(Math.floor(options.maxScaleFactor || 3), 9);
const app = express().disable('x-powered-by');
/**
* Handles requests for tile images.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - The id of the style.
* @param {string} req.params.p1 - The tile size or static parameter, if available
* @param {string} req.params.p2 - The z, static, or raw parameter.
* @param {string} req.params.p3 - The x or staticType parameter.
* @param {string} req.params.p4 - The y or width parameter.
* @param {string} req.params.p5 - The height parameter.
* @param {string} req.params.scale - The scale parameter.
* @param {string} req.params.format - The format of the image.
* @returns {Promise<void>}
*/
app.get(
`/:id{/:p1}/:p2/:p3/:p4{x:p5}{@:scale}{.:format}`,
async (req, res, next) => {
try {
const { p2 } = req.params;
if (p2 === 'static') {
if (verbose) {
console.log(req.params);
}
const { p1, p2 } = req.params;
if ((!p1 && p2 === 'static') || (p1 === 'static' && p2 === 'raw')) {
// Route to static if p2 is static
if (options.serveStaticMaps !== false) {
return handleStaticRequest(
@ -920,6 +943,14 @@ export const serve_rendered = {
},
);
/**
* Handles requests for tile json endpoint.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - The id of the tilejson
* @param {string} [req.params.tileSize] - The size of the tile, if specified.
* @returns {void}
*/
app.get('{/:tileSize}/:id.json', (req, res, next) => {
const item = repo[req.params.id];
if (!item) {

View file

@ -29,9 +29,11 @@ export const serve_style = {
* Initializes the serve_style module.
* @param {object} options Configuration options.
* @param {object} repo Repository object.
* @param {object} programOpts - An object containing the program options.
* @returns {express.Application} The initialized Express application.
*/
init: function (options, repo) {
init: function (options, repo, programOpts) {
const { verbose } = programOpts;
const app = express().disable('x-powered-by');
app.get('/:id/style.json', (req, res, next) => {
@ -64,6 +66,9 @@ export const serve_style = {
});
app.get(`/:id/sprite{/:spriteID}{@:scale}{.:format}`, (req, res, next) => {
if (verbose) {
console.log(req.params);
}
const { spriteID = 'default', id, format } = req.params;
const spriteScale = allowedSpriteScales(req.params.scale);

View file

@ -167,12 +167,12 @@ async function start(opts) {
app.use(cors());
}
app.use('/data/', serve_data.init(options, serving.data));
app.use('/data/', serve_data.init(options, serving.data, opts));
app.use('/files/', express.static(paths.files));
app.use('/styles/', serve_style.init(options, serving.styles));
app.use('/styles/', serve_style.init(options, serving.styles, opts));
if (!isLight) {
startupPromises.push(
serve_rendered.init(options, serving.rendered).then((sub) => {
serve_rendered.init(options, serving.rendered, opts).then((sub) => {
app.use('/styles/', sub);
}),
);
@ -288,7 +288,7 @@ async function start(opts) {
addStyle(id, item, true, true);
}
startupPromises.push(
serve_font(options, serving.fonts).then((sub) => {
serve_font(options, serving.fonts, opts).then((sub) => {
app.use('/', sub);
}),
);
@ -342,6 +342,13 @@ async function start(opts) {
}
});
}
/**
* Handles requests for a list of available styles.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} [req.query.key] - Optional API key.
* @returns {void}
*/
app.get('/styles.json', (req, res, next) => {
const result = [];
const query = req.query.key
@ -395,15 +402,35 @@ async function start(opts) {
return arr;
}
/**
* Handles requests for a rendered tilejson endpoint.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.tileSize - Optional tile size parameter.
* @returns {void}
*/
app.get('{/:tileSize}/rendered.json', (req, res, next) => {
const tileSize = allowedTileSizes(req.params['tileSize']);
res.send(addTileJSONs([], req, 'rendered', parseInt(tileSize, 10)));
});
/**
* Handles requests for a data tilejson endpoint.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @returns {void}
*/
app.get('/data.json', (req, res) => {
res.send(addTileJSONs([], req, 'data', undefined));
});
/**
* Handles requests for a combined rendered and data tilejson endpoint.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.tileSize - Optional tile size parameter.
* @returns {void}
*/
app.get('{/:tileSize}/index.json', (req, res, next) => {
const tileSize = allowedTileSizes(req.params['tileSize']);
res.send(
@ -421,6 +448,7 @@ async function start(opts) {
app.use('/', express.static(path.join(__dirname, '../public/resources')));
const templates = path.join(__dirname, '../public/templates');
/**
* Serves a Handlebars template.
* @param {string} urlPath - The URL path to serve the template at
@ -477,6 +505,12 @@ async function start(opts) {
}
}
/**
* Handles requests for the index page, providing a list of available styles and data.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @returns {void}
*/
serveTemplate('/', 'index', (req) => {
let styles = {};
for (const id of Object.keys(serving.styles || {})) {
@ -489,11 +523,15 @@ async function start(opts) {
if (style.serving_rendered) {
const { center } = style.serving_rendered.tileJSON;
if (center) {
style.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
style.viewer_hash = `#${center[2]}/${center[1].toFixed(
5,
)}/${center[0].toFixed(5)}`;
const centerPx = mercator.px([center[0], center[1]], center[2]);
// Set thumbnail default size to be 256px x 256px
style.thumbnail = `${Math.floor(center[2])}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.png`;
style.thumbnail = `${Math.floor(center[2])}/${Math.floor(
centerPx[0] / 256,
)}/${Math.floor(centerPx[1] / 256)}.png`;
}
const tileSize = 512;
@ -549,7 +587,9 @@ async function start(opts) {
}
if (center) {
const centerPx = mercator.px([center[0], center[1]], center[2]);
data.thumbnail = `${Math.floor(center[2])}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`;
data.thumbnail = `${Math.floor(center[2])}/${Math.floor(
centerPx[0] / 256,
)}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`;
}
}
@ -574,6 +614,13 @@ async function start(opts) {
};
});
/**
* Handles requests for a map viewer template for a specific style.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the style.
* @returns {void}
*/
serveTemplate('/styles/:id/', 'viewer', (req) => {
const { id } = req.params;
const style = clone(((serving.styles || {})[id] || {}).styleJSON);
@ -590,6 +637,13 @@ async function start(opts) {
};
});
/**
* Handles requests for a Web Map Tile Service (WMTS) XML template.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the style.
* @returns {void}
*/
serveTemplate('/styles/:id/wmts.xml', 'wmts', (req) => {
const { id } = req.params;
const wmts = clone((serving.styles || {})[id]);
@ -621,6 +675,14 @@ async function start(opts) {
};
});
/**
* Handles requests for a data view template for a specific data source.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @param {string} req.params.id - ID of the data source.
* @param {string} [req.params.view] - Optional view type.
* @returns {void}
*/
serveTemplate('/data{/:view}/:id/', 'data', (req) => {
const { id, view } = req.params;
const data = serving.data[id];
@ -649,6 +711,12 @@ async function start(opts) {
startupComplete = true;
});
/**
* Handles requests to see the health of the server.
* @param {object} req - Express request object.
* @param {object} res - Express response object.
* @returns {void}
*/
app.get('/health', (req, res) => {
if (startupComplete) {
return res.status(200).send('OK');
@ -678,7 +746,6 @@ async function start(opts) {
startupPromise,
};
}
/**
* Stop the server gracefully
* @param {string} signal Name of the received signal