Bun

HTTP 服务器

该页面主要文档了 Bun 原生的 Bun.serve API。Bun 还实现了 fetch 以及 Node.js 的 httphttps 模块。

这些模块已被重新实现,以使用 Bun 快速的内部 HTTP 基础设施。您可以直接使用这些模块;像 Express 这样依赖这些模块的框架应该可以开箱即用。有关详细的兼容性信息,请参阅 Runtime > Node.js APIs

要以简洁的 API 启动高性能 HTTP 服务器,推荐的方法是使用 Bun.serve

Bun.serve()

使用 Bun.serve 在 Bun 中启动 HTTP 服务器。

Bun.serve({
  // `routes` requires Bun v1.2.3+
  routes: {
    // Static routes
    "/api/status": new Response("OK"),

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

    // Per-HTTP method handlers
    "/api/posts": {
      GET: () => new Response("List posts"),
      POST: async req => {
        const body = await req.json();
        return Response.json({ created: true, ...body });
      },
    },

    // Wildcard route for all routes that start with "/api/" and aren't otherwise matched
    "/api/*": Response.json({ message: "Not found" }, { status: 404 }),

    // Redirect from /blog/hello to /blog/hello/world
    "/blog/hello": Response.redirect("/blog/hello/world"),

    // Serve a file by buffering it in memory
    "/favicon.ico": new Response(await Bun.file("./favicon.ico").bytes(), {
      headers: {
        "Content-Type": "image/x-icon",
      },
    }),
  },

  // (optional) fallback for unmatched routes:
  // Required if Bun's version < 1.2.3
  fetch(req) {
    return new Response("Not Found", { status: 404 });
  },
});

路由

Bun.serve() 中的路由接收一个 BunRequest (它扩展了 Request) 并返回一个 ResponsePromise<Response>。这使得发送和接收 HTTP 请求的代码更容易保持一致。

// Simplified for brevity
interface BunRequest<T extends string> extends Request {
  params: Record<T, string>;
  readonly cookies: CookieMap;
}

路由中的 Async/await

您可以在路由处理器中使用 async/await 来返回一个 Promise<Response>

import { sql, serve } from "bun";

serve({
  port: 3001,
  routes: {
    "/api/version": async () => {
      const [version] = await sql`SELECT version()`;
      return Response.json(version);
    },
  },
});

路由中的 Promise

您也可以从路由处理器返回一个 Promise<Response>

import { sql, serve } from "bun";

serve({
  routes: {
    "/api/version": () => {
      return new Promise(resolve => {
        setTimeout(async () => {
          const [version] = await sql`SELECT version()`;
          resolve(Response.json(version));
        }, 100);
      });
    },
  },
});

类型安全的路由参数

TypeScript 在传递字符串字面量作为路由参数时会进行解析,这样您的编辑器在访问 request.params 时会显示自动补全。

import type { BunRequest } from "bun";

Bun.serve({
  routes: {
    // TypeScript knows the shape of params when passed as a string literal
    "/orgs/:orgId/repos/:repoId": req => {
      const { orgId, repoId } = req.params;
      return Response.json({ orgId, repoId });
    },

    "/orgs/:orgId/repos/:repoId/settings": (
      // optional: you can explicitly pass a type to BunRequest:
      req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
    ) => {
      const { orgId, repoId } = req.params;
      return Response.json({ orgId, repoId });
    },
  },
});

百分比编码的路由参数值会被自动解码。支持 Unicode 字符。无效的 Unicode 将被替换为 Unicode 替换字符 &0xFFFD;

静态响应

路由也可以是 Response 对象(没有处理器函数)。Bun.serve() 对其进行了优化,以实现零分配的调度——非常适合健康检查、重定向和固定内容。

Bun.serve({
  routes: {
    // Health checks
    "/health": new Response("OK"),
    "/ready": new Response("Ready", {
      headers: {
        // Pass custom headers
        "X-Ready": "1",
      },
    }),

    // Redirects
    "/blog": Response.redirect("https://bun.net.cn/blog"),

    // API responses
    "/api/config": Response.json({
      version: "1.0.0",
      env: "production",
    }),
  },
});

静态响应在初始化后不会分配额外的内存。与手动返回 Response 对象相比,您通常可以获得至少 15% 的性能提升。

静态路由响应会在服务器对象的生命周期内进行缓存。要重新加载静态路由,请调用 server.reload(options)

文件响应与静态响应

在路由中提供文件时,根据是否缓冲文件内容或直接提供文件,存在两种不同的行为。

Bun.serve({
  routes: {
    // Static route - content is buffered in memory at startup
    "/logo.png": new Response(await Bun.file("./logo.png").bytes()),

    // File route - content is read from filesystem on each request
    "/download.zip": new Response(Bun.file("./download.zip")),
  },
});

静态路由new Response(await file.bytes()))在启动时将内容缓冲到内存中。

  • 请求期间零文件系统 I/O - 内容完全从内存中提供。
  • ETag 支持 - 自动生成和验证 ETags 以进行缓存。
  • If-None-Match - 当客户端 ETag 与服务器 ETag 匹配时,返回 304 Not Modified
  • 无 404 处理 - 缺少文件会导致启动错误,而不是运行时 404。
  • 内存使用 - 整个文件内容存储在 RAM 中。
  • 最适合:小型静态资源、API 响应、频繁访问的文件。

文件路由new Response(Bun.file(path)))在每次请求时从文件系统中读取。

  • 每次请求都进行文件系统读取 - 检查文件是否存在并读取内容。
  • 内置 404 处理 - 如果文件不存在或变得不可访问,则返回 404 Not Found
  • Last-Modified 支持 - 使用文件的修改时间来处理 If-Modified-Since 标头。
  • If-Modified-Since - 当文件自客户端缓存版本以来未更改时,返回 304 Not Modified
  • Range 请求支持 - 使用 Content-Range 标头自动处理部分内容请求。
  • 流式传输 - 使用带背压处理的缓冲读取器,以实现高效的内存使用。
  • 内存效率高 - 传输期间仅缓冲小块数据,而不是整个文件。
  • 最适合:大文件、动态内容、用户上传、频繁更改的文件。

HTTP 缓存行为

两种路由类型都实现了 HTTP 缓存标准,但策略不同。

静态路由缓存

  • ETag 生成:在启动时从内容自动计算 ETag 哈希值。
  • If-None-Match:将客户端 ETag 与服务器 ETag 进行验证。
  • 304 响应:当 ETags 匹配时,返回带有空主体的 304 Not Modified
  • 缓存标头:继承您在 Response 中提供的任何 Cache-Control 标头。
  • 一致性:ETag 在服务器重启或路由重新加载之前保持不变。

文件路由缓存

  • Last-Modified:使用文件的 mtime 作为 Last-Modified 标头。
  • If-Modified-Since:比较客户端日期与文件修改时间。
  • 304 响应:当文件自客户端缓存版本以来未更改时,返回 304 Not Modified
  • Content-Length:根据当前文件大小自动设置。
  • 动态验证:在每次请求时检查文件修改时间。

状态码处理

两种路由类型都会自动调整状态码。

  • 200 → 204:空文件(0 字节)返回 204 No Content 而不是 200 OK
  • 200 → 304:成功的缓存验证返回 304 Not Modified
  • 仅文件路由:缺少或无法访问的文件返回 404 Not Found
const server = Bun.serve({
  static: {
    "/api/time": new Response(new Date().toISOString()),
  },

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

// Update the time every second.
setInterval(() => {
  server.reload({
    static: {
      "/api/time": new Response(new Date().toISOString()),
    },

    fetch(req) {
      return new Response("404!");
    },
  });
}, 1000);

重新加载路由只会影响下一个请求。进行中的请求将继续使用旧的路由。在旧路由的进行中请求完成后,旧路由将从内存中释放。

为了简化错误处理,静态路由不支持从 ReadableStreamAsyncIterator 流式传输响应主体。幸运的是,您仍然可以先将响应缓冲到内存中。

const time = await fetch("https://api.example.com/v1/data");
// Buffer the response in memory first.
const blob = await time.blob();

const server = Bun.serve({
  static: {
    "/api/data": new Response(blob),
  },

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

路由优先级

路由根据特异性顺序匹配。

  1. 精确路由(/users/all
  2. 参数路由(/users/:id
  3. 通配符路由(/users/*
  4. 全局捕获所有(/*
Bun.serve({
  routes: {
    // Most specific first
    "/api/users/me": () => new Response("Current user"),
    "/api/users/:id": req => new Response(`User ${req.params.id}`),
    "/api/*": () => new Response("API catch-all"),
    "/*": () => new Response("Global catch-all"),
  },
});

按 HTTP 方法划分的路由

路由处理器可以通过 HTTP 方法进行专门化。

Bun.serve({
  routes: {
    "/api/posts": {
      // Different handlers per method
      GET: () => new Response("List posts"),
      POST: async req => {
        const post = await req.json();
        return Response.json({ id: crypto.randomUUID(), ...post });
      },
      PUT: async req => {
        const updates = await req.json();
        return Response.json({ updated: true, ...updates });
      },
      DELETE: () => new Response(null, { status: 204 }),
    },
  },
});

您可以传递以下任何一种方法:

方法用例示例
GET获取资源
HEAD检查资源是否存在
OPTIONS获取允许的 HTTP 方法(CORS)
DELETE删除资源
PATCH更新资源
POST创建资源
PUT更新资源

当传递一个函数而不是对象时,所有方法都将由该函数处理。

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

await fetch(new URL("/api/version", server.url));
await fetch(new URL("/api/version", server.url), { method: "PUT" });
// ... etc

热路由重载

使用 server.reload() 更新路由,无需重启服务器。

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

// Deploy new routes without downtime
server.reload({
  routes: {
    "/api/version": () => Response.json({ version: "2.0.0" }),
  },
});

错误处理

Bun 提供了结构化的路由错误处理。

Bun.serve({
  routes: {
    // Errors are caught automatically
    "/api/risky": () => {
      throw new Error("Something went wrong");
    },
  },
  // Global error handler
  error(error) {
    console.error(error);
    return new Response(`Internal Error: ${error.message}`, {
      status: 500,
      headers: {
        "Content-Type": "text/plain",
      },
    });
  },
});

HTML 导入

Bun 支持直接将 HTML 文件导入到您的服务器代码中,从而支持具有服务器端和客户端代码的全栈应用程序。HTML 导入有两种模式:

开发 (bun --hot): 资源在运行时按需打包,从而实现热模块替换 (HMR),提供快速、迭代的开发体验。当你更改前端代码时,浏览器会自动更新,无需完全重新加载页面。

生产 (bun build): 当使用 bun build --target=bun 构建时,import index from "./index.html" 语句会解析为一个预构建的 manifest 对象,其中包含所有已打包的客户端资源。Bun.serve 会使用此 manifest 来提供已优化的资源,而没有运行时打包的开销。这非常适合部署到生产环境。

import myReactSinglePageApp from "./index.html";

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

HTML 导入不仅是提供 HTML — 它是一个功能齐全的前端打包器、转译器和工具包,使用 Bun 的 打包器、JavaScript 转译器和 CSS 解析器构建而成。你可以使用它来构建功能齐全的前端,支持 React、TypeScript、Tailwind CSS 等。

有关使用 HTML 导入构建全栈应用程序的完整指南,包括详细的示例和最佳实践,请参阅 /docs/bundler/fullstack

实际示例:REST API

这是一个基础的、由数据库支持的 REST API,使用 Bun 的路由,零依赖。

server.ts
types.ts
server.ts
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: {
    // List posts
    "/api/posts": {
      GET: () => {
        const posts = db.query("SELECT * FROM posts").all();
        return Response.json(posts);
      },

      // Create post
      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 });
      },
    },

    // Get post by ID
    "/api/posts/:id": req => {
      const post = db
        .query("SELECT * FROM posts WHERE id = ?")
        .get(req.params.id);

      if (!post) {
        return new Response("Not Found", { status: 404 });
      }

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

  error(error) {
    console.error(error);
    return new Response("Internal Server Error", { status: 500 });
  },
});
types.ts
export interface Post {
  id: string;
  title: string;
  content: string;
  created_at: string;
}

路由性能

Bun.serve() 的路由建立在 uWebSocket 的 基于树的方法之上,增加了 SIMD 加速的路由参数解码JavaScriptCore 结构缓存,以突破现代硬件性能的极限。

fetch 请求处理器

fetch 处理器处理任何路由未匹配到的传入请求。它接收一个 Request 对象,并返回一个 ResponsePromise<Response>

Bun.serve({
  fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/") return new Response("Home page!");
    if (url.pathname === "/blog") return new Response("Blog!");
    return new Response("404!");
  },
});

fetch 处理器支持 async/await

import { sleep, serve } from "bun";
serve({
  async fetch(req) {
    const start = performance.now();
    await sleep(10);
    const end = performance.now();
    return new Response(`Slept for ${end - start}ms`);
  },
});

也支持基于 Promise 的响应

Bun.serve({
  fetch(req) {
    // Forward the request to another server.
    return fetch("https://example.com");
  },
});

你也可以从 fetch 处理器访问 Server 对象。它是传递给 fetch 函数的第二个参数。

// `server` is passed in as the second argument to `fetch`.
const server = Bun.serve({
  fetch(req, server) {
    const ip = server.requestIP(req);
    return new Response(`Your IP is ${ip}`);
  },
});

更改 porthostname

要配置服务器监听的端口和主机名,请在 options 对象中设置 porthostname

Bun.serve({
  port: 8080, // defaults to $BUN_PORT, $PORT, $NODE_PORT otherwise 3000
  hostname: "mydomain.com", // defaults to "0.0.0.0"
  fetch(req) {
    return new Response("404!");
  },
});

要随机选择一个可用端口,请将 port 设置为 0

const server = Bun.serve({
  port: 0, // random port
  fetch(req) {
    return new Response("404!");
  },
});

// server.port is the randomly selected port
console.log(server.port);

你可以通过访问服务器对象的 port 属性,或者访问 url 属性来查看选定的端口。

console.log(server.port); // 3000
console.log(server.url); // https://:3000

配置默认端口

Bun 支持多种选项和环境变量来配置默认端口。当未设置 port 选项时,将使用默认端口。

  • --port CLI 标志
bun --port=4002 server.ts
  • BUN_PORT 环境变量
BUN_PORT=4002 bun server.ts
  • PORT 环境变量
PORT=4002 bun server.ts
  • NODE_PORT 环境变量
NODE_PORT=4002 bun server.ts

Unix domain sockets

要监听一个 unix domain socket,请将 unix 选项设置为 socket 的路径。

Bun.serve({
  unix: "/tmp/my-socket.sock", // path to socket
  fetch(req) {
    return new Response(`404!`);
  },
});

抽象命名空间 sockets

Bun 支持 Linux 抽象命名空间 sockets。要使用抽象命名空间 socket,请在 unix 路径前加上一个 null 字节。

Bun.serve({
  unix: "\0my-abstract-socket", // abstract namespace socket
  fetch(req) {
    return new Response(`404!`);
  },
});

与 unix domain sockets 不同,抽象命名空间 sockets 不绑定到文件系统,并且在最后一个 socket 引用关闭时会自动删除。

错误处理

要激活开发模式,请设置 development: true

Bun.serve({
  development: true,
  fetch(req) {
    throw new Error("woops!");
  },
});

在开发模式下,Bun 会在浏览器中显示错误,并提供内置的错误页面。

Bun 内置的 500 错误页面

error 回调

要处理服务器端错误,请实现一个 error 处理器。此函数应返回一个 Response,在发生错误时提供给客户端。此响应将取代 development 模式下 Bun 的默认错误页面。

Bun.serve({
  fetch(req) {
    throw new Error("woops!");
  },
  error(error) {
    return new Response(`<pre>${error}\n${error.stack}</pre>`, {
      headers: {
        "Content-Type": "text/html",
      },
    });
  },
});

调用 Bun.serve 会返回一个 Server 对象。要停止服务器,请调用 .stop() 方法。

const server = Bun.serve({
  fetch() {
    return new Response("Bun!");
  },
});

server.stop();

TLS

Bun 内置支持 TLS,由 BoringSSL 提供支持。通过为 keycert 提供值来启用 TLS;两者都是启用 TLS 所必需的。

Bun.serve({
  fetch(req) {
    return new Response("Hello!!!");
  },

  tls: {
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
  }
});

keycert 字段需要您的 TLS 密钥和证书的 *内容*,*而不是其路径*。它可以是字符串、BunFileTypedArrayBuffer

Bun.serve({
  fetch() {},

  tls: {
    // BunFile
    key: Bun.file("./key.pem"),
    // Buffer
    key: fs.readFileSync("./key.pem"),
    // string
    key: fs.readFileSync("./key.pem", "utf8"),
    // array of above
    key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")],
  },
});

如果您的私钥使用密码加密,请为 passphrase 提供一个值来解密它。

Bun.serve({
  fetch(req) {
    return new Response("Hello!!!");
  },

  tls: {
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
    passphrase: "my-secret-passphrase",
  }
});

可选地,您可以通过为 ca 提供一个值来覆盖受信任的 CA 证书。默认情况下,服务器将信任 Mozilla 维护的知名 CA 列表。当指定 ca 时,Mozilla 列表将被覆盖。

Bun.serve({
  fetch(req) {
    return new Response("Hello!!!");
  },
  tls: {
    key: Bun.file("./key.pem"), // path to TLS key
    cert: Bun.file("./cert.pem"), // path to TLS cert
    ca: Bun.file("./ca.pem"), // path to root CA certificate
  }
});

覆盖 Diffie-Hellman 参数

Bun.serve({
  // ...
  tls: {
    // other config
    dhParamsFile: "/path/to/dhparams.pem", // path to Diffie Hellman parameters
  },
});

服务器名称指示 (SNI)

要配置服务器的服务器名称指示 (SNI),请在 tls 对象中设置 serverName 字段。

Bun.serve({
  // ...
  tls: {
    // ... other config
    serverName: "my-server.com", // SNI
  },
});

要允许多个服务器名称,请将一个对象数组传递给 tls,每个对象都包含一个 serverName 字段。

Bun.serve({
  // ...
  tls: [
    {
      key: Bun.file("./key1.pem"),
      cert: Bun.file("./cert1.pem"),
      serverName: "my-server1.com",
    },
    {
      key: Bun.file("./key2.pem"),
      cert: Bun.file("./cert2.pem"),
      serverName: "my-server2.com",
    },
  ],
});

idleTimeout

要配置空闲超时,请在 Bun.serve 中设置 idleTimeout 字段。

Bun.serve({
  // 10 seconds:
  idleTimeout: 10,

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

这是在服务器关闭连接之前允许连接空闲的最长时间。如果连接在发送或接收数据时没有活动,则该连接处于空闲状态。

export default 语法

到目前为止,本页上的示例都使用了显式的 Bun.serve API。Bun 还支持另一种语法。

server.ts
import {type Serve} from "bun";

export default {
  fetch(req) {
    return new Response("Bun!");
  },
} satisfies Serve;

将服务器选项传递给 Bun.serve 的替代方法是 export default 它。此文件可以原样执行;当 Bun 看到一个包含 fetch 处理程序的 default 导出的文件时,它会在后台将其传递给 Bun.serve

流式传输文件

要流式传输一个文件,请返回一个带有 BunFile 对象作为体的 Response 对象。

Bun.serve({
  fetch(req) {
    return new Response(Bun.file("./hello.txt"));
  },
});

⚡️ 速度 — Bun 会在可能的情况下自动使用 sendfile(2) 系统调用,从而在内核中实现零拷贝文件传输 — 这是发送文件的最快方式。

您可以通过 Bun.file 对象上的 slice(start, end) 方法发送文件的部分内容。这会自动在 Response 对象上设置 Content-RangeContent-Length 标头。

Bun.serve({
  fetch(req) {
    // parse `Range` header
    const [start = 0, end = Infinity] = req.headers
      .get("Range") // Range: bytes=0-100
      .split("=") // ["Range: bytes", "0-100"]
      .at(-1) // "0-100"
      .split("-") // ["0", "100"]
      .map(Number); // [0, 100]

    // return a slice of the file
    const bigFile = Bun.file("./big-video.mp4");
    return new Response(bigFile.slice(start, end));
  },
});

服务器生命周期方法

server.stop() - 停止服务器

停止服务器接受新连接

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

// Gracefully stop the server (waits for in-flight requests)
await server.stop();

// Force stop and close all active connections
await server.stop(true);

默认情况下,stop() 允许正在进行中的请求和 WebSocket 连接完成。传递 true 会立即终止所有连接。

server.ref() 和 server.unref() - 进程生命周期控制

控制服务器是否保持 Bun 进程的运行

// Don't keep process alive if server is the only thing running
server.unref();

// Restore default behavior - keep process alive
server.ref();

server.reload() - 热重载处理器

在不重启的情况下更新服务器的处理器

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

// Update to new handler
server.reload({
  routes: {
    "/api/version": Response.json({ version: "v2" }),
  },
  fetch(req) {
    return new Response("v2");
  },
});

这对于开发和热重载很有用。只能更新 fetcherrorroutes

每个请求的控制

server.timeout(Request, seconds) - 自定义请求超时

为单个请求设置自定义空闲超时

const server = Bun.serve({
  fetch(req, server) {
    // Set 60 second timeout for this request
    server.timeout(req, 60);

    // If they take longer than 60 seconds to send the body, the request will be aborted
    await req.text();

    return new Response("Done!");
  },
});

传递 0 以禁用请求的超时。

server.requestIP(Request) - 获取客户端信息

获取客户端 IP 和端口信息

const server = Bun.serve({
  fetch(req, server) {
    const address = server.requestIP(req);
    if (address) {
      return new Response(
        `Client IP: ${address.address}, Port: ${address.port}`,
      );
    }
    return new Response("Unknown client");
  },
});

对于已关闭的请求或 Unix domain sockets,返回 null

处理 Cookies

Bun 提供了一个内置 API,用于在 HTTP 请求和响应中处理 cookies。BunRequest 对象包含一个 cookies 属性,该属性提供了一个 CookieMap,方便访问和操作 cookies。当使用 routes 时,Bun.serve() 会自动跟踪 request.cookies.set 并将其应用于响应。

读取 cookies

使用 BunRequest 对象上的 cookies 属性从传入请求中读取 cookies。

Bun.serve({
  routes: {
    "/profile": req => {
      // Access cookies from the request
      const userId = req.cookies.get("user_id");
      const theme = req.cookies.get("theme") || "light";

      return Response.json({
        userId,
        theme,
        message: "Profile page",
      });
    },
  },
});

设置 cookies

要设置 cookies,请使用 BunRequest 对象中的 CookieMap 上的 set 方法。

Bun.serve({
  routes: {
    "/login": req => {
      const cookies = req.cookies;

      // Set a cookie with various options
      cookies.set("user_id", "12345", {
        maxAge: 60 * 60 * 24 * 7, // 1 week
        httpOnly: true,
        secure: true,
        path: "/",
      });

      // Add a theme preference cookie
      cookies.set("theme", "dark");

      // Modified cookies from the request are automatically applied to the response
      return new Response("Login successful");
    },
  },
});

Bun.serve() 会自动跟踪请求中修改过的 cookies,并将它们应用于响应。

删除 cookies

要删除 cookie,请使用 request.cookies (CookieMap) 对象上的 delete 方法。

Bun.serve({
  routes: {
    "/logout": req => {
      // Delete the user_id cookie
      req.cookies.delete("user_id", {
        path: "/",
      });

      return new Response("Logged out successfully");
    },
  },
});

删除的 cookie 会变成响应中的 Set-Cookie 标头,其中 maxAge 设置为 0value 为空。

服务器指标

server.pendingRequests 和 server.pendingWebSockets

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

const server = Bun.serve({
  fetch(req, server) {
    return new Response(
      `Active requests: ${server.pendingRequests}\n` +
        `Active WebSockets: ${server.pendingWebSockets}`,
    );
  },
});

server.subscriberCount(topic) - WebSocket 订阅者

获取 WebSocket 主题的订阅者数量

const server = Bun.serve({
  fetch(req, server) {
    const chatUsers = server.subscriberCount("chat");
    return new Response(`${chatUsers} users in chat`);
  },
  websocket: {
    message(ws) {
      ws.subscribe("chat");
    },
  },
});

WebSocket 配置

server.publish(topic, data, compress) - WebSocket 消息发布

服务器可以将消息发布给订阅了某个主题的所有 WebSocket 客户端。

const server = Bun.serve({
  websocket: {
    message(ws) {
      // Publish to all "chat" subscribers
      server.publish("chat", "Hello everyone!");
    },
  },

  fetch(req) {
    // ...
  },
});

publish() 方法返回

  • 成功发送的字节数
  • 如果消息被丢弃,则为 0
  • 如果应用了背压,则为 -1

WebSocket 处理器选项

配置 WebSockets 时,可以通过 websocket 处理器使用多种高级选项。

Bun.serve({
  websocket: {
    // Maximum message size (in bytes)
    maxPayloadLength: 64 * 1024,

    // Backpressure limit before messages are dropped
    backpressureLimit: 1024 * 1024,

    // Close connection if backpressure limit is hit
    closeOnBackpressureLimit: true,

    // Handler called when backpressure is relieved
    drain(ws) {
      console.log("Backpressure relieved");
    },

    // Enable per-message deflate compression
    perMessageDeflate: {
      compress: true,
      decompress: true,
    },

    // Send ping frames to keep connection alive
    sendPings: true,

    // Handlers for ping/pong frames
    ping(ws, data) {
      console.log("Received ping");
    },
    pong(ws, data) {
      console.log("Received pong");
    },

    // Whether server receives its own published messages
    publishToSelf: false,
  },
});

基准测试

以下是 Bun 和 Node.js 实现的一个简单 HTTP 服务器,该服务器对每个传入的 Request 响应 Bun!

Bun
Node
Bun
Bun.serve({
  fetch(req: Request) {
    return new Response("Bun!");
  },
  port: 3000,
});
Node
require("http")
  .createServer((req, res) => res.end("Bun!"))
  .listen(8080);

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

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

Reference

查看 TypeScript 定义