Chapter 04

HTTP 服务器 Bun.serve

用 Web 标准的 Request/Response API 写服务器,比 Node http + Express 快 3-5 倍。

4.1 最小 HTTP 服务器

Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello from Bun!");
  },
});
console.log("http://localhost:3000");

启动后访问 http://localhost:3000——就这么 5 行。

为什么 fetch handler?

Bun 选用 Web Fetch API 作为服务器接口——Request/Response 对象和浏览器里的 fetch() 一模一样。好处:同一套代码能跑在 Bun / Cloudflare Workers / Deno 上,学一次用到处。

4.2 基本用法

import type { Server } from "bun";

const server: Server = Bun.serve({
  port: 3000,
  hostname: "0.0.0.0",
  development: true,              // 默认从 NODE_ENV 推断

  async fetch(req, server) {
    const url = new URL(req.url);
    if (url.pathname === "/") return new Response("home");
    if (url.pathname === "/api/hello")
      return Response.json({ hello: "world" });

    // POST 读 body
    if (req.method === "POST") {
      const body = await req.json();
      return Response.json({ received: body });
    }

    return new Response("Not found", { status: 404 });
  },

  error(err) {
    console.error(err);
    return new Response("Server error", { status: 500 });
  },
});

4.3 路由:routes 字段(Bun 1.2+)

Bun 1.2 起内置声明式路由,不用手写 URL 分支:

Bun.serve({
  port: 3000,
  routes: {
    "/": new Response("Home"),
    "/api/status": Response.json({ ok: true }),

    // 按方法分发
    "/users/:id": {
      GET:    (req) => Response.json({ id: req.params.id }),
      DELETE: (req) => new Response(null, { status: 204 }),
    },

    // 通配
    "/static/*": (req) => Bun.file(`./public${new URL(req.url).pathname}`),
  },

  // 未匹配 fallback
  fetch() {
    return new Response("Not found", { status: 404 });
  },
});

req.params 自动解析 :id 这种参数,类型 { id: string }

4.4 流式 Response

Bun.serve({
  fetch() {
    const stream = new ReadableStream({
      async start(controller) {
        for (let i = 0; i < 10; i++) {
          controller.enqueue(new TextEncoder().encode(`chunk ${i}\n`));
          await Bun.sleep(100);
        }
        controller.close();
      },
    });
    return new Response(stream, {
      headers: { "Content-Type": "text/plain" },
    });
  },
});

Server-Sent Events(SSE)、聊天流式输出都用这种 ReadableStream。

4.5 WebSocket

Bun.serve({
  port: 3000,
  fetch(req, server) {
    // 升级到 WebSocket
    const ok = server.upgrade(req, {
      data: { userId: "123" },   // 关联到 ws 的自定义数据
    });
    if (ok) return;                // 升级成功无需返回 Response
    return new Response("Expected ws", { status: 400 });
  },

  websocket: {
    open(ws)    { console.log("opened", ws.data.userId); },
    message(ws, msg) {
      ws.send(`echo: ${msg}`);
      ws.publish("room1", `${ws.data.userId}: ${msg}`);
    },
    close(ws)   { console.log("closed"); },
  },
});

发布-订阅(pub/sub)

Bun 的 WebSocket 内置 topic 订阅,多个 ws 可以订阅同一 topic,publish 群发:

ws.subscribe("room1");   // 加入房间
ws.publish("room1", "hi all");   // 广播给订阅者(除自己)
server.publish("room1", "server msg");   // 从 server 侧广播

4.6 TLS / HTTPS

Bun.serve({
  port: 443,
  tls: {
    cert: Bun.file("./cert.pem"),
    key:  Bun.file("./key.pem"),
    // 可选:CA、passphrase、dhParamsFile、serverName(SNI)
  },
  fetch() { return new Response("https!"); },
});

也支持多域名(SNI)——用 tls: [{ serverName: 'a.com', cert: ... }, { serverName: 'b.com', ... }]

4.7 性能数字

服务器RPS(单核,Hello World)
Node http~40k
Node + Express~12k
Node + Fastify~50k
Bun.serve~120k

实际业务吞吐差距会缩小(因为瓶颈往往在数据库/外部 IO),但 Bun 在冷启动尾延迟上仍有明显优势。

4.8 上层框架:ElysiaJS / Hono

直接用 Bun.serve 写大型应用会很啰嗦——用专为 Bun 设计的框架:

import { Elysia } from "elysia";

new Elysia()
  .get("/", () => "home")
  .get("/users/:id", ({ params }) => ({ id: params.id }))
  .post("/echo", ({ body }) => body)
  .listen(3000);

4.9 平滑停机

const server = Bun.serve({ /* ... */ });

process.on("SIGINT", () => {
  server.stop();        // 拒绝新连接,等已有连接处理完
  console.log("bye");
  process.exit(0);
});

4.10 小结