NGINX.COM
Web Server Load Balancing with NGINX Plus

如果您有兴趣开始使用 WebAssembly 组件模型,但面对庞大的生态系统不知从何处着手吗?如果是,那么本文正适合您!

在本文中,我们将分享一些经验和心得,它们是我们在向 NGINX Unit 中添加对 WebAssembly 组件模型的支持时所获得的——感谢我们强大而活跃的社区。

如果您已经熟悉 Wasm 生态系统,或者只是想了解如何编写代码,请直接跳转到本系列博文的第二篇

 

WebAssembly 组件模型和 NGINX Unit

在推出第一版 Unit Wasm 语言模块之后,我们做了许多工作。在 2023 年 9 月,我们说过:

我们将 WebAssembly 支持作为技术预览版推出,希望尽快代之以 WASI-HTTP 支持。

我们在 Unit 1.32.0 中做到了这一点。该版本支持将 WASI 0.2 API 和 wasi:http/proxy world 用作其主接口的 Wasm 组件。

此处注意,如果前面有你不熟悉的词汇,也不要担心。本文将介绍 WebAssembly (Wasm) 组件模型的概念,以及 WebAssembly 系统接口 (WASI) 在其中所扮演的角色,还将探讨“WebAssembly 接口类型”的意义。

正如第一篇 Wasm 博文中所述,Wasm 运行时与 Wasm 模块通过共享内存以原始字节的形式共享数据。要理解此字节流,主机和 Wasm 模块必须如出一口,或者从技术上说,实现相同接口。NGINX Unit 的核心概念是创建一个与特定应用相关的 HTTP 请求的上下文,并与运行时共享内存中的这组字节。

这正是我们在 unit-wasm 中所做的。虽然学习如何向 Unit 添加 Wasm 支持既有趣也很有必要,然而这与实现或采用某一标准相去甚远,因此就需要 Wasm 组件模型上场了。

WebAssembly (Wasm) 组件模型明确了不同的 Wasm 模块或组件之间及其与运行时环境之间的通信方式。它建立了必须满足的特定契约,以确保编译到 Wasm 组件中的代码可以托管在兼容的运行时上,并在运行期间与其他 Wasm 组件无缝交换数据。

如需获取这一理论框架的应用示例,请查看 NGINX Unit 中的实现,它是典型的 Wasm 组件模型实例。

Wasm 组件模型的两个重要组成部分是 WebAssembly 系统接口 (WASI) 和 WebAssembly 接口类型 (WIT)。下面我们来详细了解一下这两个标准。

 

WASI 和 WIT

WASI 是“WebAssembly System Interface(WebAssembly 系统接口)”的缩写,由 Wasmtime 项目推出,专为 Wasm 而设计。它是 Wasm 的可移植系统接口,支持访问多项操作系统的功能,包括文件和文件系统、套接字、时钟、随机数等。为什么需要它?

因为我们现在创建的 Wasm 组件是针对服务器端运行时而非基于浏览器的 Wasm 运行时(使用 Web API 或 JavaScript)。浏览器之外的代码需要一种方式来与底层系统通信。为了更好地理解 WASI 的作用,我们用 Rust 编写了一个非常简单的程序,它会输出“Hello World”。

我们编写的代码可以编译成一个可执行的二进制文件。程序启动后,我们会看到命令行上输出“Hello World”。这背后其实是 POSIX 标准在发挥作用,它定义了系统调用。系统调用在不同操作系统上的工作方式不同。

WASI 为这些系统调用提供了一个抽象层,可供编译到 Wasm 的代码使用。兼容 WASI 的运行时能够处理该代码的执行。我们将在本系列博文第二篇的 Rust 教程中进一步介绍其实际应用。自 WASI 提案的 Preview2 开始,WASI-API 在 WIT 文件中进行定义

WIT(Wasm 接口类型)是一种用于定义接口的描述性接口描述语言 (IDL),而非一种通用编码语言。编写的 WIT 文件不包含任何业务逻辑,只是纯粹的契约定义。多个接口可以进一步组合为 ”world”。虽然深入了解如何创建自己的 WIT 文件不是必需的,但有助于在构建组件时查找问题或排除故障。如欲了解有关 WIT 编程语言的更多信息,请参阅官方文档

Wasm 组件模型和 wasi:http/proxy world 使用的 WIT 文件由字节码联盟 (Bytecode Alliance) 创建和维护。截至本文撰写之时,使用它们的最佳方式是通过 Wasmtime 项目的 GitHub 代码库,以及进行手动拉取。

WIT 文件的一个有趣之处在于其版本控制系统。由于实现 Wasm 运行时的主机以及我们将要构建的组件都在为 WIT 文件定义的契约创建绑定,因此我们必须指向这些契约的同一版本,或者选择支持多个 WIT 文件版本的运行时。这值得再写一篇博文。现在,我们只考虑最新的稳定版本,它发布于 2024 年 2 月,标记为 WASI 0.2。该版本包括 wasi:cli world 和 wasi:http world。

在 WebAssembly 生态系统中,这些契约被称为“world”,下文将使用这一术语。就 NGINX Unit 用例而言,我们的目标非常明确,那就是 wasi:http/proxy world。您可以将 wasi:http/proxy world 视为一组描述 HTTP 请求和响应的接口,包括所有数据(HTTP 方法、请求头、正文等)。如果您是老 Web 开发人员,这可能会让您想起 CGI。

 

NGINX Unit、Wasmtime 和 Rust — 运行时实现

经过上面的介绍,我们现在知道 WASI/WIT 在支持组件模型方面扮演重要角色。作为站点,Unit 必须实现 WIT 文件定义的 WASI HTTP 代理接口才能履行契约。对此我们早已知晓。那既然使用 Wasmtime 作为 Wasm 运行时,我们何不将此任务委托给运行时呢?当然,完全可以!不过,有一个虽不起眼但很重要的细节:我们当时的实现完全是用 C 语言编写的,使用的是 Wasmtime C-API。遗憾的是,这些 API 缺乏支持组件模型的必要功能。

正如本文开头所述,只要找到对的人,心往一处想,再复杂的挑战均可迎刃而解。无论过去还是现在,Fermyon 都是我们极具价值的重要合作伙伴。经过一场 Slack 和 Zoom 深夜会议后,我们发现在 Wasmtime C-API 中添加对组件模型的原生支持过于复杂。此外,没有 bindgen 等自动化工具的帮助,使用 WIT 文件手动实现接口将需要大量维护工作。

在向 Fermyon 解释 NGINX Unit 的内部结件和当前基于 C 语言的语言模块的工作原理后,他们分享了一个支持 Wasmtime 的 Rust API 的基于 Rust 的 Unit 语言模块原型。再没有 C-API 的什么事了。

现在,我们准备好开始写代码了。

 

后续

在下一篇中,我们将介绍使用 Rust 和 WASI 0.2 API 创建 Wasm 组件的流程。

第 2 部分

Hero image
免费白皮书:
NGINX 企阅版全解析

助力企业用户规避开源治理风险,应对开源使用挑战

关于作者

Timo Stark

Professional Services Engineer

关于 F5 NGINX

F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 nginx-cn.net 了解更多相关信息。