> ## 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.

# Fetch

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

Bun 实现了 WHATWG `fetch` 标准，并添加了一些扩展以满足服务端 JavaScript 的需求。

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

## 发送 HTTP 请求

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

```ts theme={null}
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={null}
const response = await fetch("https://example.com");
```

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

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

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

### 发送 POST 请求

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

```ts theme={null}
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 字符串、`URL` 实例，或一个包含 `url` 属性的对象：

```ts theme={null}
const response = await fetch("http://example.com", {
  proxy: "http://proxy.com",
});
```

要向代理服务器发送自定义头部，传递一个对象：

```ts theme={null}
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={null}
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={null}
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>`：返回一个 Promise，解析为 JSON 对象的响应体。
* `response.formData(): Promise<FormData>`：返回一个 Promise，解析为 `FormData` 对象的响应体。
* `response.bytes(): Promise<Uint8Array>`：返回一个 Promise，解析为 `Uint8Array` 的响应体。
* `response.arrayBuffer(): Promise<ArrayBuffer>`：返回一个 Promise，解析为 `ArrayBuffer` 的响应体。
* `response.blob(): Promise<Blob>`：返回一个 Promise，解析为 `Blob` 的响应体。

#### 流式传输响应体

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

```ts theme={null}
const response = await fetch("http://example.com");

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

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

```ts theme={null}
const response = await fetch("http://example.com");

const stream = response.body;

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

### 流式传输请求体

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

```ts theme={null}
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`：

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

#### 取消请求

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

```ts theme={null}
const controller = new AbortController();

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

controller.abort();
```

### Unix 域套接字

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

```ts theme={null}
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={null}
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={null}
await fetch("https://example.com", {
  tls: {
    checkServerIdentity: (hostname, peerCertificate) => {
      // 如果证书无效，返回一个 Error
    },
  },
});
```

此选项类似于 Node 的 `tls` 模块中的类似选项。

#### 禁用 TLS 验证

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

```ts theme={null}
await fetch("https://example.com", {
  tls: {
    rejectUnauthorized: false,
  },
});
```

这样可以避免使用自签名证书时出现 SSL 错误，但会禁用 TLS 验证，请谨慎使用。

### 请求选项

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

```ts theme={null}
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={null}
// 使用环境变量获取凭据
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 会自动使用分片上传来流式传输主体。

参阅 [S3](/runtime/s3) 文档。

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

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

```ts theme={null}
const response = await fetch("file:///path/to/file.txt");
const text = await response.text();
```

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

```ts theme={null}
// 在 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={null}
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={null}
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={null}
const response = await fetch("http://example.com", {
  verbose: true,
});
```

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

```sh theme={null}
[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` 是 Bun 特有的扩展，不是 Web 标准 `fetch` API 的一部分。

## 性能

在发送 HTTP 请求之前，Bun 需要解析 DNS、连接 TCP 套接字，有时还要完成 TLS 握手。每个步骤都需要时间，尤其是在 DNS 速度慢或网络连接差的情况下。请求完成后，消费响应体也需要时间和内存。

Bun 提供了优化每个步骤的 API。

### DNS 预取

当你知道即将连接到某个主机并希望避免初始 DNS 查询时，使用 `dns.prefetch`。

```ts theme={null}
import { dns } from "bun";

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

#### DNS 缓存

默认情况下，Bun 会将 DNS 查询在内存中缓存最多 30 秒，并进行去重。`dns.getCacheStats()` 返回缓存统计信息。

参见 [DNS 缓存](/runtime/networking/dns)。

### 预连接到主机

`fetch.preconnect` 会在你准备好向其发送请求之前，为一个主机启动 DNS 查询、TCP 套接字连接和 TLS 握手。

```ts theme={null}
import { fetch } from "bun";

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

在 `fetch.preconnect` 之后立即调用 `fetch` 不会使你的请求变快。预连接只有在知道主机和发送请求之间存在间隔时才有帮助。

#### 启动时预连接

要在启动时预连接到一个主机，传递 `--fetch-preconnect`：

```sh theme={null}
bun --fetch-preconnect https://bun.com ./my-script.ts
```

`--fetch-preconnect` 类似于 HTML 中的 `<link rel="preconnect">`。在 Windows 上未实现；如果你在 Windows 上需要此功能，请提交 issue。

### 连接池与 HTTP keep-alive

Bun 会自动复用到同一主机的连接。这称为**连接池**，可以显著减少建立连接所需的时间。

#### 并发连接限制

默认情况下，Bun 将并发 `fetch` 请求数量限制为 256，原因有二：

* 它提高了整体系统稳定性。操作系统对并发打开的 TCP 套接字数量有上限，通常在数千以内。接近此限制会导致整台计算机行为异常。应用程序挂起和崩溃。
* 它鼓励 HTTP Keep-Alive 连接复用。对于短生命周期的 HTTP 请求，最慢的步骤通常是初始连接建立。复用连接可以节省大量时间。

当超出限制时，请求会排队，并在下一个请求结束时发送。

要提高限制，设置 `BUN_CONFIG_MAX_HTTP_REQUESTS` 环境变量：

```sh theme={null}
BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun ./my-script.ts
```

此限制的最大值为 65,535。最大端口号是 65,535，因此任何一台计算机都很难超过此限制。

### 响应缓冲

读取响应体最快的方法是使用以下方法之一：

* `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={null}
import { write } from "bun";

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

### 实现细节

* 连接池默认启用，但可以通过 `keepalive: false` 或 `"Connection: close"` 头部为每个请求禁用。
* 大文件上传在特定条件下使用操作系统的 `sendfile` 系统调用进行优化：
  * 文件必须大于 32KB
  * 请求不能使用代理
  * 在 macOS 上，只有普通文件（不是管道、套接字或设备）可以使用 `sendfile`
  * 当这些条件不满足时，或使用 S3/流式上传时，Bun 会回退到将文件读入内存
  * 此优化对于 HTTP（而非 HTTPS）请求尤其有效，文件可以从内核直接发送到网络栈
* S3 操作自动处理签名请求和合并认证头部

其中许多功能是 Bun 对标准 fetch API 的扩展。
