@paroicms/server
Version:
The ParoiCMS server
168 lines • 6.4 kB
JavaScript
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