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(); // or response.json(), response.formData(), etc.
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。
const response = await fetch("http://example.com", {
proxy: "http://proxy.com",
});
自訂標頭
若要設定自訂標頭,請傳遞一個物件,並將 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,
});
回應本文
若要讀取回應本文,請使用下列方法之一
response.text(): Promise<string>
:傳回一個 Promise,該 Promise 會解析為字串格式的回應本文。response.json(): Promise<any>
:傳回一個 Promise,該 Promise 會解析為 JSON 物件格式的回應本文。response.formData(): Promise<FormData>
:傳回一個 Promise,該 Promise 會解析為FormData
物件格式的回應本文。response.bytes(): Promise<Uint8Array>
:傳回一個 Promise,該 Promise 會解析為Uint8Array
格式的回應本文。response.arrayBuffer(): Promise<ArrayBuffer>
:傳回一個 Promise,該 Promise 會解析為ArrayBuffer
格式的回應本文。response.blob(): Promise<Blob>
:傳回一個 Promise,該 Promise 會解析為Blob
格式的回應本文。
串流回應 body
你可以使用非同步迭代器來串流回應 body。
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();
串流請求 body
你也可以在請求 body 中使用 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) 使用串流時
- 資料會直接串流到網路,而不會將整個 body 緩衝在記憶體中
- 如果連線中斷,串流將會被取消
- 除非串流具有已知大小,否則
Content-Length
header 不會自動設定
當搭配 S3 使用串流時
- 對於 PUT/POST 請求,Bun 會自動使用 multipart 上傳
- 串流會以區塊方式消耗並平行上傳
- 進度可以透過 S3 選項監控
使用逾時時間擷取 URL
若要使用逾時時間擷取 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) => {
// Return an Error if the certificate is invalid
},
},
});
這與 Node 的 net
模組中的運作方式相似。
停用 TLS 驗證
若要停用 TLS 驗證,請將 rejectUnauthorized
設定為 false
await fetch("https://example.com", {
tls: {
rejectUnauthorized: false,
},
});
這在搭配自簽憑證時尤其有助於避免 SSL 錯誤,但這會停用 TLS 驗證,應謹慎使用。
請求選項
除了標準的 fetch 選項外,Bun 還提供了幾個擴充功能
const response = await fetch("http://example.com", {
// Control automatic response decompression (default: true)
decompress: true,
// Disable connection reuse for this request
keepalive: false,
// Debug logging level
verbose: true, // or "curl" for more detailed output
});
協定支援
除了 HTTP(S) 之外,Bun 的 fetch 還支援幾個額外的協定
S3 URL - s3://
Bun 支援直接從 S3 儲存貯體擷取。
// Using environment variables for credentials
const response = await fetch("s3://my-bucket/path/to/object");
// Or passing credentials explicitly
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 方法支援請求 body。對於上傳,Bun 會自動使用 multipart 上傳來串流 body。
你可以在 S3 文件中閱讀更多關於 Bun 的 S3 支援。
檔案 URL - file://
你可以使用 file:
協定擷取本機檔案
const response = await fetch("file:///path/to/file.txt");
const text = await response.text();
在 Windows 上,路徑會自動標準化
// Both work on 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 實作包含幾個特定的錯誤情況
- 將請求 body 與 GET/HEAD 方法一起使用會拋出錯誤(這是 fetch API 預期的行為)
- 嘗試同時使用
proxy
和unix
選項會拋出錯誤 - 當
rejectUnauthorized
為 true(或未定義)時,TLS 憑證驗證失敗 - S3 操作可能會拋出與身份驗證或權限相關的特定錯誤
Content-Type 處理
當未明確提供時,Bun 會自動為請求 body 設定 Content-Type
header
- 對於
Blob
物件,使用 blob 的type
- 對於
FormData
,設定適當的 multipart boundary - 對於 JSON 物件,設定
application/json
偵錯
為了協助偵錯,你可以將 verbose: true
傳遞給 fetch
const response = await fetch("http://example.com", {
verbose: true,
});
這會將請求和回應 header 印到你的終端機
[fetch] > HTTP/1.1 GET http://example.com/
[fetch] > Connection: keep-alive
[fetch] > User-Agent: Bun/1.2.5
[fetch] > Accept: */*
[fetch] > Host: example.com
[fetch] > Accept-Encoding: gzip, deflate, br
[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 socket,並且可能需要執行 TLS handshake。這也可能會花費大量時間。
在請求完成後,消耗回應 body 也可能會花費大量時間和記憶體。
在每個步驟中,Bun 都提供了 API 來協助你最佳化應用程式的效能。
DNS 預先擷取
若要預先擷取 DNS 項目,你可以使用 dns.prefetch
API。當你知道你很快需要連線到主機並想要避免初始 DNS 查詢時,此 API 非常有用。
import { dns } from "bun";
dns.prefetch("bun.sh");
DNS 快取
預設情況下,Bun 會將 DNS 查詢快取並去重複化在記憶體中最多 30 秒。你可以透過呼叫 dns.getCacheStats()
來查看快取統計資訊
若要了解更多關於 Bun 中的 DNS 快取,請參閱 DNS 快取 文件。
預先連線到主機
若要預先連線到主機,你可以使用 fetch.preconnect
API。當你知道你很快需要連線到主機並想要提早開始初始 DNS 查詢、TCP socket 連線和 TLS handshake 時,此 API 非常有用。
import { fetch } from "bun";
fetch.preconnect("https://bun.dev.org.tw");
注意:在 fetch.preconnect
之後立即呼叫 fetch
不會使你的請求更快。預先連線只有在你確定很快需要連線到主機,但你尚未準備好發出請求時才有幫助。
啟動時預先連線
若要在啟動時預先連線到主機,你可以傳遞 --fetch-preconnect
bun --fetch-preconnect https://bun.dev.org.tw ./my-script.ts
這有點像 HTML 中的 <link rel="preconnect">
。
此功能尚未在 Windows 上實作。如果你有興趣在 Windows 上使用此功能,請提交 issue,我們可以實作對 Windows 的支援。
連線池 & HTTP keep-alive
Bun 會自動重複使用與相同主機的連線。這稱為連線池。這可以顯著減少建立連線所需的時間。你不需要做任何事來啟用此功能;它是自動的。
同時連線限制
預設情況下,Bun 將同時 fetch
請求的最大數量限制為 256。我們這樣做有幾個原因
- 它提高了整體系統穩定性。作業系統對同時開啟的 TCP socket 數量有一個上限,通常在數千個左右。接近此限制會導致你的整部電腦行為異常。應用程式會掛起和崩潰。
- 它鼓勵 HTTP Keep-Alive 連線重複使用。對於生命週期短暫的 HTTP 請求,最慢的步驟通常是初始連線設定。重複使用連線可以節省大量時間。
當超出限制時,請求會排隊,並在下一個請求結束後立即傳送。
你可以透過 BUN_CONFIG_MAX_HTTP_REQUESTS
環境變數增加同時連線的最大數量
BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun ./my-script.ts
此限制的最大值目前設定為 65,336。最大 port 號碼為 65,535,因此任何一部電腦都很難超過此限制。
回應緩衝
Bun 竭盡全力最佳化讀取回應 body 的效能。讀取回應 body 最快的方式是使用以下其中一種方法
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
將回應 body 寫入磁碟上的檔案
import { write } from "bun";
await write("output.txt", response);
實作細節
- 連線池預設為啟用,但可以使用
keepalive: false
針對每個請求停用。"Connection: close"
header 也可用於停用 keep-alive。 - 在特定條件下,大型檔案上傳會使用作業系統的
sendfile
syscall 進行最佳化- 檔案必須大於 32KB
- 請求不得使用 proxy
- 在 macOS 上,只有一般檔案(而非 pipes、sockets 或 devices)可以使用
sendfile
- 當不符合這些條件,或使用 S3/串流上傳時,Bun 會回退到將檔案讀取到記憶體中
- 此最佳化對於 HTTP(而非 HTTPS)請求尤其有效,在這種情況下,檔案可以直接從核心傳送到網路堆疊
- S3 操作會自動處理簽署請求和合併身份驗證 header
注意:這些功能中有許多是 Bun 針對標準 fetch API 的特定擴充功能。