Bun

HTTP 客户端

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(); // or response.json(), response.formData(), etc.

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",
});

自定义标头

要设置自定义标头,请传递一个对象,并将 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,该 Promise 解析为字符串格式的响应主体。
  • response.json(): Promise<any>:返回一个 Promise,该 Promise 解析为 JSON 对象格式的响应主体。
  • response.formData(): Promise<FormData>:返回一个 Promise,该 Promise 解析为 FormData 对象格式的响应主体。
  • response.bytes(): Promise<Uint8Array>:返回一个 Promise,该 Promise 解析为 Uint8Array 格式的响应主体。
  • response.arrayBuffer(): Promise<ArrayBuffer>:返回一个 Promise,该 Promise 解析为 ArrayBuffer 格式的响应主体。
  • response.blob(): Promise<Blob>:返回一个 Promise,该 Promise 解析为 Blob 格式的响应主体。

流式响应主体

您可以使用异步迭代器来流式传输响应主体。

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

要使用超时获取 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 验证,请在 tls 中使用 checkServerIdentity 选项

await fetch("https://example.com", {
  tls: {
    checkServerIdentity: (hostname, peerCertificate) => {
      // Return an Error if the certificate is invalid
    },
  },
});

这与 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", {
  // Control automatic response decompression (default: true)
  decompress: true,

  // Disable connection reuse for this request
  keepalive: false,

  // Debug logging level
  verbose: true, // or "curl" for more detailed output
});

协议支持

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

S3 URL - s3://

Bun 支持直接从 S3 存储桶中获取数据。

// Using environment variables for credentials
const response = await fetch("s3://my-bucket/path/to/object");

// Or passing credentials explicitly
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 会自动使用多部分上传来处理流式主体。

您可以在 S3 文档中阅读有关 Bun 的 S3 支持的更多信息。

文件 URL - file://

您可以使用 file: 协议获取本地文件

const response = await fetch("file:///path/to/file.txt");
const text = await response.text();

在 Windows 上,路径会自动规范化

// Both work on 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,设置适当的多部分边界
  • 对于 JSON 对象,设置 application/json

调试

为了帮助调试,您可以将 verbose: true 传递给 fetch

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.2.5
[fetch] > Accept: */*
[fetch] > Host: example.com
[fetch] > Accept-Encoding: gzip, deflate, br

[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.sh");

DNS 缓存

默认情况下,Bun 会在内存中缓存和去重 DNS 查询长达 30 秒。您可以通过调用 dns.getCacheStats() 来查看缓存统计信息

要了解有关 Bun 中的 DNS 缓存的更多信息,请参阅 DNS 缓存 文档。

预连接到主机

要预连接到主机,您可以使用 fetch.preconnect API。当您知道您很快需要连接到主机并希望提前启动初始 DNS 查找、TCP 套接字连接和 TLS 握手时,此 API 非常有用。

import { fetch } from "bun";

fetch.preconnect("https://bun.net.cn");

注意:在 fetch.preconnect 之后立即调用 fetch 不会使您的请求更快。仅当您知道您很快需要连接到主机,但您尚未准备好发出请求时,预连接才会有所帮助。

启动时预连接

要在启动时预连接到主机,您可以传递 --fetch-preconnect

bun --fetch-preconnect https://bun.net.cn ./my-script.ts

这有点像 HTML 中的 <link rel="preconnect">

此功能尚未在 Windows 上实现。如果您有兴趣在 Windows 上使用此功能,请提交 issue,我们可以实现对 Windows 的支持。

连接池 & HTTP Keep-Alive

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" 标头也可用于禁用 keep-alive。
  • 在特定条件下,使用操作系统的 sendfile 系统调用优化大型文件上传
    • 文件必须大于 32KB
    • 请求不得使用代理
    • 在 macOS 上,只有常规文件(而不是管道、套接字或设备)可以使用 sendfile
    • 当不满足这些条件时,或者当使用 S3/流式上传时,Bun 会回退到将文件读取到内存中
    • 此优化对于 HTTP(而非 HTTPS)请求尤其有效,在 HTTP 请求中,文件可以直接从内核发送到网络堆栈
  • S3 操作会自动处理签名请求和合并身份验证标头

注意:这些功能中的许多是 Bun 对标准 fetch API 的特定扩展。