UNPKG

@versatiles/server

Version:
134 lines (133 loc) 6.21 kB
import { createServer } from 'http'; import { Layer } from './layer.js'; import { Response } from './response.js'; import { getFileContent } from './file.js'; import { logDebug, logImportant, logInfo } from './log.js'; const STATIC_DIRNAME = new URL('../../static', import.meta.url).pathname; export class Server { #options; #layer; #server; constructor(source, options) { if (source == null) throw Error('source not defined'); const port = options.port ?? 8080; const baseUrl = options.baseUrl ?? `http://localhost:${port}/`; const tilesUrl = urlJoin(options.tilesUrl ?? '/tiles/default/{z}/{x}/{y}'); const sprites = urlJoin(options.sprites ?? [{ id: 'basics', url: '/assets/sprites/basics/sprites' }]); const glyphs = urlJoin(options.glyphs ?? 'assets/glyphs/{fontstack}/{range}.pbf'); const compress = options.compress ?? true; const host = options.host ?? '0.0.0.0'; this.#options = { ...options, port, baseUrl, tilesUrl, sprites, glyphs, compress, host }; this.#layer = new Layer(source, this.#options); function urlJoin(url) { if (typeof url === 'string') { return new URL(url, baseUrl).href.replace(/%7B/g, '{').replace(/%7D/g, '}'); } if (Array.isArray(url)) { return url.map(({ id, url }) => ({ id, url: urlJoin(url) })); } throw Error('invalid url'); } } getUrl() { return this.#options.baseUrl ?? `http://localhost:${this.#options.port}/`; } async start() { const getTile = await this.#layer.getTileFunction(); const recompress = this.#options.compress ?? false; const server = createServer((req, res) => { void (async () => { const response = new Response(res); try { if (req.method !== 'GET') { logImportant(`Error 405: Method "${req.method}" not allowed`); response.sendError('Method not allowed', 405); return; } if (!(req.url ?? '')) { logImportant('Error 404: URL not found'); response.sendError('URL not found', 404); return; } // check request const acceptedEncoding = req.headers['accept-encoding'] ?? ''; const responseConfig = { acceptBr: acceptedEncoding.includes('br'), acceptGzip: acceptedEncoding.includes('gzip'), optimalCompression: recompress, }; const path = new URL(req.url ?? '', 'resolve://').pathname; logInfo('new request: ' + path); // check if tile request const match = /^\/tiles\/default\/([0-9]+)\/([0-9]+)\/([0-9]+).*/.exec(path); if (match) { const [_, z, x, y] = match; const coords = [parseInt(z, 10), parseInt(x, 10), parseInt(y, 10)]; const tileResponse = await getTile(...coords); if (!tileResponse) { logImportant('Error 404: tile not found: ' + path); response.sendError('tile not found: ' + path, 404); return; } logInfo('send tile: ' + coords.join('/')); await response.sendContent(tileResponse, responseConfig); return; } if (path == '/tiles/default/tiles.json') { return await response.sendJSONString(await this.#layer.getMetadata() ?? '', responseConfig); } if (path == '/tiles/default/style.json') { return await response.sendJSONString(await this.#layer.getStyle(), responseConfig); } if (path == '/tiles/index.json') { return await response.sendJSONString('["default"]', responseConfig); } // check if request for user defined static content if (this.#options.static != null) { const content = await getFileContent(this.#options.static, path); if (content != null) { logDebug('send user defined static file'); return await response.sendContent(content, responseConfig); } } // check if request for standard static content const content = await getFileContent(STATIC_DIRNAME, path); if (content != null) { logDebug('send standard static file'); return await response.sendContent(content, responseConfig); } // error 404 logImportant('Error 404: file not found: ' + path); response.sendError('file not found: ' + path, 404); return; } catch (err) { logImportant('Error 500: internal error: ' + String(err)); response.sendError(err, 500); return; } })(); }); this.#server = server; const { host, port } = this.#options; await new Promise(r => server.listen(port, host, () => { r(); })); logImportant(`listening on port ${port}`); } async stop() { if (this.#server === undefined) return; await new Promise((res, rej) => { logInfo('stop server'); this.#server?.close(err => { if (err) rej(err); else res(); }); }); this.#server = undefined; } }