Skip to main content
Bun 实现了 WHATWG 的 fetch 标准,同时进行了扩展以满足服务端 JavaScript 的需求。 Bun 也实现了 node:http,但通常推荐使用 fetch

发送 HTTP 请求

要发送 HTTP 请求,使用 fetch
const response = await fetch("http://example.com");

console.log(response.status); // => 200

const text = await response.text(); // 或者 response.json(), response.formData() 等等
fetch 也支持 HTTPS URL。
const response = await fetch("https://example.com");
你也可以传递一个 Request 对象给 fetch
const request = new Request("http://example.com", {
  method: "POST",
  body: "Hello, world!",
});

const response = await fetch(request);

发送 POST 请求

要发送 POST 请求,传入一个 method 属性值为 "POST" 的对象。
const response = await fetch("http://example.com", {
  method: "POST",
  body: "Hello, world!",
});
body 可以是字符串、FormData 对象、ArrayBufferBlob 等更多类型。更多信息请参考 MDN 文档

代理请求

要代理请求,传入一个带有 proxy 属性的 URL 字符串:
const response = await fetch("http://example.com", {
  proxy: "http://proxy.com",
});
你也可以用对象格式发送自定义的代理请求头:
const response = await fetch("http://example.com", {
  proxy: {
    url: "http://proxy.com",
    headers: {
      "Proxy-Authorization": "Bearer my-token",
      "X-Custom-Proxy-Header": "value",
    },
  },
});
这些 headers 会直接在 CONNECT 请求(针对 HTTPS 目标)或代理请求(针对 HTTP 目标)中发送给代理。如果你提供了 Proxy-Authorization 头,它会覆盖代理 URL 中的任何凭据。

自定义请求头

要设置自定义请求头,传入一个带有 headers 属性的对象。
const response = await fetch("http://example.com", {
  headers: {
    "X-Custom-Header": "value",
  },
});
你也可以使用 Headers 对象设置请求头。
const headers = new Headers();
headers.append("X-Custom-Header", "value");

const response = await fetch("http://example.com", {
  headers,
});

响应体

要读取响应体,使用以下方法之一:
  • response.text(): Promise<string>:返回包含响应体文本的 Promise。
  • response.json(): Promise<any>:返回包含响应体 JSON 对象的 Promise。
  • response.formData(): Promise<FormData>:返回包含响应体 FormData 对象的 Promise。
  • response.bytes(): Promise<Uint8Array>:返回包含响应体 Uint8Array 的 Promise。
  • response.arrayBuffer(): Promise<ArrayBuffer>:返回包含响应体 ArrayBuffer 的 Promise。
  • response.blob(): Promise<Blob>:返回包含响应体 Blob 的 Promise。

流式响应体

你可以使用异步迭代器来流式读取响应体。
const response = await fetch("http://example.com");

for await (const chunk of response.body) {
  console.log(chunk);
}
你也可以直接访问 ReadableStream 对象。
const response = await fetch("http://example.com");

const stream = response.body;

const reader = stream.getReader();
const { value, done } = await reader.read();

流式请求体

你也可以使用 ReadableStream 来流式传输请求体数据:
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello");
    controller.enqueue(" ");
    controller.enqueue("World");
    controller.close();
  },
});

const response = await fetch("http://example.com", {
  method: "POST",
  body: stream,
});
使用 HTTP(S) 协议时的流式传输要点:
  • 数据直接流式传输到网络,而不需要将整个请求体缓存在内存中
  • 如果连接丢失,流会被取消
  • 除非流大小已知,否则不会自动设置 Content-Length
使用 S3 时:
  • 对于 PUT/POST 请求,Bun 会自动使用分块上传
  • 流以块的形式消费,并行上传
  • 可以通过 S3 选项监控上传进度

带超时的 URL 请求

要带超时发送请求,使用 AbortSignal.timeout
const response = await fetch("http://example.com", {
  signal: AbortSignal.timeout(1000),
});

取消请求

要取消请求,使用 AbortController
const controller = new AbortController();

const response = await fetch("http://example.com", {
  signal: controller.signal,
});

controller.abort();

Unix 域套接字

要通过 Unix 域套接字请求 URL,使用 unix: string 选项:
const response = await fetch("https://hostname/a/path", {
  unix: "/var/run/path/to/unix.sock",
  method: "POST",
  body: JSON.stringify({ message: "Hello from Bun!" }),
  headers: {
    "Content-Type": "application/json",
  },
});

TLS

使用客户端证书时,使用 tls 选项:
await fetch("https://example.com", {
  tls: {
    key: Bun.file("/path/to/key.pem"),
    cert: Bun.file("/path/to/cert.pem"),
    // ca: [Bun.file("/path/to/ca.pem")],
  },
});

自定义 TLS 验证

要定制 TLS 验证,使用 tlscheckServerIdentity 选项:
await fetch("https://example.com", {
  tls: {
    checkServerIdentity: (hostname, peerCertificate) => {
      // 如果证书无效,返回一个 Error
    },
  },
});
这与 Node 中 net 模块的工作方式类似。

禁用 TLS 验证

要禁用 TLS 验证,将 rejectUnauthorized 设为 false
await fetch("https://example.com", {
  tls: {
    rejectUnauthorized: false,
  },
});
这在使用自签名证书时避免 SSL 错误时尤其有用,但会关闭 TLS 验证,应谨慎使用。

请求选项

除了标准的 fetch 选项,Bun 还提供了几个扩展:
const response = await fetch("http://example.com", {
  // 控制自动响应解压(默认为 true)
  // 支持 gzip、deflate、brotli (br) 和 zstd
  decompress: true,

  // 禁用此请求的连接复用
  keepalive: false,

  // 调试日志等级
  verbose: true, // 或 "curl" 以获得更详细的输出
});

协议支持

除了 HTTP(S) 之外,Bun 的 fetch 还支持多种其他协议:

S3 URL - s3://

Bun 支持直接从 S3 桶读取。
// 使用环境变量提供凭据
const response = await fetch("s3://my-bucket/path/to/object");

// 或者显式提供凭据
const response = await fetch("s3://my-bucket/path/to/object", {
  s3: {
    accessKeyId: "YOUR_ACCESS_KEY",
    secretAccessKey: "YOUR_SECRET_KEY",
    region: "us-east-1",
  },
});
注意:使用 S3 时,只有 PUT 和 POST 方法支持请求体。上传时,Bun 会自动为流式请求体使用分块上传。 更多关于 Bun 的 S3 支持请查看 S3 文档。

本地文件 URL - file://

你可以用 file: 协议获取本地文件:
const response = await fetch("file:///path/to/file.txt");
const text = await response.text();
在 Windows 上,路径会自动规范化:
// 以下两者都能在 Windows 上工作
const response = await fetch("file:///C:/path/to/file.txt");
const response2 = await fetch("file:///c:/path\\to/file.txt");

Data URL - data:

Bun 支持 data: URL 方案:
const response = await fetch("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==");
const text = await response.text(); // "Hello, World!"

Blob URL - blob:

你可以使用 URL.createObjectURL() 创建的 URL 来获取 Blob:
const blob = new Blob(["Hello, World!"], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const response = await fetch(url);

错误处理

Bun 的 fetch 实现包含多个特定错误场景:
  • 使用 GET/HEAD 方法时带请求体会抛出错误(这是 fetch API 的预期行为)
  • 同时使用 proxyunix 选项会抛出错误
  • rejectUnauthorized 为 true(或未定义)时,TLS 证书验证失败
  • S3 操作可能抛出与身份验证或权限相关的特定错误

Content-Type 处理

当未明确提供时,Bun 会自动为请求体设置 Content-Type 头:
  • 对于 Blob 对象,使用 Blob 的 type
  • 对于 FormData,设置合适的 multipart 边界

调试

为了方便调试,你可以给 fetch 传入 verbose: true
const response = await fetch("http://example.com", {
  verbose: true,
});
这将把请求和响应头打印到终端:
[fetch] > HTTP/1.1 GET http://example.com/
[fetch] > Connection: keep-alive
[fetch] > User-Agent: Bun/1.3.3
[fetch] > Accept: */*
[fetch] > Host: example.com
[fetch] > Accept-Encoding: gzip, deflate, br, zstd

[fetch] < 200 OK
[fetch] < Content-Encoding: gzip
[fetch] < Age: 201555
[fetch] < Cache-Control: max-age=604800
[fetch] < Content-Type: text/html; charset=UTF-8
[fetch] < Date: Sun, 21 Jul 2024 02:41:14 GMT
[fetch] < Etag: "3147526947+gzip"
[fetch] < Expires: Sun, 28 Jul 2024 02:41:14 GMT
[fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
[fetch] < Server: ECAcc (sac/254F)
[fetch] < Vary: Accept-Encoding
[fetch] < X-Cache: HIT
[fetch] < Content-Length: 648
注意:verbose: boolean 不是 Web 标准 fetch API 的一部分,仅针对 Bun 特有。

性能

HTTP 请求发送前,必须完成 DNS 查找。这一步可能耗时较长,尤其当 DNS 服务器缓慢或网络差时。 DNS 查找后,必须连接 TCP 套接字,且可能进行 TLS 握手。这也可能耗费显著时间。 请求完成后,读取响应体也可能花费大量时间和内存。 在各个阶段,Bun 提供了 API 以帮助你优化应用性能。

DNS 预读取

要预读取 DNS 条目,可以使用 dns.prefetch API。当你知道将要连接某个主机且想避免初始 DNS 查找时,此 API 非常有用。
import { dns } from "bun";

dns.prefetch("bun.com");

DNS 缓存

默认情况下,Bun 会将 DNS 查询的结果缓存并去重,缓存时长最多 30 秒。你可以通过调用 dns.getCacheStats() 查看缓存状态。 想了解更多关于 Bun 中的 DNS 缓存,请参阅 DNS 缓存 文档。

预连接到主机

要预连接到主机,可以使用 fetch.preconnect API。当你知道将很快要连接某个主机并希望提前开始 DNS 查找、TCP 建连和 TLS 握手时,此 API 非常有用。
import { fetch } from "bun";

fetch.preconnect("https://bun.com");
注意:紧跟着调用 fetch.preconnect 马上发起 fetch 请求不会更快。预连接仅在你知道将很快连接某主机,但尚未准备好发起请求时生效。

启动时预连接

要在启动时预连接某主机,可以传入 --fetch-preconnect 参数:
bun --fetch-preconnect https://bun.com ./my-script.ts
这有点类似 HTML 中的 <link rel="preconnect"> 该功能尚未在 Windows 上实现。如果你希望在 Windows 上使用该功能,请提交 issue,方便我们实现支持。

连接池与 HTTP 长连接

Bun 会自动重用对同一个主机的连接,即连接池。这能显著减少建立连接所需的时间。你无需做任何配置,默认启用。

同时连接数限制

默认情况下,Bun 限制最大同时进行的 fetch 请求数量为 256。这样做有几个原因:
  • 改善系统整体稳定性。操作系统对同时打开的 TCP 套接字数有上限,通常在几千左右。接近该限制时,会导致整台电脑异常,应用挂起或崩溃。
  • 鼓励 HTTP Keep-Alive 的连接复用。对于短时 HTTP 请求,最慢的步骤往往是初次连接。连接复用可节省大量时间。
当超过限制时,请求会排队,等前一个请求完成后立即发出。 你可以通过环境变量 BUN_CONFIG_MAX_HTTP_REQUESTS 增加最大同时连接数:
BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun ./my-script.ts
此限制的最大值当前设置为 65,336。考虑到最大端口号为 65,535,单台电脑很难超过此限制。

响应体缓冲

Bun 非常优化了响应体读取的性能。读取响应体速度最快的方法是使用以下任一方法:
  • response.text(): Promise<string>
  • response.json(): Promise<any>
  • response.formData(): Promise<FormData>
  • response.bytes(): Promise<Uint8Array>
  • response.arrayBuffer(): Promise<ArrayBuffer>
  • response.blob(): Promise<Blob>
你也可以使用 Bun.write 将响应体写入磁盘文件:
import { write } from "bun";

await write("output.txt", response);

实现细节

  • 连接池默认启用,但每个请求可通过 keepalive: false 禁用。同样,设置 "Connection: close" 头也会禁用长连接。
  • 大文件上传会在特定条件下利用操作系统的 sendfile 系统调用优化:
    • 文件必须大于 32KB
    • 请求不能使用代理
    • 在 macOS 上,仅常规文件(非管道、套接字或设备)支持 sendfile
    • 若条件不满足,或使用了 S3/流式上传,则回退到内存读取文件
    • 此优化对 HTTP(非 HTTPS)请求特别有效,可直接将文件从内核发往网络栈
  • S3 操作自动处理请求签名和合并认证头
注意:许多功能是 Bun 针对标准 fetch API 的特定扩展。