import { createReadStream } from "node:fs"; import { stat } from "node:fs/promises"; import { extname, resolve } from "node:path"; import type { IncomingMessage, ServerResponse } from "node:http"; const CONTENT_TYPES: Record = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".svg": "image/svg+xml", }; export function sendJson(response: ServerResponse, statusCode: number, body: unknown): void { const payload = JSON.stringify(body); response.statusCode = statusCode; response.setHeader("Content-Type", "application/json; charset=utf-8"); response.end(payload); } export function sendText(response: ServerResponse, statusCode: number, body: string): void { response.statusCode = statusCode; response.setHeader("Content-Type", "text/plain; charset=utf-8"); response.end(body); } export async function parseJsonBody(request: IncomingMessage): Promise { const chunks: Buffer[] = []; await new Promise((resolveBody, rejectBody) => { request.on("data", (chunk: Buffer) => { chunks.push(chunk); }); request.on("end", () => resolveBody()); request.on("error", rejectBody); }); const body = Buffer.concat(chunks).toString("utf8").trim(); if (!body) { throw new Error("Request body is required."); } return JSON.parse(body) as T; } export function methodNotAllowed(response: ServerResponse): void { sendJson(response, 405, { ok: false, error: "Method not allowed.", }); } export function notFound(response: ServerResponse): void { sendJson(response, 404, { ok: false, error: "Not found.", }); } export async function serveStaticFile(input: { response: ServerResponse; filePath: string; }): Promise { try { const absolutePath = resolve(input.filePath); const fileStats = await stat(absolutePath); if (!fileStats.isFile()) { return false; } const extension = extname(absolutePath).toLowerCase(); const contentType = CONTENT_TYPES[extension] ?? "application/octet-stream"; input.response.statusCode = 200; input.response.setHeader("Content-Type", contentType); await new Promise((resolveStream, rejectStream) => { const stream = createReadStream(absolutePath); stream.on("error", rejectStream); stream.on("end", () => resolveStream()); stream.pipe(input.response); }); return true; } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") { return false; } throw error; } }