Files
UHD_Media_Tool/spikes/deno/apps/server/src/http.ts
T
2026-09-27 18:24:08 +08:00

53 lines
2.3 KiB
TypeScript

// Hono 4 on Deno.serve with an SSE stream + graceful shutdown.
import { Hono } from "hono";
import { streamSSE } from "hono/streaming";
import { serveStatic } from "hono/deno";
import { redact } from "@spike/x";
export function createApp() {
const app = new Hono();
app.get("/api/v1/health", (c) => c.json({ ok: true, runtime: `deno ${Deno.version.deno}`, ts: Deno.version.typescript }));
app.get("/api/v1/redact", (c) => c.json(redact({ url: c.req.url, apiKey: "x" }) as Record<string, unknown>));
app.get("/api/v1/events", (c) =>
streamSSE(c, async (stream) => {
const after = Number(c.req.query("after") ?? 0);
let aborted = false;
stream.onAbort(() => { aborted = true; });
for (let i = after + 1; i <= after + 3 && !aborted; i++) {
await stream.writeSSE({ id: String(i), event: "tick", data: JSON.stringify({ i }) });
await stream.sleep(100);
}
}));
// Endless stream: used to check how Deno.serve shutdown treats open SSE connections.
app.get("/api/v1/forever", (c) =>
streamSSE(c, async (stream) => {
let i = 0;
while (!stream.aborted && !shutdownSignal.aborted) {
await stream.writeSSE({ id: String(++i), data: "ping" });
await stream.sleep(200);
}
}));
// Single-container mode: serve the built SPA (react-router ssr:false) from WEB_ROOT with index.html fallback.
const webRoot = Deno.env.get("WEB_ROOT");
if (webRoot) {
app.use("/*", serveStatic({ root: webRoot }));
app.get("*", serveStatic({ root: webRoot, path: "index.html" }));
}
return app;
}
/** App-level shutdown signal: SSE loops must observe it, otherwise graceful shutdown waits forever. */
export const shutdownCtl = new AbortController();
const shutdownSignal = shutdownCtl.signal;
if (import.meta.main) {
const port = Number(Deno.env.get("PORT") ?? 5199);
const ac = new AbortController();
const server = Deno.serve({ port, hostname: Deno.env.get("HOST") ?? "127.0.0.1", signal: ac.signal, onListen: ({ port }) => console.log(`listening ${port}`) }, createApp().fetch);
const stop = () => { console.log("shutdown"); if (Deno.env.get("CLOSE_SSE") !== "0") shutdownCtl.abort(); ac.abort(); };
Deno.addSignalListener("SIGTERM", stop);
Deno.addSignalListener("SIGINT", stop);
await server.finished;
console.log("server finished");
}