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

# Fetch

> 使用 Bun 的 fetch API 发送 HTTP 请求

Bun 实现了 WHATWG 的 `fetch` 标准，同时进行了扩展以满足服务端 JavaScript 的需求。

Bun 也实现了 `node:http`，但通常推荐使用 `fetch`。

## 发送 HTTP 请求

要发送 HTTP 请求，使用 `fetch`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com");

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

const text = await response.text(); // 或者 response.json(), response.formData() 等等
```

`fetch` 也支持 HTTPS URL。

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("https://example.com");
```

你也可以传递一个 [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) 对象给 `fetch`。

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const request = new Request("http://example.com", {
  method: "POST",
  body: "Hello, world!",
});

const response = await fetch(request);
```

### 发送 POST 请求

要发送 POST 请求，传入一个 `method` 属性值为 `"POST"` 的对象。

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  method: "POST",
  body: "Hello, world!",
});
```

`body` 可以是字符串、`FormData` 对象、`ArrayBuffer`、`Blob` 等更多类型。更多信息请参考 [MDN 文档](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#setting_a_body)。

### 代理请求

要代理请求，传入一个带有 `proxy` 属性的 URL 字符串：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  proxy: "http://proxy.com",
});
```

你也可以用对象格式发送自定义的代理请求头：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
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` 属性的对象。

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  headers: {
    "X-Custom-Header": "value",
  },
});
```

你也可以使用 [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) 对象设置请求头。

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
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。

#### 流式响应体

你可以使用异步迭代器来流式读取响应体。

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com");

for await (const chunk of response.body) {
  console.log(chunk);
}
```

你也可以直接访问 `ReadableStream` 对象。

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com");

const stream = response.body;

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

### 流式请求体

你也可以使用 `ReadableStream` 来流式传输请求体数据：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
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`：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  signal: AbortSignal.timeout(1000),
});
```

#### 取消请求

要取消请求，使用 `AbortController`：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const controller = new AbortController();

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

controller.abort();
```

### Unix 域套接字

要通过 Unix 域套接字请求 URL，使用 `unix: string` 选项：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
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` 选项：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
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` 选项：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await fetch("https://example.com", {
  tls: {
    checkServerIdentity: (hostname, peerCertificate) => {
      // 如果证书无效，返回一个 Error
    },
  },
});
```

这与 Node 中 `net` 模块的工作方式类似。

#### 禁用 TLS 验证

要禁用 TLS 验证，将 `rejectUnauthorized` 设为 `false`：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await fetch("https://example.com", {
  tls: {
    rejectUnauthorized: false,
  },
});
```

这在使用自签名证书时避免 SSL 错误时尤其有用，但会关闭 TLS 验证，应谨慎使用。

### 请求选项

除了标准的 fetch 选项，Bun 还提供了几个扩展：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
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 桶读取。

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// 使用环境变量提供凭据
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](/runtime/s3) 文档。

#### 本地文件 URL - `file://`

你可以用 `file:` 协议获取本地文件：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("file:///path/to/file.txt");
const text = await response.text();
```

在 Windows 上，路径会自动规范化：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// 以下两者都能在 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 方案：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==");
const text = await response.text(); // "Hello, World!"
```

#### Blob URL - `blob:`

你可以使用 `URL.createObjectURL()` 创建的 URL 来获取 Blob：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
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 的预期行为）
* 同时使用 `proxy` 和 `unix` 选项会抛出错误
* 当 `rejectUnauthorized` 为 true（或未定义）时，TLS 证书验证失败
* S3 操作可能抛出与身份验证或权限相关的特定错误

### Content-Type 处理

当未明确提供时，Bun 会自动为请求体设置 `Content-Type` 头：

* 对于 `Blob` 对象，使用 Blob 的 `type`
* 对于 `FormData`，设置合适的 multipart 边界

## 调试

为了方便调试，你可以给 `fetch` 传入 `verbose: true`：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  verbose: true,
});
```

这将把请求和响应头打印到终端：

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
[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 非常有用。

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dns } from "bun";

dns.prefetch("bun.com");
```

#### DNS 缓存

默认情况下，Bun 会将 DNS 查询的结果缓存并去重，缓存时长最多 30 秒。你可以通过调用 `dns.getCacheStats()` 查看缓存状态。

想了解更多关于 Bun 中的 DNS 缓存，请参阅 [DNS 缓存](/runtime/networking/dns) 文档。

### 预连接到主机

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

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { fetch } from "bun";

fetch.preconnect("https://bun.com");
```

注意：紧跟着调用 `fetch.preconnect` 马上发起 `fetch` 请求不会更快。预连接仅在你知道将很快连接某主机，但尚未准备好发起请求时生效。

#### 启动时预连接

要在启动时预连接某主机，可以传入 `--fetch-preconnect` 参数：

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
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` 增加最大同时连接数：

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
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` 将响应体写入磁盘文件：

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { write } from "bun";

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

### 实现细节

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

注意：许多功能是 Bun 针对标准 fetch API 的特定扩展。
