WASI:WebAssembly 系统接口
为什么需要 WASI?
在浏览器中,WebAssembly 通过 JavaScript API 与系统交互(DOM、Fetch、Web Storage)。但在浏览器之外(服务器、CLI 工具、嵌入式设备),Wasm 需要访问文件系统、网络、时钟等系统资源。
WASI(WebAssembly System Interface)是 Bytecode Alliance 制定的标准接口规范,为 Wasm 提供了一套类 POSIX 的系统调用接口。它的设计原则是基于能力的安全模型(Capability-Based Security):默认零权限,只有宿主显式授予的能力才能使用。
wasmtime:生产级 Wasm 运行时
# 安装 wasmtime
curl https://wasmtime.dev/install.sh -sSf | bash
# 编译 Rust 程序到 WASI 目标
rustup target add wasm32-wasip1
cargo build --target wasm32-wasip1 --release
# 运行(默认允许标准输入输出)
wasmtime target/wasm32-wasip1/release/hello.wasm
# 授予文件系统权限(能力授予)
wasmtime --dir /tmp hello.wasm
# 映射目录(沙箱内路径 → 宿主路径)
wasmtime --dir /sandbox::/host/path hello.wasm
Rust WASI 程序示例
// src/main.rs:编译目标 wasm32-wasip1
use std::io::{self, BufRead};
use std::fs;
fn main() -> io::Result<()> {
// 读取命令行参数
let args: Vec<String> = std::env::args().collect();
if args.len() > 1 {
// 读取文件(需要 wasmtime --dir 授权)
let content = fs::read_to_string(&args[1])?;
println!("文件内容:{}", content);
} else {
// 从 stdin 读取
let stdin = io::stdin();
for line in stdin.lock().lines() {
println!("读到:{}", line?);
}
}
Ok(())
}
Spin:WASI 微服务框架
# 安装 Spin CLI
curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash
# 创建新的 HTTP 处理器
spin new http-rust my-service
cd my-service
spin build
spin up
// src/lib.rs:Spin HTTP 处理器
use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;
#[http_component]
fn handle_request(req: Request) -> Response {
println!("请求路径: {}", req.uri().path());
Response::new(200, "Hello from Wasm!")
}
WASI 能力模型深度解析
什么是基于能力的安全模型?
传统的 POSIX 程序以 UID/GID 作为权限控制——进程可以访问该用户有权访问的所有资源。这是环境权限(Ambient Authority)模型,权限是隐式的。
WASI 使用基于能力的安全模型(Capability-Based Security):程序默认没有任何权限,宿主(运行时)必须显式传递文件描述符、目录句柄等「能力令牌」给程序。没有获得能力令牌的程序无法访问任何资源——即使是同一系统上的其他文件。
WASI 接口层次结构
Rust WASI 程序开发完整示例
// Cargo.toml
[package]
name = "wasi-tool"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
// src/main.rs — 一个 WASI 命令行工具:JSON 格式化器
use std::io::{self, Read, Write};
fn main() -> io::Result<()> {
let args: Vec<String> = std::env::args().collect();
// 从 stdin 读取 JSON 输入
let mut input = String::new();
io::stdin().read_to_string(&mut input)?;
// 解析并格式化 JSON
let value: serde_json::Value = serde_json::from_str(&input)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let indent = if args.len() > 1 {
args[1].parse::<usize>().unwrap_or(2)
} else { 2 };
// 输出格式化后的 JSON 到 stdout
let formatter = serde_json::ser::PrettyFormatter::with_indent(
b" ".repeat(indent).as_bytes()
);
let formatted = serde_json::to_string_pretty(&value)?;
io::stdout().write_all(formatted.as_bytes())?;
println!();
Ok(())
}
# 编译为 WASI 目标
cargo build --target wasm32-wasip1 --release
# 运行(wasmtime)
echo '{"name":"Alice","age":30}' | wasmtime target/wasm32-wasip1/release/wasi-tool.wasm
# 输出:
# {
# "name": "Alice",
# "age": 30
# }
# 在 Node.js 22+ 中运行(原生 WASI 支持)
node --experimental-wasm-modules wasi-runner.mjs
Node.js 中的 WASI API
// wasi-runner.mjs — 在 Node.js 中运行 WASI 程序
import { WASI } from 'wasi';
import { readFileSync } from 'fs';
import { argv, env } from 'process';
// 配置 WASI 能力
const wasi = new WASI({
args: argv, // 传递命令行参数
env, // 传递环境变量
preopens: {
// 将宿主目录 '/data' 映射为 Wasm 内的 '/sandbox'
'/sandbox': '/data',
// Wasm 程序只能访问这个映射的目录
},
returnOnExit: true, // 程序调用 exit() 时返回而不是抛出
});
// 读取并实例化 Wasm 模块
const wasm = readFileSync('./wasi-tool.wasm');
const module = await WebAssembly.compile(wasm);
const instance = await WebAssembly.instantiate(module, {
// wasi.getImportObject() 提供所有 WASI 系统调用的实现
wasi_snapshot_preview1: wasi.wasiImport,
});
// 启动程序(调用 _start = main 函数)
wasi.start(instance);
WASI 0.2 与旧版 Preview 1 的区别
// 使用 WASI 0.2 HTTP 接口(需要 wasm32-wasip2 目标)
// Cargo.toml:
// [dependencies]
// wit-bindgen = "0.28"
// wasi = "0.13"
// wit-bindgen 为 wasi:http 自动生成 Rust 绑定
wit_bindgen::generate!({
world: "wasi:http/proxy",
path: "wit",
});
struct HttpHandler;
impl exports::wasi::http::incoming_handler::Guest for HttpHandler {
fn handle(request: IncomingRequest, response_out: ResponseOutparam) {
// 读取请求路径
let path = request.path_with_query()
.unwrap_or("/".to_string());
// 构造 HTTP 响应
let response = OutgoingResponse::new(
Fields::from_list(&[(
"content-type".to_string(),
b"text/plain".to_vec(),
)]).unwrap()
);
response.set_status_code(200).unwrap();
let body = response.body().unwrap();
let output = body.write().unwrap();
output.blocking_write_and_flush(
format!("Hello from WASI 0.2! Path: {}", path).as_bytes()
).unwrap();
ResponseOutparam::set(response_out, Ok(response));
}
}
export!(HttpHandler);
WASI Preview 1 和 WASI 0.2 是不同格式:Preview 1 是普通 Core Wasm 模块,WASI 0.2 是 Wasm 组件格式。两者互不兼容。wasmtime 14+ 提供了"适配器"(adapter),可以将 wasip1 模块提升为 wasip2 组件:wasm-tools component new my_prog.wasm --adapt wasi_snapshot_preview1=adapter.wasm -o my_component.wasm。这是在 WASI 生态过渡期的重要工具。
wasmtime CLI 高级用法
# 查看 Wasm 模块的导入需求
wasmtime inspect module.wasm
# 启用性能计数器
wasmtime --profile=vtune module.wasm
# 限制内存使用
wasmtime --max-wasm-stack 1MiB module.wasm
# 启用 WASI 网络(需要 WASI 0.2)
wasmtime --wasi-modules=experimental-wasi-nn module.wasm
# 运行时 AOT 编译(Ahead-of-Time,加快启动速度)
wasmtime compile module.wasm -o module.cwasm # 预编译
wasmtime run module.cwasm # 直接运行编译结果
本章小结
- WASI 是基于能力的安全模型:默认零权限,宿主显式授予资源句柄(目录、网络、时钟等);比传统 POSIX 环境权限模型更安全
- WASI Preview 1 vs WASI 0.2:Preview 1 是目前工具链支持最广泛的版本;WASI 0.2 基于组件模型重新设计,新增 HTTP/sockets,是未来方向
- wasm32-wasip1 编译目标:Rust 程序可以直接使用 std::io(文件/stdin/stdout),与普通 Rust 程序写法相同
- Node.js WASI API:通过 new WASI({ preopens, env, args }) 配置能力,wasi.wasiImport 提供系统调用实现
- wasmtime AOT 编译:wasmtime compile 预编译为 .cwasm 可加快冷启动速度,适合 serverless 场景