benjivers/notes Public Public: anyone can read this; nobody can change it.

Files 7260e8fc5b5c3ca9332ff3506ef97626f456e658
83 lines · 2.7 KB · ~785 tok Raw History
1
/**
2
 * The process the forge runs. `serve` opens the database and listens:
3
 * every request gets the floor (`http.ts`), an identity (`who.ts`), the
4
 * door (`security.ts`) and one route (`routes.ts`) — so a request from
5
 * another site, from someone going too fast, or from someone the contract
6
 * turns away is refused before a byte of it is read. Run directly, it
7
 * serves what the forge said; a test calls `serve` with its own.
8
 */
9
10
import { openDb } from "./db.ts";
11
import { dataDir, identityKey, title, where } from "./env.ts";
12
import { html, text } from "./http.ts";
13
import { match } from "./routes.ts";
14
import { door } from "./security.ts";
15
import { who } from "./who.ts";
16
17
export interface ServeOptions {
18
  readonly port: number;
19
  readonly hostname?: string | undefined;
20
  readonly dataDir: string;
21
  readonly identityKey: string;
22
}
23
24
export function serve(options: ServeOptions) {
25
  const db = openDb(options.dataDir);
26
  return Bun.serve({
27
    port: options.port,
28
    hostname: options.hostname ?? "127.0.0.1",
29
    // A connection that says nothing for a minute is dropped. The event
30
    // stream heartbeats every 25 seconds to stay well inside that.
31
    idleTimeout: 60,
32
    async fetch(request, server) {
33
      const url = new URL(request.url);
34
      const me = who(request, options.identityKey);
35
      const refused = door(
36
        db,
37
        request,
38
        url,
39
        me,
40
        server.requestIP(request)?.address ?? "?",
41
      );
42
      if (refused !== undefined) return head(request, refused);
43
      const found = match(
44
        request.method === "HEAD" ? "GET" : request.method,
45
        url.pathname,
46
      );
47
      if (found === undefined) return text("not found", 404);
48
      const response = await found.route.handle({
49
        request,
50
        url,
51
        db,
52
        who: me,
53
        params: found.params,
54
      });
55
      return head(request, response);
56
    },
57
    // A bug answers with the floor and nothing else: never the runtime's
58
    // own error page, which carries source and paths, and never without
59
    // the headers. The detail goes to the log, where it belongs.
60
    error(error) {
61
      console.error(error);
62
      return html("<h1>Something went wrong</h1>", 500);
63
    },
64
  });
65
}
66
67
// HEAD is GET without the body: a health check sees the app up.
68
const head = (request: Request, response: Response): Response =>
69
  request.method === "HEAD"
70
    ? new Response(null, { status: response.status, headers: response.headers })
71
    : response;
72
73
if (import.meta.main) {
74
  const server = serve({
75
    port: Number(process.env.PORT ?? 8080),
76
    hostname: process.env.HOST,
77
    dataDir,
78
    identityKey,
79
  });
80
  console.log(
81
    `${title} listening on http://${server.hostname}:${server.port} (${where})`,
82
  );
83
}
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts