Bun技术评估 - 04 HTTP Client

概述

本文是笔者的系列博文 《Bun技术评估》 中的第四篇。

在上一章节中,我们探讨了作为bun作为一个Web应用开发体系,最重要的对于HTTP协议和服务器操作的能力。在本章节中,我们将探讨其作为HTTP客户端的实现和操作。这个功能特性,在现代Web应用系统越来越复杂,和外部应用的集成越来越频繁的情况下,也是非常重要的。

本章节讨论的主要内容来自bun官方技术文档的相关章节:

bun.sh/docs/api/fe...

基础形式

在nodejs中,http的客户端,也是http模块,通过执行request方法,来实现客户端的功能。但在bun中,http的客户端,几乎就是使用和浏览器中相同的fetch方法。 下面就是其标准应用方式:

js 复制代码
// http get 
const response = await fetch("http://example.com");

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

const text = await response.text(); /

// http post with headers 
const response = await fetch("http://example.com", {
  method: "POST",
  body: "Hello, world!",
  headers: {
    "X-Custom-Header": "value",
  },
});

可以看到,其实现了一般性的HTTP请求,需要控制的项目,包括:

  • 提供请求地址
  • 支持标准HTTP和HTTPS协议(在URL中标识)
  • 可以设置请求方法,如GET(默认)、POST或者任何文本化的方法声明
  • 可以提供请求体,和默认的转换形式
  • 可以设置请求标头,包括标准标头和自定义标头
  • 响应时,检查响应状态
  • 随后,使用转换方法,获取响应内容
  • Cookie,在HTTP协议规范中,本质上就是一种标准标头的应用

笔者没有深入研究现在浏览器中新引入的fetch方法,但感觉上,对于HTTP请求,bun基本上就是复刻了这个fetch方法。

错误处理

根据bun的文档,fetch方法在工作的时候,出现的错误将会使用标准方式抛出,所以,如果需要对错误进行处理,可能应该使用标准的try...catch方式。

响应体转换

前面的代码中,我们可以看到,fetch方法,返回的结果,是一个response对象,基于这个对象,bun内置了很多内置的处理方法,来获取或者转换响应的实际内容,例如:

  • response.text(): 转换响应体为简单文本
  • response.json(): 转换为JSON对象
  • response.formData(): 转换为表单对象
  • response.bytes(): 返回Uint8Array数组
  • response.arrayBuffer(): 返回ArrayBuffer
  • response.blob(): 返回Blob.

这基本上已经可以涵盖所有应用需求的形式。需要注意的是,在实际应用的代码中,这些方法返回的都是promise对象,需要进行await处理。

响应流

除了常见的响应内容之外,fetch其实还支持流化的响应内容。这种方式一般用于更高效的处理比较大的响应结果,如下载文件等场景。使用也非常简单,就是使用chunk来遍历响应的结果体,参考代码如下:

js 复制代码
// support stream
const response = await fetch("http://example.com");

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

// 连接读取流
const response = await fetch("http://example.com");

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

超时和退出

结合AbortSignal和AbortController对象,在fetch可以方便的对HTTP请求的生命周期进行控制:

js 复制代码
// 自动超时
const response = await fetch("http://example.com", {
  signal: AbortSignal.timeout(1000),
});

// 可控撤销
const controller = new AbortController();
const response = await fetch("http://example.com", {
  signal: controller.signal,
});

// in some case 
controller.abort();

这里面展示了两个场景,就是设置超时时间,和主动中断请求。其实使用后者,结合一个定时器,也应该可以实现超时中断,或者条件中断的效果。

客户端认证

在HTTP请求响应需要双向认证的场景中,比如两个应用系统之间进行点对点的集成,为了保证更高的安全性,这一特性是非常必要的。使用也非常简单:

js 复制代码
// 客户端配置
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")],
    checkServerIdentity: (hostname, peerCertificate) => {
      // Return an Error if the certificate is invalid
    },
  },
});

// 省略SSL验证,常用于开发和测试环境
await fetch("https://example.com", {
  tls: {
    rejectUnauthorized: false,
  },
});

扩展协议

这个特性是比较令人满意的,除了标准的HTTP和HTTPS之外,bun内置的fetch还支持多种扩展协议。

  • Unix Socket

我们都知道,在类Unix系统中,HTTP网络端口和Unix Sock端口基本是等效的。所以,理论如果有端口输出文件,可以使用类似的方式来使用(比如PHP-FPM就可以以Sock文件方式工作)。官方的示例是这样的:

js 复制代码
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",
  },
});

但实际上,笔者对此是有点疑问的。这种情况下,它使用了一个unix属性来指定端口文件,这时其实不应该设置默认的url地址;还有就是其实可以不用unix属性,而使用这种模式的url: "unix://path/sockfile" ,可能更加优雅简洁。

  • S3

fetch支持标准S3对象存储协议和其方法。应该也支持S3兼容的系统,如minio。

js 复制代码
// 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",
  },
});

// put 对象
import { createReadStream } from "fs";
import { stat } from "fs/promises";

const filePath = "./test.jpg";
const fileStream = createReadStream(filePath);
const fileStat = await stat(filePath);

// 预签名 URL 或直连 S3 端点(带权限)
const uploadUrl = "https://your-s3-endpoint.com/bucket-name/test.jpg";

const res = await fetch(uploadUrl, {
  method: "PUT",
  body: fileStream, // 数据流
  headers: {
    "Content-Type": "image/jpeg",
    "Content-Length": fileStat.size.toString(), // 非必须,但建议带上
    // "Authorization": "..." // 如果没有预签名 URL,需要签名
  },
});

if (res.ok) {
  console.log("✅ 上传成功");
} else {
  console.error("❌ 上传失败", res.status, await res.text());
}

需要说明,这个特性,笔者现在尚没有机会实践,但从代码不难理解其操作逻辑。对于大量使用文件进行集成的应用系统,这是一个非常重要的特性。

  • File

可以使用一种通用的方式,处理来自网络和本地的文件的内容:

js 复制代码
const response = await fetch("file:///path/to/file.txt");
const text = await response.text();
  • Data URL
js 复制代码
const response = await fetch("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==");
const text = await response.text(); // "Hello, Worl
  • Blog URL
js 复制代码
const blob = new Blob(["Hello, World!"], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const response = await fetch(url);

性能和选项

bun的fetch使用了很多方式来提升请求的性能,并增加了一些应用方面的选项,其中笔者觉得值得一提的如下:

  • DNS prefetching, DNS预取

下面的代码,用于实现DNS的预取,可以在实际请求前操作,并缓存一段时间,节省真实请求时DNS查找的时间。

js 复制代码
import { dns } from "bun";
dns.prefetch("bun.sh");
  • Preconnect to a host

主机预连接,可以在实际连接前准备一下,这些准备工作包括DNS查询,TCP Socket连接和早期的TLS握手,用于简化随后请求的过程。

js 复制代码
import { fetch } from "bun";
fetch.preconnect("https://bun.sh");
  • Preconnect at startup

启动预连接,可以在启动时设计,简化在程序中请求连接的过程。

  • Simultaneous connection 并发

在一个应用中,默认支持256个并发的fetch请求操作。

  • 调试

请求时,设置debug: true 调试选项,可以在控制台展示整个请求过程的详细信息。

小结

本文总结和讨论了在bun中,操作和实现HTTP应用的另一个重要的部分:Client,其核心是fetch方法。文章列举了其一般方法和标准形式,请求选项。响应内容的处理,TLS应用,错误和超时,扩展协议,性能优化等方面的内容。覆盖了比较完整的请求响应生命周期的处理和管理。

相关推荐
_r0bin_13 分钟前
前端面试准备-7
开发语言·前端·javascript·fetch·跨域·class
IT瘾君14 分钟前
JavaWeb:前端工程化-Vue
前端·javascript·vue.js
zhang988000014 分钟前
JavaScript 核心原理深度解析-不停留于表面的VUE等的使用!
开发语言·javascript·vue.js
恸流失16 分钟前
DJango项目
后端·python·django
Mr Aokey3 小时前
Spring MVC参数绑定终极手册:单&多参/对象/集合/JSON/文件上传精讲
java·后端·spring
地藏Kelvin3 小时前
Spring Ai 从Demo到搭建套壳项目(二)实现deepseek+MCP client让高德生成昆明游玩4天攻略
人工智能·spring boot·后端
拉不动的猪4 小时前
都25年啦,还有谁分不清双向绑定原理,响应式原理、v-model实现原理
前端·javascript·vue.js
菠萝014 小时前
共识算法Raft系列(1)——什么是Raft?
c++·后端·算法·区块链·共识算法
狂炫一碗大米饭4 小时前
一文打通TypeScript 泛型
前端·javascript·typescript
长勺4 小时前
Spring中@Primary注解的作用与使用
java·后端·spring