執行環境套件管理器打包器測試執行器程式碼檢查格式化指南參考部落格意見回饋

Fetch

使用 Bun 的 fetch API 傳送 HTTP 請求

Bun 實現了 WHATWG 的 fetch 標準,同時進行了擴充套件以滿足伺服器端 JavaScript 的需求。

Bun 也實現了 node:http,但我們通常建議使用 fetch。

傳送 HTTP 請求

要傳送 HTTP 請求,請使用 fetch:

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

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

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

fetch 也支援 HTTPS URL。

TypeScript
const response = await fetch("https://example.com");

你也可以傳遞一個 Request 物件給 fetch。

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

const response = await fetch(request);

傳送 POST 請求

要傳送 POST 請求,傳入一個 method 屬性值為 "POST" 的物件。

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

body 可以是字串、FormData 物件、ArrayBuffer、Blob 或 MDN 文件中列出的其他請求體型別。

代理請求

要代理請求,傳入一個物件,並將 proxy 屬性設定為 URL 字串、URL 例項,或將其設定為一個 url 為字串或 URL 的物件:

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

要向代理伺服器傳送自定義請求頭,請傳入一個物件:

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

對於 HTTPS 目標,Bun 會在 CONNECT 請求中將 headers 直接傳送給代理;對於 HTTP 目標,則會在代理請求中傳送。如果你提供了 Proxy-Authorization 請求頭,它會覆蓋代理 URL 中的任何憑據。

如果沒有 proxy 選項,Bun 會在每次執行 fetch 時讀取 HTTP_PROXY、HTTPS_PROXY 和 ALL_PROXY(大寫或小寫),因此對 process.env 的更改會應用於下一個請求。如果未設定特定於協議的變數,Bun 會對 http: 和 https: URL 一律使用 ALL_PROXY。如果值指定了其他型別的代理(例如 socks5://),Bun 會忽略它。curl 也會讀取 ALL_PROXY;Node.js 則不會。帶有 unix 選項的請求永遠不會使用代理環境變數。

no_proxy 列出繞過代理的主機。與 curl 和 Node.js 一樣,當 no_proxy 未設定或為空時,Bun 會讀取 NO_PROXY。條目之間以逗號或空格分隔。

條目匹配物件
*所有主機
example.com、.example.com、*.example.comexample.com 及其子域名
127.0.0.1、::1、[::1]該 IP 地址
10.0.0.0/8、fd00::/8該地址塊中的所有 IP 地址
example.com:8080僅該主機的該埠

NO_PROXY 也適用於顯式指定的 proxy。設定 respectNoProxy: false 可強制通過代理傳送請求;設定 proxy: false 則可強制直接連線,不受環境影響:

TypeScript
await fetch("https://example.com", {
  proxy: { url: "http://proxy.com", respectNoProxy: false },
});

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

當代理對 CONNECT 的響應狀態碼不是 2xx 時,fetch 會以 ERR_PROXY_TUNNEL 錯誤拒絕。該錯誤包含代理的 status、statusText 和 headers。

TypeScript
try {
  await fetch("https://example.com", { proxy: "http://proxy.com" });
} catch (error) {
  if (error.code === "ERR_PROXY_TUNNEL") {
    console.log(error.status, error.headers.get("proxy-authenticate"));
  }
}

自定義請求頭

要設定自定義請求頭,傳入一個帶有 headers 屬性的物件。

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

你也可以使用 Headers 物件設定請求頭。

TypeScript
const headers = new Headers();
headers.append("X-Custom-Header", "value");

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

除非請求本身設定了 Authorization 請求頭,否則 Bun 會將 URL 中的憑據(https://user:password@example.com/)作為 Basic Authorization 請求頭髮送。Node.js 的 fetch 則會拒絕此類 URL。連線錯誤的 path 不會包含憑據。

響應體

要讀取響應體,使用以下方法之一:

  • 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。

流式響應體

你可以使用非同步迭代器來流式讀取響應體。

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

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

你也可以直接訪問 ReadableStream。

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

const stream = response.body;

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

流式請求體

你也可以使用 ReadableStream 來流式傳輸請求體資料:

TypeScript
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) 協議時的流式傳輸要點:

  • Bun 會將資料直接流式傳輸到網路,不會將整個請求體緩衝到記憶體中
  • 如果連線斷開,Bun 會取消該流
  • 只有當流的大小已知時,Bun 才會自動設定 Content-Length 請求頭

使用 S3 時:

  • 對於 PUT/POST 請求,Bun 會自動使用分段上傳
  • Bun 會分塊讀取該流,並行上傳各個資料塊
  • 你可以通過 S3 選項監控進度

帶超時的 URL 請求

要帶超時傳送請求,使用 AbortSignal.timeout:

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

取消請求

要取消請求,使用 AbortController:

TypeScript
const controller = new AbortController();

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

controller.abort();

Unix 域套接字

要通過 Unix 域套接字請求 URL,使用 unix: string 選項:

TypeScript
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 選項:

TypeScript
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 選項:

TypeScript
await fetch("https://example.com", {
  tls: {
    checkServerIdentity: (hostname, peerCertificate) => {
      // 如果證書無效,傳回一個 Error
    },
  },
});

此選項類似於 Node 的 tls 模組中的選項。Bun 會在驗證證書鏈之後呼叫它。即使設定了 rejectUnauthorized: false,Bun 仍會呼叫它,並忽略其傳回值。

該函式會取代 Bun 自己的主機名檢查。傳回 undefined 表示接受證書。要拒絕證書,請傳回一個 Error 或丟擲一個錯誤:fetch() promise 會以該錯誤拒絕。與 Node 一樣,任何其他真值傳回值也會導致請求失敗。物件會原樣作為拒絕原因。傳回 Promise 或 true 之類的基本型別會導致 ERR_INVALID_RETURN_VALUE 錯誤,因此非同步函式不會接受任何證書。與 Node 一樣,所有假值傳回值都會接受證書,因此 false 不會拒絕證書。出於這個原因,TypeScript 型別只允許 undefined 和 Error。

checkServerIdentity 必須是一個函式。null 和 undefined 表示沒有函式。對於任何其他值,fetch() 都會以 ERR_INVALID_ARG_TYPE 錯誤拒絕。

通過自身 checkServerIdentity 檢查的請求會開啟自己的連線,並在之後關閉連線,因此其他請求的函式永遠不會替它背書。若要每個連線只驗證一次並複用該連線,請將函式設定在 Bun.FetchSession 上。

停用 TLS 驗證

要停用 TLS 驗證,將 rejectUnauthorized 設為 false:

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

這可以避免使用自簽名證書時出現 SSL 錯誤,但會停用 TLS 驗證,因此請謹慎使用。

請求選項

除了標準的 fetch 選項,Bun 還提供了幾個擴充套件:

TypeScript
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 桶讀取。

TypeScript
// 使用環境變數提供憑據
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 文件。

本地檔案 URL - file://

你可以用 file: 協議獲取本地檔案:

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

URL 的主機必須為空或為 localhost。fetch("file://other.host/path") 會以 ERR_INVALID_FILE_URL_HOST 錯誤拒絕。

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

TypeScript
// 以下兩者都能在 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 方案:

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

Blob URL - blob:

你可以使用 URL.createObjectURL() 建立的 URL 來獲取 Blob:

TypeScript
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 選項會丟擲錯誤
  • 連線失敗時會以 TypeError 拒絕,且其 code 為套接字錯誤(ECONNREFUSED、ECONNRESET、ETIMEDOUT、ENOTFOUND 等)。訊息以該錯誤程式碼開頭。
  • 當 rejectUnauthorized 為 true(或未定義)時,TLS 證書驗證失敗
  • S3 操作可能會丟擲與身份驗證或許可權相關的特定錯誤

Content-Type 處理

當未明確提供時,Bun 會自動為請求體設定 Content-Type 頭:

  • 對於 Blob 物件,使用 Blob 的 type
  • 對於 FormData,設定合適的 multipart 邊界

除錯

要進行除錯,請將 verbose: true 傳遞給 fetch:

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

這會將請求和響應標頭列印到終端:

shell
[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。

TypeScript
import { dns } from "bun";

dns.prefetch("bun.com");

DNS 快取

預設情況下,Bun 會在記憶體中快取並去重 DNS 查詢,最長快取 30 秒。dns.getCacheStats() 會傳回快取統計資訊。

參見 DNS 快取。

預連線到主機

fetch.preconnect 會在你準備向某個主機發送請求之前,啟動 DNS 查詢、TCP 套接字連線和 TLS 握手。

TypeScript
import { fetch } from "bun";

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

在 fetch.preconnect 後立即呼叫 fetch 並不會讓請求更快。只有在確定主機和傳送請求之間存在時間間隔時,預連線才能發揮作用。

啟動時預連線

要在啟動時預連線到某個主機,請傳入 --fetch-preconnect:

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

--fetch-preconnect 類似於 HTML 中的 <link rel="preconnect">。Windows 尚未實現此功能;如果你需要在 Windows 上使用,請提交 issue。

連線池與 HTTP 長連線

Bun 會自動複用連線到同一主機的連線。這稱為連線池,可以顯著減少建立連線所需的時間。

Fetch sessions

Bun.FetchSession 儲存連線設定並擁有一個 Keep-Alive 連線池。session.fetch 是使用該 session 的 fetch,且已繫結,因此可用於任何接受 fetch 函式的位置(new SomeClient({ fetch: session.fetch }))。它沒有 preconnect。fetch(url, { session }) 通過全域性 fetch 實現相同功能。Bun 絕不會在兩個 session 之間,或 session 與普通 fetch 之間共享連線。

TypeScript
const session = new Bun.FetchSession({
  tls: { ca: await Bun.file("corp-ca.pem").text() },
  proxy: { url: "http://proxy.internal:8080", respectNoProxy: false },
  keepAlive: { idleTimeout: 30, maxIdleSockets: 8 },
});

const response = await session.fetch("https://example.com");
session.close(); // closes the idle connections
選項描述
tls與 fetch 的 tls 相同的選項。checkServerIdentity 每個連線執行一次。
proxy與 fetch 的 proxy 相同的值,包括 false。
keepAlivefalse 會在每個連線收到響應後將其關閉。idleTimeout 的單位為秒(預設值為 300)。maxIdleSockets 限制每種型別(普通連線、TLS、Unix 套接字)的空閒連線數。這兩個限制均不適用於 HTTP/3 連線。
unixUnix 套接字路徑。

請求中給出的選項優先於 session 的選項,包括 proxy: false。請求的 tls 會整體替換 session 的 tls。

如果建構函式遇到無法使用的 proxy 或 tls.checkServerIdentity,例如 proxy: 8080、缺少 url 的 proxy 物件,或不是函式的 checkServerIdentity,就會丟擲錯誤。對於 proxy,只有 undefined、null 和 "" 表示未設定選項。在請求中,fetch 會忽略此類 proxy(true 會丟擲錯誤),並保留其 session 或環境中的代理設定。

Bun 會將 idleTimeout 向上取整到套接字計時器的間隔;該間隔在四分鐘內以 4 秒為步長遞增,之後以整分鐘為步長遞增。連線絕不會在 idleTimeout 之前關閉,且最多可能比該時間多保持兩個步長。idleTimeout 最長為 238 分鐘。更大的值會按 238 分鐘處理。

如果沒有設定 maxIdleSockets,session 自身沒有連線數限制。所有 session 和普通 fetch 的空閒連線會按型別共享一個有上限的連線池。連線池已滿時,Bun 會關閉其中空閒時間最長的連線,無論它屬於哪個 session。高於連線池大小的 maxIdleSockets 不會產生任何效果。

連線到特定地址

要將發往某個主機名的請求傳送到你自行解析並檢查過的地址,請將該地址放入 URL,並在 Host 請求頭和 tls.serverName 中指定源站名稱。Bun 會連線該地址,傳送 Host(在 HTTP/2 中作為 :authority),使用 serverName 進行 SNI,並根據 serverName 驗證證書。

TypeScript
import { lookup } from "node:dns/promises";

const { address, family } = await lookup("example.com");
if (isPrivateAddress(address)) throw new Error(`refusing to connect to ${address}`);

const response = await fetch(`https://${family === 6 ? `[${address}]` : address}/webhook`, {
  headers: { Host: "example.com" },
  tls: { serverName: "example.com" },
  proxy: false,
  redirect: "manual",
});
  • 傳入 proxy: false。否則,Bun 會根據 URL 中的地址而非主機名判斷環境代理和 NO_PROXY。請在解析主機名之前決定是否需要代理。
  • 自行跟隨重定向(redirect: "manual"):Host 和 serverName 僅描述此源站,相對 Location 會基於該地址解析。
  • 如果一個名稱對應多個地址,此處不會回退嘗試其他地址。請自行逐個嘗試。
  • Bun 會按原樣傳送 Host。如果埠不是該協議的預設埠,請包含埠。普通的 http:// URL 只需要 Host。
  • tls.serverName 適用於 HTTP/1.1 和 HTTP/2。protocol: "http3" 不接受該選項。
  • 每個不同的 serverName 都有自己的 TLS 上下文和 Keep-Alive 連線池。Bun 最多快取 60 個。

同時連線數限制

預設情況下,Bun 將同時進行的 fetch 請求數限制為 256,原因有二:

  • 改善系統整體穩定性。作業系統對同時開啟的 TCP 套接字數有上限,通常在幾千左右。接近該限制時,會導致整臺電腦異常,應用掛起或崩潰。
  • 鼓勵 HTTP Keep-Alive 的連線複用。對於短時 HTTP 請求,最慢的步驟往往是初次連線。連線複用可節省大量時間。

超過限制時,Bun 會將請求排入佇列,並在下一個請求結束後立即傳送。

要提高該限制,請設定 BUN_CONFIG_MAX_HTTP_REQUESTS 環境變數:

shell
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 將響應體寫入磁碟檔案:

TypeScript
import { write } from "bun";

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

實現細節

  • 預設啟用連線池。你可以通過 keepalive: false 或 "Connection: close" 請求頭針對單個請求停用連線池。
  • 在特定條件下,Bun 使用作業系統的 sendfile 系統呼叫最佳化大檔案上傳:
    • 檔案必須大於 32KB
    • 請求不能使用代理或 compress 選項
    • 在 macOS 上,只有常規檔案(非管道、套接字或裝置)可以使用 sendfile
    • 不滿足這些條件,或使用 S3/流式上傳時,Bun 會改為將檔案讀入記憶體
    • Bun 僅對 HTTP(非 HTTPS)請求使用此最佳化,此時檔案可直接從核心傳送到網路堆疊;Bun 在 Windows 上不使用此最佳化
  • S3 操作會自動處理請求籤名併合並身份驗證請求頭

其中許多功能是 Bun 針對標準 fetch API 提供的專屬擴充套件。