> ## Documentation Index
> Fetch the complete documentation index at: https://bun.ll1025.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# 服务器

> 使用 `Bun.serve` 启动 Bun 的高性能 HTTP 服务器

## 基本设置

```ts title="index.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
const server = Bun.serve({
  // `routes` 需要 Bun v1.2.3+
  routes: {
    // 静态路由
    "/api/status": new Response("OK"),

    // 动态路由
    "/users/:id": req => {
      return new Response(`Hello User ${req.params.id}!`);
    },

    // 按 HTTP 方法处理
    "/api/posts": {
      GET: () => new Response("列出文章"),
      POST: async req => {
        const body = await req.json();
        return Response.json({ created: true, ...body });
      },
    },

    // 通配符路由，匹配所有以 "/api/" 开头且未匹配的其他路由
    "/api/*": Response.json({ message: "未找到" }, { status: 404 }),

    // 从 /blog/hello 重定向到 /blog/hello/world
    "/blog/hello": Response.redirect("/blog/hello/world"),

    // 通过惰性加载到内存来提供文件服务
    "/favicon.ico": Bun.file("./favicon.ico"),
  },

  // (可选) 未匹配路由的回退方案：
  // 如果 Bun 版本 < 1.2.3 则必须设置
  fetch(req) {
    return new Response("Not Found", { status: 404 });
  },
});

console.log(`服务器运行在 ${server.url}`);
```

***

## HTML 导入

将 HTML 文件直接导入到服务器代码中，构建同时包含服务端和客户端代码的全栈应用。HTML 导入支持两种模式：

**开发模式 (`bun --hot`)：** Bun 在运行时按需打包资源，并启用热模块替换 (HMR)：当你修改前端代码时，浏览器自动更新，无需完整页面刷新。

**生产模式 (`bun build`)：** 当你使用 `bun build --target=bun` 构建时，`import index from "./index.html"` 语句解析为一个预构建的清单对象，其中包含所有打包好的客户端资源。`Bun.serve` 从该清单中提供资源，无需在运行时进行打包。

```ts theme={null}
import myReactSinglePageApp from "./index.html";

Bun.serve({
  routes: {
    "/": myReactSinglePageApp,
  },
});
```

HTML 导入不仅服务 HTML：它还会运行 Bun 的[打包器](/bundler)、JavaScript 转译器和 CSS 解析器，因此你可以使用 React、TypeScript 和 Tailwind CSS 构建前端。

有关使用 HTML 导入构建全栈应用的完整指南，请参阅[全栈开发服务器](/bundler/fullstack)。

***

## 配置

### 修改 `port` 和 `hostname`

要配置服务器监听的端口和主机名，请在选项对象中设置 `port` 和 `hostname`。

```ts theme={null}
Bun.serve({
  port: 8080, // 默认为 $BUN_PORT, $PORT, $NODE_PORT，否则为 3000 // [!code ++]
  hostname: "mydomain.com", // 默认为 "0.0.0.0" // [!code ++]
  fetch(req) {
    return new Response("404!");
  },
});
```

要随机选择一个可用端口，将 `port` 设置为 `0`。

```ts theme={null}
const server = Bun.serve({
  port: 0, // 随机端口 // [!code ++]
  fetch(req) {
    return new Response("404!");
  },
});

// server.port 是随机选择的端口
console.log(server.port);
```

从服务器的 `port` 或 `url` 属性中读取所选端口。

```ts theme={null}
console.log(server.port); // 3000
console.log(server.url); // http://localhost:3000
```

### 配置默认端口

当未设置 `port` 选项时，几个标志和环境变量会设置 Bun 使用的默认端口。

* `--port` CLI 标志

```sh theme={null}
bun --port=4002 server.ts
```

* `BUN_PORT` 环境变量

```sh theme={null}
BUN_PORT=4002 bun server.ts
```

* `PORT` 环境变量

```sh terminal icon="terminal" theme={null}
PORT=4002 bun server.ts
```

* `NODE_PORT` 环境变量

```sh terminal icon="terminal" theme={null}
NODE_PORT=4002 bun server.ts
```

***

## Unix 域套接字

要监听 [Unix 域套接字](https://en.wikipedia.org/wiki/Unix_domain_socket)，传递 `unix` 选项并指定套接字路径。

```ts theme={null}
Bun.serve({
  unix: "/tmp/my-socket.sock", // 套接字路径
  fetch(req) {
    return new Response(`404!`);
  },
});
```

### 抽象命名空间套接字

在 Linux 上，Bun 还支持抽象命名空间套接字：在 `unix` 路径前加上一个空字节。

```ts theme={null}
Bun.serve({
  unix: "\0my-abstract-socket", // 抽象命名空间套接字
  fetch(req) {
    return new Response(`404!`);
  },
});
```

与 Unix 域套接字不同，抽象命名空间套接字不绑定到文件系统，当最后一个对套接字的引用关闭时会自动移除。

***

## HTTP/3 (QUIC)

<Note>`Bun.serve` 中的 HTTP/3 支持是**实验性**的，可能在未来版本中发生变更。</Note>

`Bun.serve` 也可以通过 QUIC 协议监听 HTTP/3。设置 `http3: true` 并结合 [`tls`](./tls)；HTTP/3 需要 TLS。

```ts theme={null}
Bun.serve({
  tls: {
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
  },
  http3: true, // [!code ++]
  fetch(req) {
    return new Response("Hello over HTTP/3!");
  },
});
```

当启用 `http3` 时，服务器同时通过 TCP (HTTP/1.1) 和 UDP (HTTP/3) 监听同一端口。HTTP/1.1 响应中包含 `Alt-Svc` 头部，通知 HTTP/3 端点，支持该协议能力的客户端可以自动升级。

如果只想服务 HTTP/3（完全不监听 TCP），可以设置 `http1: false`：

```ts theme={null}
Bun.serve({
  tls: {
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
  },
  http3: true,
  http1: false, // [!code ++]
  fetch(req) {
    return new Response("仅 HTTP/3");
  },
});
```

<Note>Unix 域套接字不支持 `http3`——QUIC 需要一个 UDP 端口。`http1: false` 需要同时设置 `http3: true`。</Note>

***

## idleTimeout

默认情况下，`Bun.serve` 会在**10 秒**无活动后关闭连接。当没有数据发送或接收时，连接被视为空闲，包括正在处理中的请求——即你的处理器仍在运行但尚未向响应写入任何字节。浏览器和 `fetch()` 客户端会将其视为连接重置。

要配置此设置，请设置 `idleTimeout` 字段（以秒为单位）。最大值为 `255`，设置为 `0` 则完全禁用超时。

```ts theme={null}
Bun.serve({
  // 30 秒（默认是 10）
  idleTimeout: 30,

  fetch(req) {
    return new Response("Bun!");
  },
});
```

<Note>
  **流式传输与服务器发送事件**——空闲定时器在响应流式传输时同样生效。如果你的流空闲时间超过 `idleTimeout`，Bun
  会在响应传输中途关闭连接。对于长时间运行的流，可以使用 [`server.timeout(req, 0)`](#server-timeout-request-seconds)
  禁用该请求的超时。
</Note>

***

## export default 语法

你可以不将服务器选项传递给 `Bun.serve`，而是使用 `export default` 导出。

```ts server.ts theme={null}
import type { Serve } from "bun";

export default {
  fetch(req) {
    return new Response("Bun!");
  },
} satisfies Serve.Options<undefined>;
```

类型参数 `<undefined>` 是 WebSocket 数据类型。如果你添加了使用 `server.upgrade(req, { data: ... })` 附加自定义数据的 `websocket` 处理器，请将 `undefined` 替换为你的数据类型。

你可以直接运行此文件：当 Bun 发现一个包含 `fetch` 处理器的 `default` 导出的文件时，会将其传入 `Bun.serve`。

***

## 热路由重载

使用 `server.reload()` 可无需重启服务器即可更新路由：

```ts theme={null}
const server = Bun.serve({
  routes: {
    "/api/version": () => Response.json({ version: "1.0.0" }),
  },
});

// 不停机部署新路由
server.reload({
  routes: {
    "/api/version": () => Response.json({ version: "2.0.0" }),
  },
});
```

***

## 服务器生命周期方法

### `server.stop()`

停止服务器接受新连接：

```ts theme={null}
const server = Bun.serve({
  fetch(req) {
    return new Response("Hello!");
  },
});

// 优雅地停止服务器（等待正在处理的请求）
await server.stop();

// 强制停止并关闭所有活动连接
await server.stop(true);
```

默认情况下，`stop()` 允许正在处理的请求和 WebSocket 连接完成。传递 `true` 可立即终止所有连接。

### `server.ref()` 和 `server.unref()`

控制服务器是否保持 Bun 进程继续运行：

```ts theme={null}
// 如果服务器是唯一运行的东西，不保持进程存活
server.unref();

// 恢复默认行为 - 保持进程存活
server.ref();
```

### `server.reload()`

无需重启即可更新服务器的处理器：

```ts theme={null}
const server = Bun.serve({
  routes: {
    "/api/version": Response.json({ version: "v1" }),
  },
  fetch(req) {
    return new Response("v1");
  },
});

// 更新为新处理器
server.reload({
  routes: {
    "/api/version": Response.json({ version: "v2" }),
  },
  fetch(req) {
    return new Response("v2");
  },
});
```

用于开发环境和热重载。只有 `fetch`、`error`、`routes` 和 `websocket` 可以更新。

***

## 按请求控制

### `server.timeout(Request, seconds)`

覆盖单个请求的空闲超时时间。传递 `0` 可完全禁用该请求的超时。

```ts theme={null}
const server = Bun.serve({
  async fetch(req, server) {
    // 为此请求设置最多 60 秒的空闲时间，而不是默认的 10 秒
    server.timeout(req, 60);

    // 如果发送请求体超过 60 秒，请求将被中止
    await req.text();

    return new Response("完成!");
  },
});
```

使用 `server.timeout(req, 0)` 保持长时间运行的流式响应（如服务器发送事件）存活，而无需为每个请求提高全局 `idleTimeout`：

```ts theme={null}
Bun.serve({
  routes: {
    "/events": (req, server) => {
      // 为此流式响应禁用空闲超时。
      // 否则，如果 10 秒（默认 idleTimeout）内没有发送字节，
      // 连接将被关闭。
      server.timeout(req, 0);

      return new Response(
        async function* () {
          yield "data: hello\n\n";
          // 事件可以偶尔到达而不会导致连接被终止
        },
        { headers: { "Content-Type": "text/event-stream" } },
      );
    },
  },
});
```

### `server.requestIP(Request)`

获取客户端 IP 和端口信息：

```ts theme={null}
const server = Bun.serve({
  fetch(req, server) {
    const address = server.requestIP(req);
    if (address) {
      return new Response(`客户端 IP: ${address.address}, 端口: ${address.port}`);
    }
    return new Response("未知客户端");
  },
});
```

对于已关闭的请求或 Unix 域套接字，返回 `null`。

***

## 服务器指标

### `server.pendingRequests` 和 `server.pendingWebSockets`

使用内置计数器监控服务器活动：

```ts theme={null}
const server = Bun.serve({
  fetch(req, server) {
    return new Response(`活动请求: ${server.pendingRequests}\n` + `活动 WebSocket: ${server.pendingWebSockets}`);
  },
});
```

### `server.subscriberCount(topic)`

获取 WebSocket 主题的订阅者数量：

```ts theme={null}
const server = Bun.serve({
  fetch(req, server) {
    const chatUsers = server.subscriberCount("chat");
    return new Response(`${chatUsers} 个用户在聊天中`);
  },
  websocket: {
    message(ws) {
      ws.subscribe("chat");
    },
  },
});
```

***

## 基准测试

以下 Bun 和 Node.js 服务器对每个传入的 `Request` 都响应 `Bun!`。

```ts Bun theme={null}
Bun.serve({
  fetch(req: Request) {
    return new Response("Bun!");
  },
  port: 3000,
});
```

```ts theme={null}
require("http")
  .createServer((req, res) => res.end("Bun!"))
  .listen(8080);
```

`Bun.serve` 服务器在 Linux 上每秒可处理的请求数量大约是 Node.js 的 2.5 倍。

| 运行时     | 每秒请求数     |
| ------- | --------- |
| Node 16 | \~64,000  |
| Bun     | \~160,000 |

<Frame>
  ![image](https://user-images.githubusercontent.com/709451/162389032-fc302444-9d03-46be-ba87-c12bd8ce89a0.png)
</Frame>

***

## 实战示例：REST API

以下是一个使用 Bun 路由器的基本数据库驱动 REST API，零依赖：

<CodeGroup>
  ```ts server.ts expandable icon="file-code" theme={null}
  import type { Post } from "./types.ts";
  import { Database } from "bun:sqlite";

  const db = new Database("posts.db");
  db.exec(`
    CREATE TABLE IF NOT EXISTS posts (
      id TEXT PRIMARY KEY,
      title TEXT NOT NULL,
      content TEXT NOT NULL,
      created_at TEXT NOT NULL
    )
  `);

  Bun.serve({
    routes: {
      // 列出文章
      "/api/posts": {
        GET: () => {
          const posts = db.query("SELECT * FROM posts").all();
          return Response.json(posts);
        },

        // 创建文章
        POST: async req => {
          const post: Omit<Post, "id" | "created_at"> = await req.json();
          const id = crypto.randomUUID();

          db.query(
            `INSERT INTO posts (id, title, content, created_at)
             VALUES (?, ?, ?, ?)`,
          ).run(id, post.title, post.content, new Date().toISOString());

          return Response.json({ id, ...post }, { status: 201 });
        },
      },

      // 按 ID 获取文章
      "/api/posts/:id": req => {
        const post = db.query("SELECT * FROM posts WHERE id = ?").get(req.params.id);

        if (!post) {
          return new Response("未找到", { status: 404 });
        }

        return Response.json(post);
      },
    },

    error(error) {
      console.error(error);
      return new Response("内部服务器错误", { status: 500 });
    },
  });
  ```

  ```ts types.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
  export interface Post {
    id: string;
    title: string;
    content: string;
    created_at: string;
  }
  ```
</CodeGroup>

***

## 参考

```ts expandable 查看 TypeScript 定义 theme={null}
interface Server extends Disposable {
  /**
   * 停止服务器接受新连接。
   * @param closeActiveConnections 如果为 true，立即终止所有连接
   * @returns 服务器停止后解析的 Promise
   */
  stop(closeActiveConnections?: boolean): Promise<void>;

  /**
   * 无需重启服务器即可更新处理器。
   * 只能更新 fetch 和 error 处理器。
   */
  reload(options: Serve): void;

  /**
   * 向运行中的服务器发起请求。
   * 适用于测试或内部路由。
   */
  fetch(request: Request | string): Response | Promise<Response>;

  /**
   * 将 HTTP 请求升级为 WebSocket 连接。
   * @returns 如果升级成功返回 true，失败返回 false
   */
  upgrade<T = undefined>(
    request: Request,
    options?: {
      headers?: Bun.HeadersInit;
      data?: T;
    },
  ): boolean;

  /**
   * 向订阅了某个主题的所有 WebSocket 客户端发布消息。
   * @returns 发送的字节数，丢弃返回 0，应用背压返回 -1
   */
  publish(
    topic: string,
    data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
    compress?: boolean,
  ): ServerWebSocketSendStatus;

  /**
   * 获取订阅某个主题的 WebSocket 客户端数量。
   */
  subscriberCount(topic: string): number;

  /**
   * 获取客户端 IP 地址和端口。
   * @returns 对于已关闭的请求或 Unix 套接字返回 null
   */
  requestIP(request: Request): SocketAddress | null;

  /**
   * 为某个请求设置自定义空闲超时。
   * @param seconds 超时秒数，0 表示禁用
   */
  timeout(request: Request, seconds: number): void;

  /**
   * 在服务器运行时保持进程存活。
   */
  ref(): void;

  /**
   * 如果服务器是唯一运行的内容，允许进程退出。
   */
  unref(): void;

  /** 正在处理中的 HTTP 请求数量 */
  readonly pendingRequests: number;

  /** 活动的 WebSocket 连接数量 */
  readonly pendingWebSockets: number;

  /** 服务器 URL，包括协议、主机名和端口 */
  readonly url: URL;

  /** 服务器监听的端口 */
  readonly port: number;

  /** 服务器绑定的主机名 */
  readonly hostname: string;

  /** 服务器是否处于开发模式 */
  readonly development: boolean;

  /** 服务器实例标识符 */
  readonly id: string;
}

interface WebSocketHandler<T = undefined> {
  /** WebSocket 消息的最大字节数 */
  maxPayloadLength?: number;

  /** 应用背压前的排队消息字节数 */
  backpressureLimit?: number;

  /** 达到背压限制时是否关闭连接 */
  closeOnBackpressureLimit?: boolean;

  /** 背压缓解时调用 */
  drain?(ws: ServerWebSocket<T>): void | Promise<void>;

  /** 空闲超时秒数 */
  idleTimeout?: number;

  /** 启用每条消息的 deflate 压缩 */
  perMessageDeflate?:
    | boolean
    | {
        compress?: WebSocketCompressor | boolean;
        decompress?: WebSocketCompressor | boolean;
      };

  /** 发送 ping 帧以保持连接存活 */
  sendPings?: boolean;

  /** 服务器是否接收自己发布的消息 */
  publishToSelf?: boolean;

  /** 连接打开时调用 */
  open?(ws: ServerWebSocket<T>): void | Promise<void>;

  /** 收到消息时调用 */
  message(ws: ServerWebSocket<T>, message: string | Buffer): void | Promise<void>;

  /** 连接关闭时调用 */
  close?(ws: ServerWebSocket<T>, code: number, reason: string): void | Promise<void>;

  /** 收到 ping 帧时调用 */
  ping?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;

  /** 收到 pong 帧时调用 */
  pong?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
}

interface TLSOptions {
  /** 证书颁发机构链 */
  ca?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;

  /** 服务器证书 */
  cert?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;

  /** DH 参数文件路径 */
  dhParamsFile?: string;

  /** 私钥 */
  key?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;

  /** 减少 TLS 内存使用 */
  lowMemoryMode?: boolean;

  /** 私钥口令 */
  passphrase?: string;

  /** OpenSSL 选项标志 */
  secureOptions?: number;

  /** 用于 SNI 的服务器名称 */
  serverName?: string;
}
```
