import type {IncomingMessage, ServerResponse} from "node:http"; import {NodeRequest, sendNodeResponse} from "srvx/node"; import {staticMiddleware} from "srvx/static"; export type StaticFileServer = (request: IncomingMessage, response: ServerResponse) => void; const escapeHtml = (value: string): string => value.replace(/[&<>"']/g, (char) => `${char.charCodeAt(0)};`); /** Terminal `404` handler for requests no file matched, mirroring the response `finalhandler` used to produce. */ export function sendNotFound(request: IncomingMessage, response: ServerResponse): void { const method = request.method /* v8 ignore next */ ?? "GET"; const url = request.url /* v8 ignore next */ ?? "/"; const message = escapeHtml(`Cannot ${method} ${encodeURI(url)}`); const body = `\n\n
\n\n${message}\n\n\n`;
response.setHeader("Content-Security-Policy", "default-src 'none'");
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("Content-Type", "text/html; charset=utf-8");
response.setHeader("Content-Length", Buffer.byteLength(body));
response.writeHead(404);
response.end(body);
}
/**
* Serves `dir` on top of a plain `node:http` server, preferring the precompressed `.br`/`.gz` variant of a file when the client accepts it.
*
* Requests that match no file are answered by {@link sendNotFound}.
*/
export function createStaticFileServer(dir: string, logError: (message: string) => void): StaticFileServer {
// `compress: false` restricts serving to the precompressed variants shipped on disk, never compressing on the fly
const serveDir = staticMiddleware({dir, encodings: true, compress: false});
const handle = async (request: IncomingMessage, response: ServerResponse): Promise