Skip to content

Bun 實現了 WHATWG fetch 標准,並添加了一些擴展以滿足服務器端 JavaScript 的需求。

Bun 也實現了 node:http,但更推薦使用 fetch

發送 HTTP 請求

要發送 HTTP 請求,使用 fetch

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

你也可以傳遞 fetch 一個 Request 對象。

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

const response = await fetch(request);

發送 POST 請求

要發送 POST 請求,傳遞一個對象,將 method 屬性設置為 "POST"

ts
const response = await fetch("http://example.com", {
  method: "POST",
  body: "Hello, world!",
});

body 可以是字符串、FormData 對象、ArrayBufferBlob 等。有關更多信息,請參閱 MDN 文檔

代理請求

要代理請求,傳遞一個對象,將 proxy 屬性設置為 URL 字符串:

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

你也可以使用對象格式向代理服務器發送自定義頭:

ts
const response = await fetch("http://example.com", {
  proxy: {
    url: "http://proxy.com",
    headers: {
      "Proxy-Authorization": "Bearer my-token",
      "X-Custom-Proxy-Header": "value",
    },
  },
});

headersCONNECT 請求(對於 HTTPS 目標)中直接發送到代理,或在代理請求中(對於 HTTP 目標)發送。如果你提供 Proxy-Authorization 頭,它會覆蓋代理 URL 中的任何憑據。

自定義頭

要設置自定義頭,傳遞一個對象,將 headers 屬性設置為對象。

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

你也可以使用 Headers 對象設置頭。

ts
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
const response = await fetch("http://example.com");

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

你也可以更直接地訪問 ReadableStream 對象。

ts
const response = await fetch("http://example.com");

const stream = response.body;

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

流式請求體

你也可以使用 ReadableStream 在請求體中流式傳輸數據:

ts
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
const response = await fetch("http://example.com", {
  signal: AbortSignal.timeout(1000),
});

取消請求

要取消請求,使用 AbortController

ts
const controller = new AbortController();

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

controller.abort();

Unix 域套接字

要使用 Unix 域套接字獲取 URL,使用 unix: string 選項:

ts
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
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
await fetch("https://example.com", {
  tls: {
    checkServerIdentity: (hostname, peerCertificate) => {
      // 如果證書無效則返回 Error
    },
  },
});

這與它在 Node 的 net 模塊中的工作方式類似。

禁用 TLS 驗證

要禁用 TLS 驗證,將 rejectUnauthorized 設置為 false

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

這在使用自簽名證書時特別有用,可以避免 SSL 錯誤,但這會禁用 TLS 驗證,應謹慎使用。

請求選項

除了標准 fetch 選項外,Bun 還提供了幾個擴展:

ts
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
// 使用環境變量獲取憑據
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 文檔中閱讀有關 Bun 的 S3 支持的更多信息。

文件 URL - file://

你可以使用 file: 協議獲取本地文件:

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

在 Windows 上,路徑會自動規范化:

ts
// 在 Windows 上都有效
const response = await fetch("file:///C:/path/to/file.txt");
const response2 = await fetch("file:///c:/path\\to/file.txt");

數據 URL - data:

Bun 支持 data: URL 方案:

ts
const response = await fetch("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==");
const text = await response.text(); // "Hello, World!"

Blob URL - blob:

你可以使用 URL.createObjectURL() 創建的 URL 獲取 blob:

ts
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,設置適當的多部分邊界

調試

為了幫助調試,你可以傳遞 verbose: truefetch

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

這會將請求和響應頭打印到你的終端:

sh
[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
import { dns } from "bun";

dns.prefetch("bun.com");

DNS 緩存

默認情況下,Bun 在內存中緩存和去重 DNS 查詢,最長 30 秒。你可以通過調用 dns.getCacheStats() 查看緩存統計信息:

要了解有關 Bun 中 DNS 緩存的更多信息,請參閱 DNS 緩存 文檔。

預連接到主機

要預連接到主機,你可以使用 fetch.preconnect API。當你知道將來需要連接到主機並希望盡早開始初始 DNS 查找、TCP 套接字連接和 TLS 握手時,此 API 很有用。

ts
import { fetch } from "bun";

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

注意:在 fetch.preconnect 之後立即調用 fetch 不會使你的請求更快。只有當你知道將來需要連接到主機但尚未准備好發出請求時,預連接才有幫助。

啟動時預連接

要在啟動時預連接到主機,傳遞 --fetch-preconnect

sh
bun --fetch-preconnect https://bun.com ./my-script.ts

這有點像 HTML 中的 <link rel="preconnect">

此功能尚未在 Windows 上實現。如果你有興趣在 Windows 上使用此功能,請提交問題,我們可以在 Windows 上實現支持。

連接池和 HTTP keep-alive

Bun 自動重用到相同主機的連接。這稱為連接池。這可以顯著減少建立連接所需的時間。你不需要做任何事情來啟用它;它是自動的。

同時連接限制

默認情況下,Bun 將同時 fetch 請求的最大數量限制為 256。我們這樣做有幾個原因:

  • 它提高了整體系統穩定性。操作系統對同時打開的 TCP 套接字數量有上限,通常在幾千個。接近這個限制會導致你的整個計算機行為異常。應用程序掛起和崩潰。
  • 它鼓勵 HTTP Keep-Alive 連接重用。對於短命的 HTTP 請求,最慢的步驟通常是初始連接設置。重用連接可以節省大量時間。

當超過限制時,請求會排隊並在下一個請求結束時發送。

你可以通過 BUN_CONFIG_MAX_HTTP_REQUESTS 環境變量增加最大同時連接數:

sh
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
import { write } from "bun";

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

實現細節

  • 連接池默認啟用,但可以使用 keepalive: false 按請求禁用。"Connection: close" 頭也可用於禁用 keep-alive。
  • 大文件上傳在特定條件下使用操作系統的 sendfile 系統調用進行優化:
    • 文件必須大於 32KB
    • 請求不能使用代理
    • 在 macOS 上,只有常規文件(不是管道、套接字或設備)可以使用 sendfile
    • 當不滿足這些條件時,或使用 S3/流式上傳時,Bun 會將文件讀入內存
    • 此優化對於 HTTP(不是 HTTPS)請求特別有效,其中文件可以直接從內核發送到網絡棧
  • S3 操作自動處理簽名請求和合並身份驗證頭

注意:許多這些功能是 Bun 對標准 fetch API 的特定擴展。

Bun學習網由www.bunjs.com.cn整理維護