120 lines
3.0 KiB
TypeScript
120 lines
3.0 KiB
TypeScript
import { StrRenderer } from "ren/str.ts";
|
|
import { Layout } from "./comp/layout.ts";
|
|
import { Context } from "./context.ts";
|
|
import { E404Page } from "./views/e404.ts";
|
|
import * as log from "./log.ts";
|
|
import { HomePage } from "./views/home.ts";
|
|
import { RecipesPage } from "./views/recipes.ts";
|
|
import { IngredientsPage } from "./views/ingredients.ts";
|
|
|
|
if (import.meta.main) {
|
|
await main();
|
|
}
|
|
|
|
async function main() {
|
|
await startServer({ port: 33334 });
|
|
}
|
|
|
|
async function startServer(cfg: ServerConfig) {
|
|
const srv = Deno.listen({ hostname: "localhost", port: cfg.port });
|
|
log.info(`Server listening at http://localhost:${cfg.port}`);
|
|
|
|
for await (const conn of srv) {
|
|
serveHttp(conn);
|
|
}
|
|
}
|
|
|
|
interface ServerConfig {
|
|
port: number;
|
|
}
|
|
|
|
async function serveHttp(conn: Deno.Conn) {
|
|
const httpConn = Deno.serveHttp(conn);
|
|
|
|
for await (const reqEvt of httpConn) {
|
|
const res = await handleRequest(reqEvt.request);
|
|
reqEvt.respondWith(res);
|
|
}
|
|
}
|
|
|
|
async function handleRequest(req: Request): Promise<Response> {
|
|
log.debug({ url: req.url });
|
|
const ctx = createContextFromRequest(req);
|
|
|
|
try {
|
|
const res = await tryCreateFileResponse(ctx.locPath);
|
|
return res;
|
|
} catch (_) {
|
|
const ren = new StrRenderer({ wrapNode: Layout });
|
|
|
|
if (ctx.locPath === "/") {
|
|
return createHtmlResponse(ren.render(HomePage(ctx)));
|
|
} else if (ctx.locPath === "/recipes") {
|
|
return createHtmlResponse(ren.render(RecipesPage(ctx)));
|
|
} else if (ctx.locPath === "/ingredients") {
|
|
return createHtmlResponse(ren.render(IngredientsPage(ctx)));
|
|
} else {
|
|
return createHtmlResponse(ren.render(E404Page(ctx)), 404);
|
|
}
|
|
}
|
|
}
|
|
|
|
function createContextFromRequest(req: Request): Context {
|
|
return {
|
|
locPath: new URL(req.url).pathname,
|
|
};
|
|
}
|
|
|
|
function createHtmlResponse(body: string, status = 200): Response {
|
|
return new Response(body, {
|
|
status,
|
|
headers: getContentTypeHeader("html"),
|
|
});
|
|
}
|
|
|
|
async function tryCreateFileResponse(urlPath: string): Promise<Response> {
|
|
const filePath = extractFilePath(urlPath);
|
|
if (!filePath) throw new SkipFile();
|
|
|
|
const content = await Deno.readTextFile(filePath).catch(() => {
|
|
throw new SkipFile();
|
|
});
|
|
|
|
return createFileResponse(content, getFileExt(filePath));
|
|
}
|
|
|
|
class SkipFile extends Error {}
|
|
|
|
function createFileResponse(content: string, fileExt: string): Response {
|
|
log.debug(fileExt);
|
|
return new Response(content, {
|
|
headers: getContentTypeHeader(fileExt),
|
|
});
|
|
}
|
|
|
|
function extractFilePath(urlPath: string): string | null {
|
|
if (urlPath.startsWith("/static")) {
|
|
return urlPath.slice(1);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getContentTypeHeader(fileExt: string): Record<string, string> {
|
|
return { "content-type": getContentTypeByExt(fileExt) };
|
|
}
|
|
|
|
function getContentTypeByExt(fileExt: string): string {
|
|
switch (fileExt) {
|
|
case "html":
|
|
return "text/html";
|
|
case "css":
|
|
return "text/css";
|
|
default:
|
|
return "text/plain";
|
|
}
|
|
}
|
|
|
|
function getFileExt(filePath: string): string {
|
|
return filePath.slice((filePath.lastIndexOf(".") >>> 0) + 1);
|
|
}
|