UNPKG

@kronos-integration/service-http

Version:
121 lines (99 loc) 2.75 kB
import { compile } from "multi-path-matcher"; import { SendEndpoint } from "@kronos-integration/endpoint"; import { TEXT_PLAIN } from "./constants.mjs"; import { Server as HTTPServer } from "node:http"; /** * @typedef {Object} CTX * @property {ServerResponse} res * @property {ServerRequest} req * @property {Function} is * @property {Function} throw */ /** * Endpoint to link against a http route. * * The endpoint name may be in the form of '<METHOD>:<path>'. * Then <METHOD> will be used as http method * and <path> as the url path component. * @param {string} name endpoint name * @param {Object} owner owner of the endpoint * @param {Object} options * @param {string} options.path url path component defaults to endpoint name * @param {string} options.method http method defaults to GET */ export class HTTPEndpoint extends SendEndpoint { #path; #method; constructor(name, owner, options) { super(name, owner, options); const m = name.match(/^(?<method>\w+):(?<path>.*)/); let { method, path } = m ? m.groups : options; if (path !== undefined) { this.#path = path; } if (method !== undefined) { method = method.toUpperCase(); if (method !== "GET") { this.#method = method; } } } get method() { return this.#method || "GET"; } get path() { return this.#path || this.name; } get toStringAttributes() { return { ...super.toStringAttributes, method: "method", path: "path" }; } get jsonAttributes() { return [...super.jsonAttributes, "method", "path"]; } } /** * * @param {HTTPServer} httpService * @return {RequestListener} */ export function endpointRouter(httpService) { const routingEndpoints = compile( Object.values(httpService.endpoints).filter(e => e instanceof HTTPEndpoint) ); return async (req, res) => { let statusCode; const ctx = { req, res, is: mime => req.headers["content-type"] === mime, throw(code, message) { statusCode = code; throw new Error(message, { cause: code }); } }; const method = req.method; const path = req.url; for (const route of routingEndpoints) { const m = path.match(route.regex); if (m && route.method === method) { try { await route.send( ctx, Object.fromEntries(route.keys.map((k, i) => [k, m[i + 1]])) ); } catch (error) { httpService.error({ method, path, error }); res.writeHead(statusCode || 500, TEXT_PLAIN); res.end(error.message); } return; } } res.writeHead(404, TEXT_PLAIN); res.end(); }; }