UNPKG

@sigiljs/sigil

Version:

TypeScript-first Node.js HTTP framework offering schema-driven routing, modifier-based middleware, plugin extensibility, and flexible response templating

162 lines (161 loc) 6 kB
import w from "node:fs"; import h from "node:path"; import R from "../requests/process-request-content.mjs"; import { NotFound as f } from "../responses/exceptions/exceptions-basic.mjs"; import p from "../responses/middleware-modification-request.mjs"; import u from "../responses/file-response.mjs"; import H from "../responses/raw-response.mjs"; import g from "../responses/redirect.mjs"; import $ from "../responses/sigil-response.mjs"; import "../utils/make-log.mjs"; import { jsonStringify as l } from "../utils/safe-json.mjs"; import y from "./misc/sigil-responses-list.mjs"; import M from "./sigil-plugin-system.mjs"; import m from "../responses/exceptions/exception.mjs"; class L extends M { /** * Binds the incoming message handler implementation. */ constructor(t) { super(t), this.incomingMessageHandler = this.incomingMessageHandler.bind(this), this.$incomingMessageHandlerImpl = this.$incomingMessageHandlerImpl.bind(this), this.$incomingMessageHandlerRef = this.$incomingMessageHandlerImpl; } /** * Entry point for handling HTTP requests. * Delegates to the internal handler reference. * * @param req incoming HTTP message. * @param res HTTP server response object. */ incomingMessageHandler(t, r) { return this.$incomingMessageHandlerRef?.(t, r); } /** * Sends a formatted response back to the client. * Applies the response template, triggers plugin hooks, * logs the request, and writes the HTTP response. * * @param request inbound HTTP message for context. * @param processedRequest processed request * @param response sigilResponse or Exception to send. * @param res HTTP server response object. * @param modification Modified options * @param at timestamp when request processing started. */ async $sendResponse(t, r, e, n, a, c) { a.code && (e.code = a.code); for (const s of this.$plugins.values()) { const i = await s.onBeforeResponseSent(r, e); if (i) { n.writeHead(i.code, Object.assign(i.headers.link, a.headers)).end(Buffer.isBuffer(i.content) || typeof i.content == "string" ? i.content : l(i.content, { throw: !0 })); return; } } const o = this.$responseTemplate(e), d = performance.now() - c; if (this.logger({ message: `${t.method} ${t.url} - ${e.code} - ${Math.round(d * 1e3) / 1e3}ms`, level: e.code > 499 ? "error" : "info", module: "requests", json: { method: t.method, path: t.url, code: e.code, time: d } }), o.code > 399 && this.$options.codeOnlyResponse?.includes(o.code)) { n.writeHead(o.code, a.headers).end(); return; } if (e instanceof H) { const s = typeof e.content == "string" || Buffer.isBuffer(e.content) ? e.content : l(e.content, { throw: !0 }); n.writeHead(e.code, Object.assign(e.headers.link, a.headers)).end(s); return; } if (e instanceof u) { n.writeHead(e.code, Object.assign(e.headers.link, a.headers)).end(e.content); return; } if (e instanceof m) { n.writeHead(e.code, Object.assign(o.headers, a.headers)).end(o.content), e.code > 499 && this.logger({ level: "error", module: "handler", message: (s) => ` ${s("-> ")}${s(`${e.name}: ${e.message}`)}`, json: { name: e.name, message: e.message } }); return; } if (e instanceof g) { n.writeHead(e.code, Object.assign(e.headers.link, a.headers)).end(); return; } n.writeHead(o.code, Object.assign(o.headers, a.headers)).end( typeof o.content == "string" ? o.content : l(o.content) ); } /** * Executes a registered route handler safely, converting errors to Exception. * * @param handler pathfinder handler function. * @param req client request object. * @returns handler's response or an Exception on error. */ async $executeHandler(t, r) { try { return await t(r); } catch (e) { return m.fromError(e); } } /** * Formats a raw handler response into a SigilResponse or Exception. * Handles file reading, redirections, and exception wrapping. * * @param response raw handler response. * @returns SigilResponse or Exception ready for sending. */ async $formatResponse(t) { if (t instanceof Error) return m.fromError(t); if (t instanceof g) return t; if (t instanceof u) try { const r = h.isAbsolute(t.content) ? t.content : h.resolve(t.content), e = t.headers.get("content-encoding"); return t.content = await w.promises.readFile(r, e ?? "utf-8"), t; } catch { return new f(`File at path ${t.content} not found`); } return t instanceof $ ? t : new $(t, 200); } /** * Internal implementation for handling incoming HTTP messages. * Parses the request, invokes middleware, routes lookup, and * delegates to handler execution and response sending. * * @param req incoming HTTP message. * @param res HTTP server response object. */ async $incomingMessageHandlerImpl(t, r) { const e = performance.now(), n = await R(t), a = new y(); if (!n) return this.$sendResponse(t, n, new f(), r, {}, e); for (const d of this.$plugins.values()) d.onRequestReceived(n); const c = { headers: {}, code: void 0 }; for (const d of this.$middlewares.values()) { const s = await d(n, a, c); if (s !== void 0) { const i = await this.$formatResponse(s); if (i instanceof p) c.headers = { ...c.headers, ...i.headers.link }, c.code = i.code; else return this.$sendResponse(t, n, i, r, {}, e); } } const o = this.$root.__$pathfinder.lookup(n.method, n.path); if (o) { const d = await this.$executeHandler( o.handler, n.createClientRequest(o.params) ), s = await this.$formatResponse(d); return this.$sendResponse(t, n, s, r, c, e); } this.$sendResponse(t, n, new f(), r, c, e); } } export { L as default };