SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
3 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
2.6 KB · 59 lines typescript
Raw Blame History
1/**2 * Minimal HTTP server shared by the worker (`main.ts`) and the standalone scheduler (`scheduler.ts`):3 *   GET /healthz  → JSON from `health()`; 503 while `ready()` is false (shutting down / not started)4 *   GET /metrics  → Prometheus text from prom.ts after `beforeScrape()` refreshed the gauges5 * Never throws into the caller; errors become 500 JSON responses.6 */7import { createServer, type Server } from "node:http";8import { renderMetrics } from "./prom.js";910export interface HealthHttpOptions {11  port: number;12  role: string;13  health: () => Promise<Record<string, unknown>>;14  ready: () => boolean;15  /** refresh snapshot gauges (queue depth, budgets…) right before rendering */16  beforeScrape?: () => Promise<void>;17  log?: (msg: string) => void;18  /** extra JSON routes keyed by path prefix ("/trace" matches "/trace/<id>") */19  routes?: Record<string, (url: URL) => Promise<{ status: number; body: unknown }>>;20}2122export function startHealthHttp(o: HealthHttpOptions): Server {23  const log = o.log ?? ((m: string) => console.error(`[${o.role}] ${m}`));24  const server = createServer(async (req, res) => {25    try {26      const url = new URL(req.url ?? "/", "http://localhost");27      if (url.pathname === "/healthz" || url.pathname === "/") {28        const body = await o.health();29        const ok = o.ready();30        res.writeHead(ok ? 200 : 503, { "content-type": "application/json", "cache-control": "no-store" });31        res.end(JSON.stringify({ ok, role: o.role, ...body }, null, 2));32        return;33      }34      if (url.pathname === "/metrics") {35        if (o.beforeScrape) await o.beforeScrape().catch((e: Error) => log(`metrics refresh failed: ${e.message}`));36        res.writeHead(200, { "content-type": "text/plain; version=0.0.4; charset=utf-8", "cache-control": "no-store" });37        res.end(renderMetrics());38        return;39      }40      for (const [prefix, handler] of Object.entries(o.routes ?? {})) {41        if (url.pathname === prefix || url.pathname.startsWith(`${prefix}/`)) {42          const r = await handler(url);43          res.writeHead(r.status, { "content-type": "application/json", "cache-control": "no-store" });44          res.end(JSON.stringify(r.body));45          return;46        }47      }48      res.writeHead(404, { "content-type": "application/json" });49      res.end(JSON.stringify({ error: "not found" }));50    } catch (e) {51      res.writeHead(500, { "content-type": "application/json" });52      res.end(JSON.stringify({ error: (e as Error).message }));53    }54  });55  server.on("error", (e) => log(`http error: ${e.message}`));56  server.listen(o.port, "0.0.0.0", () => log(`http :${o.port} (/healthz, /metrics)`));57  return server;58}59