Bun技术评估 - 03 HTTP Server

概述

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

本文主要探讨的内容,是bun作为一个Web应用开发的系统,是如何实现HTTP Server相关操作的。因为从本质上而言,所有的Web应用开发的核心,就是结合业务的需求和流程,来对HTTP协议的实现和操作。所以,这一部分的内容,也是bun技术的核心,非常重要。

基础服务器

其实,在nodejs中,也提供了一个其实功能非常完整的HTTP协议模块,我们甚至可以认为,我们一般在开发中使用的各种框架,比如express、fastify等等,其实都只是在这个模块上的一个扩展而已。当然,这些框架提供了很多应用层面和通用业务层面的功能,和一些比较好的开发范式,使Web应用的开发更加方便和规范,并且方便业务应用模块的组织、管理和扩展,但从底层和本质上而言,特别是在HTTP协议的层面上,其实这些框架做的工作并不是那么多。

在bun中,道理也是类似的,但笔者感觉和体会是,它在nodejs的基础上,好像是做了更多的工作,提供了更完善的HTTP协议实现。这样,对于简单的Web应用,甚至理论上,可以不用框架,就可以进行架构和实现。

为了方便读者理解这些理念,我们先来看看,bun提供的一个最基本的HTTP服务应用的示例代码:

server.ts 复制代码
Bun.serve({
  port: 8080, // defaults to $BUN_PORT, $PORT, $NODE_PORT otherwise 3000
  hostname: "mydomain.com", // defaults to "0.0.0.0"
  fetch(req) {
    return new Response("404!");
  },
});

// 使用 bun server.ts 执行

从这段代码中,我们可以一窥使用bun开发web应用的一些特点,以及它和nodejs不同的地方:

  • 如果使用bun来执行这个ts文件,可以直接使用一个全局静态对象Bun,无需单独引用和声明
  • Bun可使用serve方法,来创建一个Web端口侦听和服务,相关配置基于一个对象参数
  • 这里参数中的port和hostname属性其实都是可选的,默认是80和"0.0.0.0"
  • 另外可以使用一个名为fetch的参数方法来处理请求,这里的请求对象,会在请求时注入此方法的参数(req)
  • 响应,是通过返回一个新创建的Reponse对象来实现的,在bun中,Response就是响应对象的类,也不需要引用即可以直接使用
  • 响应的内容,就是实例化响应对象时的参数,可以简单的就是一个字符串,当然我们在后面的示例中,可以看到可以有其他的响应选项,如状态码,响应标头,响应的格式等等

这样,在基础上而言,有了这个结构,我们通过扩展fetch方法的功能,就可以实现一个完整的HTTP业务服务的应用程序了。

作为对比,我们也来看看在nodejs中,类似的特性,是如何实现的:

server.mjs 复制代码
import { createServer } from 'node:http';

const server = createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World!\n');
});

// starts a simple http server locally on port 3000
server.listen(3000, '127.0.0.1', () => {
  console.log('Listening on 127.0.0.1:3000');
});

当然,也不可否认,这个结构也很完整和正规,但相比bun的处理而言,稍显繁复。

有了这个基础结构,我们就可以来看看bun作为一个一站式的应用开发体系,如何在HTTP服务方面,进行的扩展。

性能

严格的说来,HTTP服务的性能并不是一个bun的功能特性,但确实相比Nodejs而言是它的一个突出的特点。笔者也想在这里先展示和强调一下。因为,对于一些对性能比较敏感的应用而言,这可能是选择bun技术的一个主要的重要的理由。

我们来看一个简单的对比。下面是两个分别用nodejs和bun编写的测试接口,非常简单,操作和输出内容都是一样的。但为了稍微增加一点计算量和随机化输出内容,需要计算一个hmac,都使用了nodejs的crypto模块。

nodejs版本:

t1.cjs 复制代码
const start = ()=>{
    const { createHmac } = require("crypto");

    require("http")
    .createServer((req, res) => {
        const t =  Date.now().toString(36);
        const rdata = JSON.stringify({ t,
            d: createHmac("SHA256","key").update(t).digest("hex")
        });
        res.writeHead(200, { 'Content-Type': 'application.json' }).end(rdata);
    })
    .listen(7086, '0.0.0.0', () => {
        console.log('Listening on 5001');
    });

}; start();

bun版本:

js 复制代码
const start = ()=>{
    const { createHmac } = require("crypto");

    Bun.serve({
        port: 7086, // defaults to $BUN_PORT, $PORT, $NODE_PORT otherwise 3000
        hostname: "0.0.0.0", // defaults to "0.0.0.0"
        fetch(req) {
            const t =  Date.now().toString(36);
            return Response.json({ t, 
                d: createHmac("SHA256","key").update(t).digest("hex")
            });
        },
    });
}; start();

标准的输出内容如下,两者没什么区别:

curl 复制代码
curl http://192.168.9.192:7086

StatusCode        : 200
StatusDescription : OK
Content           : {123, 34, 116, 34...}
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Keep-Alive: timeout=5
                    Transfer-Encoding: chunked
                    Content-Type: application.json
                    Date: Fri, 06 Jun 2025 04:24:04 GMT

                    {"t":"mbkawc1k","d":"ae0bf06696ef348c...
Headers           : {[Connection, keep-alive], [Keep-Alive, timeout=5], [Transfer-Encoding, chunked], [Content-Type, ap
                    plication.json]...}
RawContentLength  : 87

随后使用autocannon进行简单的压力测试, nodejs的版本结果如下:

js 复制代码
nodejs t1.cjs ...

autocannon -c 300 -d 10 http://192.168.9.192:7086
Running 10s test @ http://192.168.9.192:7086
300 connections

┌─────────┬───────┬───────┬───────┬───────┬──────────┬───────────┬─────────┐
│ Stat    │ 2.5%  │ 50%   │ 97.5% │ 99%   │ Avg      │ Stdev     │ Max     │
├─────────┼───────┼───────┼───────┼───────┼──────────┼───────────┼─────────┤
│ Latency │ 19 ms │ 36 ms │ 52 ms │ 66 ms │ 54.57 ms │ 309.65 ms │ 8283 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴───────────┴─────────┘
┌───────────┬────────┬────────┬─────────┬─────────┬─────────┬──────────┬────────┐
│ Stat      │ 1%     │ 2.5%   │ 50%     │ 97.5%   │ Avg     │ Stdev    │ Min    │
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼──────────┼────────┤
│ Req/Sec   │ 1,470  │ 1,470  │ 6,355   │ 6,839   │ 5,450.9 │ 1,903.15 │ 1,470  │
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼──────────┼────────┤
│ Bytes/Sec │ 384 kB │ 384 kB │ 1.66 MB │ 1.79 MB │ 1.42 MB │ 497 kB   │ 384 kB │
└───────────┴────────┴────────┴─────────┴─────────┴─────────┴──────────┴────────┘

Req/Bytes counts sampled once per second.
# of samples: 10

55k requests in 10.08s, 14.2 MB read

bun的版本测试结果如下

shell 复制代码
autocannon -c 300 -d 10 http://192.168.9.192:7086
Running 10s test @ http://192.168.9.192:7086
300 connections


┌─────────┬──────┬───────┬───────┬───────┬─────────┬─────────┬────────┐
│ Stat    │ 2.5% │ 50%   │ 97.5% │ 99%   │ Avg     │ Stdev   │ Max    │
├─────────┼──────┼───────┼───────┼───────┼─────────┼─────────┼────────┤
│ Latency │ 8 ms │ 16 ms │ 30 ms │ 34 ms │ 17.5 ms │ 6.43 ms │ 125 ms │
└─────────┴──────┴───────┴───────┴───────┴─────────┴─────────┴────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg       │ Stdev  │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼────────┼─────────┤
│ Req/Sec   │ 14,319  │ 14,319  │ 17,023  │ 17,135  │ 16,712.41 │ 818.06 │ 14,313  │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼────────┼─────────┤
│ Bytes/Sec │ 2.99 MB │ 2.99 MB │ 3.56 MB │ 3.58 MB │ 3.49 MB   │ 171 kB │ 2.99 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴────────┴─────────┘

Req/Bytes counts sampled once per second.
# of samples: 10

167k requests in 10.08s, 34.9 MB read

这里顺便说一下,这里虽然看到测试成绩是一万七QPS,但其实笔者使用的测试平台,并不是什么高性能的系统,而是一个实验性的ARM架构的开发环境,操作系统是Ubntuo20.04,内核版本4.4;主CPU是RK3399,内存4G,千兆网卡,而且非常便宜。笔者用这个系统,也是有兴趣来评估一下nodejs和bun的应用在和ARM架构的操作系统上的兼容性的表现,看起来还不错,没有遇到明显的兼容性的问题。

作为对比,和一个简单的概念,相同的测试代码和方法,在另一台Intel i3 7100T / Windows 11的主机上,测得的成绩是平均20345QPS。

看到这里,笔者觉得基本上可以得到一个结论,就是bun确实能够提供比nodejs更好的性能,而且提升的幅度不小,大约在2~3倍左右(符合bun官方网站上的声称的内容)。虽然在实际的应用中,其他的一些因素会造成性能提升的效果不会有如此的明显,但这也是一个非常好的应用基础了。

关于内存占用方面,上面的示例,在启动时会占用不到50M的内存空间。这体现出两者都有很高的内存使用效率,所以笔者就不将资源占用作为性能评估的重点进行讨论了。

有意思的是,在运行期间,笔者还观察到,nodejs会明显的有内存增加的情况,而bun却能够基本保证变化不大。这和一些网络上的说法不太一致,他们的说法是bun比较激进的使用内容缓存,可能相比nodejs会占用比较多的内存资源,起码从笔者的测试过程来看,这个判断是存疑的。如果哪位读者有其他的观察和测试,欢迎质疑和讨论。

静态内容和文件

bun提供了一个有趣的特性,就是可以直接将其当作静态文件Web服务器来使用。这对于现在Web应用,前后端分离的应用场景,是比较友好的。简单的应用,只需要一个Bun应用就可以支撑了,不需要在使用一个额外的前端Web文件服务来承载,后期运维也非常简单方便。

其实,除了文件,还有一些静态的响应内容,都使用类似的方式,例如下面的代码:

js 复制代码
import myReactSinglePageApp from "./index.html";

Bun.serve({
  routes: {
    "/": myReactSinglePageApp,
     
    // Health checks
    "/health": new Response("OK"),
    "/ready": new Response("Ready", {
      headers: {
        // Pass custom headers
        "X-Ready": "1",
      },
    }),

    // Redirects
    "/blog": Response.redirect("https://bun.sh/blog"),

    // API responses
    "/api/config": Response.json({
      version: "1.0.0",
      env: "production",
    }),
    
    // file
    "/hello": return new Response(Bun.file("./hello.txt"));
    
    // Serve a file by buffering it in memory file type
    "/favicon.ico": new Response(await Bun.file("./favicon.ico").bytes(), {
      headers: {
        "Content-Type": "image/x-icon",
      },
    }),
    
    "bigfile": (req)=>{
        // parse `Range` header
        const [start = 0, end = Infinity] = req.headers
          .get("Range") // Range: bytes=0-100
          .split("=") // ["Range: bytes", "0-100"]
          .at(-1) // "0-100"
          .split("-") // ["0", "100"]
          .map(Number); // [0, 100]

        // return a slice of the file
        const bigFile = Bun.file("./big-video.mp4");
        return new Response(bigFile.slice(start, end));
      }
  },
});

我们先不管这里新出现的routes对象参数(会在下个章节展开说明)。 可以看到,Bun提供了很多种响应静态内容的方式。

  • 直接响应文本
  • 文本加标头Headers
  • 重定向,使用Response.redirect静态方法
  • JSON内容,使用Response.json静态方法
  • 读取并输出文件
  • 输出import的内容
  • 输入部分文件内容(典型的流媒体方式)

这里有几个值得注意的问题:

  • 可以不定义响应回调函数,直接使用response对象
  • 可以按需定义响应标头
  • Bun.file方法可以尽可能的使用sendfile系统调用,实现文件数据在内核的零拷贝,来提升文件数据传输速度
  • 根据bun的说法,其import的文件,并不简单的就是当前文件的html文本,而是会加载所有相关的资源,这对于前端 应用的部署而言是非常友好的。但笔者的重点还是在后端,有对前端比较感兴趣的读者,可以自行验证。

路由

习惯于nodejs的开发人员都会了解,虽然nodejs提供了基础的HTTP模块,但用在实际的业务系统中,并不是很方便。他们通常会选择某种"框架"技术,来解决实际的问题,比如常见的express,还有笔者习惯于使用的fastify。如果再深入研究一下,我们应该会发现,这些框架本质上,应该就是使用一些范式,来对nodejs的http基础模块进行了封装,更便于开发应用,提高开发的效率和一致性而已。

经过一段时间的发展,我们还会发现,各种框架完成的工作,和提供的功能,也越来越相似了,说明经过初始的技术和范式的竞争,技术已经能够稳定到一个比较一致的状态,就是这些框架都认为这种模式是比较好的。其中一个很重要的内容就是对于请求路由进行处理。

在这个基础上,我们会发现,bun内置封装了路由的处理,这样我们就可以在不使用外部框架的基础上,可以直接使用内置的路由,来完成应用的开发。所以,bun是非常适合于小型和轻型的Web应用开发的。这在很大程度上适应了现代Web开发技术前后端分离和微服务化的发展趋势。

标准形式

在理解了这个底层逻辑之后,我们来了解一下bun是如何实现HTTP路由处理的,它的简单的标准形式如下:

js 复制代码
import { sql, serve } from "bun";

serve({
  port: 3001,
  routes: {
    "/api/version": async () => {
      const [version] = await sql`SELECT version()`;
      return Response.json(version);
    },
  },
  fetch(req) {
    return new Response("404!");
  },
});

笔者是这样理解bun内置routes技术的:

  • bun通过带有routes属性的参数来构造http服务
  • 注意,此处的routers是一个对象,而非一般理解的一系列对象(数组)
  • 对象中的键,是一个字符串,定义匹配的请求路径
  • 对象中的值,是一个函数,这个函数应当是一个async方法,其返回值,就是需要响应的内容
  • 可以提供一个独立的fetch对象方法,用于处理无法匹配路径的情况,在次序上,也应当是在后面定义的

以上就是bun中路径处理的简单标准形式,当然,如果要用到实际的开发中,需要提供更具扩展性和方便性的设计,这些内容包括模式匹配、匹配参数、请求方法处理等,我们随后展开讨论。

路由匹配

前面的标准路由比较容易理解,就是精确匹配请求的路径,然后执行对应的响应处理方法。但在实际应用开发中,我们更经常遇到的情况是想要处理某种模式化的情况,比如在某个URL路径之下的请求,我们想要将其作为一个业务模块统一进行处理,这时就会使用到通配符匹配的方式,如下面的情况:

js 复制代码
...
// 精确匹配
"/api/status": new Response("OK"),

// 通配符匹配,此处可处理 /api名字空间下,其他的所有情况
"/api/*": Response.json({ message: "Not found" }, { status: 404 }),

匹配参数

如果我们在实现一个Web应用的时候,希望使用REST风格,来组织应用的路径并将其参数化,这时可以使用路径匹配参数,来简化相关的开发工作。下面是一个简单的例子:

js 复制代码
// Dynamic routes
"/users/:id": req => {
  return new Response(`Hello User ${req.params.id}!`);
},

这里的实现分为两个阶段:

  • 首先在定义匹配路径时,可以使用一个带冒号开始的路径区段名称来构成匹配路径的字符串
  • 然后,在匹配的处理方法中,可以使用req.params.变量名,来访问匹配路径中的内容,实现了自动化的参数变量注入
  • 显然,这里的变量名,是对应在定义路径时,使用的字符串的

在上面的例子中,客户端请求的地址如果为 /user/1001, 那么路径中的1001,会被系统自动注入到req.params.id这个变量当中,上面响应的内容,就应该是 "Hello User 1001"。

请求方法

我们在应用开发中,经常使用请求方法来区分不同的通用业务语义。比如使用GET来标识信息查询;使用POST来进行记录的创建和更新;用DELETE来进行信息删除。这就要求,需要在请求处理中区分这些请求方法。

bun在这方面的实现方式,是在路由对象中,在增加一些固定名称的方法对象,来进行处理,比如下面的示例:

js 复制代码
// Per-HTTP method handlers
"/api/posts": {
  GET: () => new Response("List posts"),
  POST: async req => {
    const body = await req.json();
    return Response.json({ created: true, ...body });
  },
},

现在,尚不知道bun是否支持自定义HTTP方法,但通常的HTTP标准方法已经足够使用。

重新加载

bun的http服务,还提供了一个很有趣的特性,就是可以在不重启http服务的情况下,重新加载和定义路由信息。比如下面的示例代码:

js 复制代码
const server = Bun.serve({
  routes: {
    "/api/version": () => Response.json({ version: "1.0.0" }),
  },
});

// Deploy new routes without downtime
server.reload({
  routes: {
    "/api/version": () => Response.json({ version: "2.0.0" }),
  },
});

利用这个特性,我们可以在理论上,构建一个完全动态的Web服务,就是它的服务模式、请求处理和内容响应都可以是动态的。当然笔者尚未为这个特性找到一个特别合适的应用场景。但这无疑对一个需要持续运行的Web应用系统(比如API),来提升服务质量和动态的功能完善,提供了一个很好的基础。

请求参数

HTTP遵循基本而标准的请求响应模型。在实际的业务中,大多数情况下,都需要从请求信息中,获得可变的业务信息,并根据这些信息进行处理。根据HTTP协议的规范,这些可变的请求信息有很多种形式,包括URL、queryString、POST(PUT) body,和Headers等,不同类型的请求参数,可能需要不同形式的处理方法。

  • request

无论是使用router或者fetch作为处理请求的方法,bun都会想其注入request对象作为参数,然后开发者就可以使用这个对象来进行业务处理了。

其实,这个方法,还有第二个被注入的参数,就是当前bun http server这个对象,它可以用于处理一些更基本的和系统相关的信息。比如下面这个例子:

js 复制代码
const server = Bun.serve<{ authToken: string }>({
  fetch(req, server) {
    const success = server.upgrade(req);
    if (success) {
      // Bun automatically returns a 101 Switching Protocols
      // if the upgrade succeeds
      return undefined;
    }

    // handle HTTP request normally
    return new Response("Hello world!");
  },
  websocket: {
    // this is called when a message is received
    async message(ws, message) {
      console.log(`Received ${message}`);
      // send back a message
      ws.send(`You said: ${message}`);
    },
  },
});

熟悉HTTP协议的开发者应当了解到,HTTP协议其实是一个协议族,比如WebSocket就是http协议的"升级"协议,但初始化时还是http基础协议,但服务器可以做一个"升级"操作,告知客户端可以切换到WebSocket协议。这里就需要使用server对象的升级方法来实现了。

这里笔者并不是要讨论server相关的内容,只是想要说明bun有这样一个机制,如果开发者有更底层的需求,也许可以在这方面寻找相关的内容。

  • URL路径

虽然,我们由于长期使用HTTP文件服务器的经验的影响,觉得URL中的路径,是代表这某种层次化的组织逻辑。但实际上,HTTP请求的地址和路径,可以没有任何实际的意义,就是一种标识方式而已。所以,逻辑上可以使用URL来携带请求的参数。当然为了更好的理解和组织,我们将其标识成为路径的方式,就是使用斜杠来区隔不同的参数内容。

在这种情况下,结合bun使用的routes定义,系统可以将这些可变的信息,映射到请求参数当中,这个在前面的例子中,我们已经看到了,它们是被自动封装到req.params对象当中来使用的。

  • queryString

queryString,其实是nodejs的概念和说法。在HTTP中的表述,应该是searchParams(搜索参数)。它是在URL中,除了请求路径之外,增加的另外一种可以表示请求参数的机制。通常在URL字符串中,它的标准形式是(就是以问号?开始的,并使用&分隔的键值对,键值之间用等号分隔):

?参数1=值1&参数2=值2...

和nodejs不同,bun没有专门的queryString模块。它使用标准的URL对象来处理,如下面的代码所示:

ta.ts 复制代码
Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);

    // 获取 query 参数
    const name = url.searchParams.get("name") || "匿名";
    const age = url.searchParams.get("age");

    return new Response(`你好,${name},年龄:${age ?? "未知"}`);
  },
});


// 执行
bun test/ta.ts
Reponse: 你好,颜建,年龄:未知
  • POST/PUT body

可以通过request对象的相关方法,来获取请求的内容。常用的方法包括 text(),formData(),json(),bytes(),等等,需要注意的是这些方法调用的结果都是promise对象,需要在进行await处理,才是实际内容。

  • Headers

其实无论是URL、queryString还是PostBody,它们一般都处理的是和业务操作相关的内容。但实际应用中,我们还需要处理很多和业务可能没有直接关联的"技术性"内容,这时我们可以考虑是header来进行承载,这也是HTTP协议提供的一种标准化的可扩展的机制。

在bun中,使用header也非常简单,就是req的headers属性:

js 复制代码
  ...
  fetch(req) {
    // 获取所有 headers
    const headers = req.headers;
    
    // 获取特定 header
    const userAgent = req.headers.get('user-agent');
    const contentType = req.headers.get('content-type');
    const authorization = req.headers.get('authorization');
    
    // 检查 header 是否存在
    const hasAuth = req.headers.has('authorization');
    
    // 遍历所有 headers
    for (const [name, value] of req.headers.entries()) {
      console.log(`${name}: ${value}`);
    }
    
    return new Response('OK');
  }

http有一套标准的headers的定义和使用模式,作为开发者,一般遵循使用即可。当然,也可以自行扩展,在真实的业务应用系统中,使用自定义的header的场景,就是API请求所使用的TOKEN。

  • Cookie

本质上而言,cookies是一种标准化的headers的扩展应用,就是一种特殊的headers。Cookies在传统的基于页面的Web应用中,使用的比较多,多作为一种维持session和在页面间传递参数的方式,现代化的前后端分离应用中,使用cookie的机会已经不是很多了。所以虽然bun和nodejs都有内置支持cookie的模块,这里也不再展开讨论,读者有兴趣可以自行查阅研究。

  • 客户端参数

除了常规的业务参数之外, 基于安全或者应用运营的考虑,有时候具有需要获取客户端参数的需求。最常见的比如需要获取请求时客户端的IP地址,特别是真实的IP地址,还有比如客户端浏览器的类型(因为可能带有操作系统和版本的信息)。

客户端的类型,在HTTP协议规范中的说法,就是用户代理的类型,这个信息一般会在浏览器提交请求时,自动会在请求headers中的user-agent属性中设置,然后服务器端,就可以取出对应的header内容(前面的header章节已经有所展示)。

另外一个非常常见的需求,是获得客户端的(真实)IP地址,虽然这一需求的有效性,在现代这个移动互联网时代的必要性看起来不是那么重要了,但对于桌面系统,还是有一定需求的。客户端IP地址获取看起来非常简单,就是socket的IP地址,但实际情况可能非常复杂,比如NAT、HTTP代理和Nginx这种反向代理都会影响这一信息的真实性。

我们先说最简单的情况,如果是客户端和服务端是直连的情况,可能需要引入server对象来获取请求地址,示例如下:

js 复制代码
  fetch(req, server) {
    const ip = server.requestIP(req);
    return new Response(`Your IP is ${ip}`);
  },

如果是非直连的方式,比如接口通过反向代理发布,这是客户端请求时,反向代理会在处理的时候,加入一个代表原生请求IP的标头。在服务端也需要加入相应的处理过程,参考代码如下:

js 复制代码
function getClientIP(req) {
  // 优先从 X-Forwarded-For 获取
  const xForwardedFor = req.headers.get('x-forwarded-for');
  if (xForwardedFor) {
    // X-Forwarded-For 可能包含多个 IP,取第一个(最原始的客户端 IP)
    return xForwardedFor.split(',')[0].trim();
  }
  
  // 备选方案
  return req.headers.get('x-real-ip') || 
         req.headers.get('x-client-ip') || 
         'unknown';
}

响应内容

bun中提供了一个Response对象(貌似就是复刻了浏览器中标准的响应对象),可以来作为请求响应的内容。一般是通过创建一个新的response对象,或者是调用其一系列静态方法来进行操作,比如下面的一些示例:

js 复制代码
  fetch(req) {
    // 响应文本 
    return new Response("Hello!!!");
    
       // 响应JSON
    return Response.json({
      version: "1.0.0",
      env: "production",
    }); 
    
    // 响应状态 
    return new Response("Not Found", { status: 404});
       
    // headers
    return new Response("Time", { headers: { x-time: Data.now() } });

    // 文件
    return new Response(await Bun.file("./favicon.ico").bytes(), {
      headers: { "Content-Type": "image/x-icon", },
    });
    
    // 重定向
    return Response.redirect("https://bun.sh/blog");    
    
    // 代理
    return fetch("https://example.com");
  },

从这些代码中,我们也应当可以感受到bun提供的内容响应机制是非常完善丰富,而且容易使用的。

TLS

虽然在比较现代的反向代理+多后端的应用系统当中,直接在Web应用中部署TLS的机会并不是很多,但bun也可以很好的支持TLS。它的使用也非常简单,就是tls参数的定义,包括密钥文件,证书文件和口令等:

js 复制代码
Bun.serve({
  fetch(req) {
    return new Response("Hello!!!");
  },

  tls: {
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
    passphrase: "my-secret-passphrase",
  }
});

笔者想到一个可能的应用场景,就是数据集成的接口,特别是跨互联网的两个应用系统之间,进行基于HTTP调用的数据交互,使用TLS可以比较好的保证数据传输的安全。

错误处理

bun.serve方法可以设置全局的错误处理对象,来统一处理相关错误信息。相关参考代码如下:

js 复制代码
Bun.serve({
  fetch(req) {
    throw new Error("woops!");
  },
  error(error) {
    return new Response(`<pre>${error}\n${error.stack}</pre>`, {
      headers: {
        "Content-Type": "text/html",
      },
    });
  },
});

但笔者认为,上面的机制只应当作为一种托底的方式。用于处理系统运行时的异常错误,而不应当作为标准的业务层级的处理方式。

小结

本文作为系列文章的核心。着重讨论了bun中实现http server的相关方法和细节。包括了创建服务,配置侦听地址和端口,设置默认请求处理方法,设置路由,处理请求对象、参数和内容,多种响应的方式和内容等等,包括了一个实现一个相对比较完整的http服务所涉及到的内容。

经过相关的评估和实践,笔者对于这个评估结果还是相对比较满意的。觉得bun实现了开发一个http框架的绝大多数的应用需求,解决了笔者关心的请求路由处理,URL形式和参数,请求体处理,标头处理,响应状态和标头,响应形式和内容、TLS支持等在内的很多疑问,基本上没有遇到太大的问题。而且从性能、易用性和外部依赖角度而言,比nodejs有比较明显的优势。

相关推荐
江城开朗的豌豆18 分钟前
JavaScript篇:函数间的悄悄话:callee和caller的那些事儿
javascript·面试
江城开朗的豌豆34 分钟前
JavaScript篇:回调地狱退散!6年老前端教你写出优雅异步代码
前端·javascript·面试
TE-茶叶蛋1 小时前
Vue Fragment vs React Fragment
javascript·vue.js·react.js
2302_809798321 小时前
【JavaWeb】Docker项目部署
java·运维·后端·青少年编程·docker·容器
zhojiew2 小时前
关于akka官方quickstart示例程序(scala)的记录
后端·scala
sclibingqing2 小时前
SpringBoot项目接口集中测试方法及实现
java·spring boot·后端
Carlos_sam2 小时前
Opnelayers:封装Popup
前端·javascript
MessiGo3 小时前
Javascript 编程基础(5)面向对象 | 5.1、构造函数实例化对象
开发语言·javascript·原型模式
前端小白从0开始3 小时前
Vue3项目实现WPS文件预览和内容回填功能
前端·javascript·vue.js·html5·wps·文档回填·文档在线预览