UNPKG

@paroicms/server

Version:
168 lines 6.4 kB
import { ApiError } from "@paroicms/public-server-lib"; import { Stream } from "node:stream"; import { platformLogger } from "../context.js"; import { generateRequestId, queueRequest, trackRequest, untrackRequest, } from "../request-tracking/request-state.js"; import { getSiteContext } from "../site-context/site-context.js"; import { getPaSiteContext, serve404SimpleHtml, serve500Html, serve503ServiceUnavailable, } from "./http-helpers.js"; import { toPaHttpContext } from "./pa-http-context.js"; function isMigrationModeAllowedPath(path) { if (path === "/api/v1/login") return true; if (path === "/adm" || path.startsWith("/adm/")) return true; return false; } export async function loadPaSiteContextMiddleware(req, res, next) { try { const siteContext = await getSiteContext(req.hostname, { returnsUndef: true }); if (!siteContext) { platformLogger.debug(`[404] Site not found: ${req.protocol}://${req.hostname}${req.originalUrl}`); serve404SimpleHtml(toPaHttpContext(req, res)); return; } req.paSiteContext = siteContext; if (siteContext.status === "migration") { if (isMigrationModeAllowedPath(req.path)) { next(); return; } serve503ServiceUnavailable(toPaHttpContext(req, res), "Site requires migration"); return; } const requestState = siteContext.requestTracker.state; if (requestState === "gracefulShutdown") { serve503ServiceUnavailable(toPaHttpContext(req, res), "Site shutting down"); return; } if (requestState === "backupMode") { try { await queueRequest(siteContext, 60_000); } catch { serve503ServiceUnavailable(toPaHttpContext(req, res), "Request timeout during backup"); return; } } const requestId = generateRequestId(); trackRequest(siteContext, requestId); const cleanup = () => untrackRequest(siteContext, requestId); res.on("finish", cleanup); res.on("close", cleanup); res.on("error", cleanup); next(); } catch (error) { if (error instanceof Error && error.message === "Service shutting down") { serve503ServiceUnavailable(toPaHttpContext(req, res), "Service shutting down"); return; } handleErrorSafely(req, res, error, undefined); } } export function wrapRouteController(handler, options = {}) { return async (req, res) => { const httpContext = toPaHttpContext(req, res); const siteContext = getPaSiteContext(req); if (siteContext.status === "migration" && !options.allowMigration) { serve503ServiceUnavailable(httpContext, "Site not ready"); return; } try { let result = await handler(siteContext, httpContext, req.params); if (result instanceof Stream) { res.setHeader("Content-Type", "application/octet-stream"); result.on("error", (error) => { handleErrorSafely(req, res, error, undefined); }); result.pipe(res); return; } if (!httpContext.res.headersSent) { result ??= ""; if (typeof result === "object" || Array.isArray(result)) { res.status(200).json(result); } else { res.status(200).send(String(result)); } } else if (result) { (req.paSiteContext?.logger ?? platformLogger).error(`[${req.url}]`, "Cannot send a http response:", result); } } catch (error) { handleErrorSafely(req, res, error, httpContext); } }; } export function reqHandlerToExpressMiddleware(handler, options = {}) { return async (req, res, next) => { const httpContext = toPaHttpContext(req, res); const siteContext = getPaSiteContext(req); if (siteContext.status === "migration" && !options.allowMigration) { next(); return; } try { const done = await handler(siteContext, httpContext); if (!done) { next(); } } catch (error) { handleErrorSafely(req, res, error, httpContext); } }; } export function mergeReqHandlers(handlers) { return async (siteContext, httpContext) => { for (const handler of handlers) { if (await handler(siteContext, httpContext)) return true; } return false; }; } export function wrapExpressRoute(handler) { return async (req, res) => { try { let result = await handler(req, res); if (result instanceof Stream) { res.setHeader("Content-Type", "application/octet-stream"); result.on("error", (error) => { handleErrorSafely(req, res, error, undefined); }); result.pipe(res); return; } if (!res.headersSent) { result ??= ""; if (typeof result === "object" || Array.isArray(result)) { res.status(200).json(result); } else { res.status(200).send(String(result)); } } else if (result) { (req.paSiteContext?.logger ?? platformLogger).error(`[${req.url}]`, "Cannot send a http response:", result); } } catch (error) { handleErrorSafely(req, res, error, undefined); } }; } function handleErrorSafely(req, res, error, httpContext) { (req.paSiteContext?.logger ?? platformLogger).error(`[${req.url}]`, error); if (!res.headersSent) { if (error instanceof ApiError) { const message = error.status === 500 ? "Internal Server Error" : error.message; res.status(error.status).json({ message }); } else { serve500Html(httpContext ?? toPaHttpContext(req, res)); } } } //# sourceMappingURL=route-handler-wrapper.js.map