Fetch
使用 Bun 的 fetch API 傳送 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(); // 或者 response.json(), response.formData() 等等
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 物件、ArrayBuffer、Blob 或 MDN 文件中列出的其他請求體型別。
代理請求
要代理請求,傳入一個物件,並將 proxy 屬性設定為 URL 字串、URL 例項,或將其設定為一個 url 為字串或 URL 的物件:
const response = await fetch("http://example.com", {
proxy: "http://proxy.com",
});
要向代理伺服器傳送自定義請求頭,請傳入一個物件:
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.com | example.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 則可強制直接連線,不受環境影響:
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。
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 屬性的物件。
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,
});
除非請求本身設定了 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。
流式響應體
你可以使用非同步迭代器來流式讀取響應體。
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) 協議時的流式傳輸要點:
- Bun 會將資料直接流式傳輸到網路,不會將整個請求體緩衝到記憶體中
- 如果連線斷開,Bun 會取消該流
- 只有當流的大小已知時,Bun 才會自動設定
Content-Length請求頭
使用 S3 時:
- 對於 PUT/POST 請求,Bun 會自動使用分段上傳
- Bun 會分塊讀取該流,並行上傳各個資料塊
- 你可以通過 S3 選項監控進度
帶超時的 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) => {
// 如果證書無效,傳回一個 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:
await fetch("https://example.com", {
tls: {
rejectUnauthorized: false,
},
});
這可以避免使用自簽名證書時出現 SSL 錯誤,但會停用 TLS 驗證,因此請謹慎使用。
請求選項
除了標準的 fetch 選項,Bun 還提供了幾個擴充套件:
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 桶讀取。
// 使用環境變數提供憑據
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: 協議獲取本地檔案:
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 會自動規範化路徑:
// 以下兩者都能在 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 的預期行為)
- 同時使用
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:
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.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。
import { dns } from "bun";
dns.prefetch("bun.com");
DNS 快取
預設情況下,Bun 會在記憶體中快取並去重 DNS 查詢,最長快取 30 秒。dns.getCacheStats() 會傳回快取統計資訊。
參見 DNS 快取。
預連線到主機
fetch.preconnect 會在你準備向某個主機發送請求之前,啟動 DNS 查詢、TCP 套接字連線和 TLS 握手。
import { fetch } from "bun";
fetch.preconnect("https://bun.sh");
在 fetch.preconnect 後立即呼叫 fetch 並不會讓請求更快。只有在確定主機和傳送請求之間存在時間間隔時,預連線才能發揮作用。
啟動時預連線
要在啟動時預連線到某個主機,請傳入 --fetch-preconnect:
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 之間共享連線。
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。 |
keepAlive | false 會在每個連線收到響應後將其關閉。idleTimeout 的單位為秒(預設值為 300)。maxIdleSockets 限制每種型別(普通連線、TLS、Unix 套接字)的空閒連線數。這兩個限制均不適用於 HTTP/3 連線。 |
unix | Unix 套接字路徑。 |
請求中給出的選項優先於 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 驗證證書。
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 環境變數:
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 將響應體寫入磁碟檔案:
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 提供的專屬擴充套件。