拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Apache ShenYu Wasm 插件开发:构建 Rust 测试 Wasm 文件与 Java 侧加载机制解析

后端API网关微服务【免费下载链接】shenyuApache ShenYu is a Java native API Gateway for service proxy, protocol conversion and API governance.项目地址https://gitcode.com/gh_mirrors/sh/shenyu点击查看免费下载本篇技术指南以shenyu-plugin-wasm-api模块测试目录下的 构建说明文档 为主体完整梳理如何用 Rust 编译出供 Wasm 插件测试使用的.wasm文件包括工具链安装、cargo build --target wasm32-wasi编译命令、产物定位与重命名规则。读完后你不仅能复现该 Wasm 测试文件的构建流程还能理解 Wasm 文件名为何必须与 Java 测试类的全限定名一致、Rust 侧execute导出函数与 Java 侧get_args/put_result导入函数之间如何通过线性内存交互——这正是 Apache ShenYu 多语言 Wasm 插件体系的入口级机制。一、文档背景这个 README 解决什么问题Apache ShenYu 提供了 Wasm 插件能力允许开发者用 Rust 等语言编写网关插件逻辑由 Java 网关进程通过 Wasmtime 运行时加载执行。shenyu-plugin-wasm-api模块是这一能力的 API 层核心抽象为 AbstractWasmPlugin 与 WasmLoader。模块的单元测试 AbstractWasmPluginTest 需要一个真实的 Rust 编译产物.wasm文件来验证「Java 调用 Wasm 函数、Wasm 反向调用 Java 函数」的完整往返链路。由于.wasm是二进制文件开发者修改 Rust 源码后需要本地重新编译替换。src/test/README.md 就是这份「如何重新构建 wasm 测试文件」的操作手册全文共四步安装 rustup安装 rust 工具链进入 Rust 工程目录执行cargo build --target wasm32-wasi --release将编译产物重命名为org.apache.shenyu.plugin.wasm.api.AbstractWasmPluginTest$RustWasmPlugin.wasm。下面结合仓库实际目录与源码把每一步展开为可复制的操作并解释背后的机制。二、构建 Wasm 文件的完整操作步骤2.1 环境准备rustup 与 Rust 工具链文档第 1、2 步要求先安装 rustupRust 官方版本管理器再通过 rustup 安装 Rust 工具链。使用 rustup 安装完成后cargo命令即可用。编译 Wasm 目标前还需要确认目标三元组已安装这是cargo build --target wasm32-wasi的前置条件rustup target add wasm32-wasi这里的目标必须是wasm32-wasi而非更常见的wasm32-unknown-unknown。原因见第五节Java 侧WasmLoader通过 WASI 接口把标准输出/错误输出继承到宿主进程Rust 测试代码中大量使用eprintln!打印调试信息只有 wasi 目标才提供这些系统调用。2.2 编译命令文档给出的核心构建命令为cd {shenyu}/shenyu-plugin/shenyu-plugin-wasm-api/src/test/rust-wasm-filter cargo build --target wasm32-wasi --release其中{shenyu}是 ShenYu 仓库根目录。执行完成后编译产物出现在{shenyu}/shenyu-plugin/shenyu-plugin-wasm-api/src/test/rust-wasm-filter/target/wasm32-wasi/release/rust_filter.wasm版本差异提示以当前仓库为准在本文对应的仓库快照中该 Rust 工程目录实际名为rust-wasm-plugin见 Cargo.toml 中name rust-wasm-plugin与 README 中写的rust-wasm-filter不完全一致可以推断文档撰写于工程更名之前。按仓库现状应改为cd {shenyu}/shenyu-plugin/shenyu-plugin-wasm-api/src/test/rust-wasm-plugin cargo build --target wasm32-wasi --release此时产物路径相应变为rust-wasm-plugin/target/wasm32-wasi/release/rust_wasm_plugin.wasmCargo 会将包名中的连字符转为下划线作为产物文件名。该工程 Cargo.toml 的关键配置决定了它能被编译为 Wasm 模块[lib] crate-type [cdylib]cdylib类型使 Cargo 产出无入口函数的动态库形态二进制这正是 Wasm 模块所要求的形态由宿主调用其导出函数而非自身 main 启动。2.3 重命名文件名必须匹配测试类全限定名文档第 4 步要求将产物重命名为org.apache.shenyu.plugin.wasm.api.AbstractWasmPluginTest$RustWasmPlugin.wasm这一步不是随意的命名约定而是由 Java 侧加载逻辑强制决定的详见第三节。仓库中该文件最终位于测试类路径资源根目录 src/test/resources/说明重命名后还需将其放入或替换该目录保证 Maven 测试时它能出现在 classpath 根路径下。由于.wasm是二进制文件重新编译后请用新产物覆盖此资源文件再运行AbstractWasmPluginTest即可验证。三、重命名规则的原理WasmLoader 如何定位 Wasm 文件WasmLoader 的构造过程揭示了命名规则的来源final Class? clazz wasmClass ! null ? wasmClass : this.getClass(); this.wasmName clazz.getName() .wasm; // locate .wasm lib. URL resource clazz.getClassLoader().getResource(wasmName); if (Objects.isNull(resource)) { throw new ShenyuWasmInitException(Cant find wasm file: wasmName); } byte[] wasmBytes Files.readAllBytes(Paths.get(resource.toURI()));要点如下对应源码 WasmLoader.java 第 88~97 行文件名 加载类全限定名 .wasm。测试中的插件是内部类AbstractWasmPluginTest.RustWasmPlugin其Class.getName()返回用$分隔的全限定名org.apache.shenyu.plugin.wasm.api.AbstractWasmPluginTest$RustWasmPlugin因此资源文件名必须恰好是...AbstractWasmPluginTest$RustWasmPlugin.wasm——这就是 README 第 4 步重命名的直接原因。通过ClassLoader.getResource按资源路径查找所以文件必须位于 classpath 根路径即src/test/resources/根下而不是某个子包目录。找不到文件会抛出 ShenyuWasmInitException插件初始化直接失败。此外WasmLoader基于 io.github.kawamuray.wasmtimewasmtime-java运行时完成模块实例化先Module.fromBinary编译字节码再通过Linker完成宿主函数定义与 WASI 链接第 109~116 行并注册 JVM 关闭钩子在进程退出时释放Store、Module等 Native 资源第 124 行WasmLoader.java 第 124 行。四、Rust 侧与 Java 侧的分工execute、get_args 与 put_result理解了构建流程后再看这个 Wasm 文件内部装的是什么就能明白整个测试在验证什么。4.1 Rust 侧导出 execute导入 Java 函数lib.rs 结构非常清晰#[link(wasm_import_module shenyu)] extern C { fn get_args(arg_id: i64, addr: i64, len: i32) - i32; fn put_result(arg_id: i64, addr: i64, len: i32) - i32; } #[no_mangle] pub unsafe extern C fn execute(arg_id: i64) { let mut buf [0u8; 32]; let buf_ptr buf.as_mut_ptr() as i64; // get arg from java let len get_args(arg_id, buf_ptr, buf.len() as i32); let java_arg std::str::from_utf8(buf[..len as usize]).unwrap(); // pass rust result to java let rust_result rust result.as_bytes(); let result_ptr rust_result.as_ptr() as i64; _ put_result(arg_id, result_ptr, rust_result.len() as i32); }要点#[no_mangle]的execute是模块的导出函数Java 侧按名字execute查找调用对应 AbstractWasmPlugin 中的常量EXECUTE_METHOD_NAME execute。get_args、put_result是导入函数#[link(wasm_import_module shenyu)]声明它们来自名为shenyu的导入模块——这与 Java 侧 WasmLoader 中的常量IMPORT_WASM_MODULE_NAME shenyu一一对应链接阶段靠这个名字配对。4.2 Java 侧AbstractWasmPlugin 调用 Wasm测试类注册导入函数AbstractWasmPlugin 实现了 ShenYu 插件的before/execute/after三个生命周期钩子均按before/execute/after三个名字到 Wasm 模块中查找导出函数第 96~106 行。核心调用逻辑在callWASI第 82~92 行// WASI cannot easily pass Java objects like JNI, here we pass Long as arg // then we can get the argument by Long final Long argumentId getArgumentId(exchange, chain); ARGUMENTS.put(argumentId, new Argument(exchange, chain)); // call WASI function WasmFunctions.consumer(super.getStore(), execute.func(), WasmValType.I64) .accept(argumentId);这里体现了 Wasm 插件与 JNI 的本质区别Wasm 线性内存边界无法直接传递 Java 对象引用因此框架采用「Long 类型 argumentId 作为间接句柄」的设计——先把ServerWebExchange与ShenyuPluginChain包进Argument对象放入静态ARGUMENTSMap再把 keyargumentId以 I64 传入 WasmWasm 侧回调 Java 时带着同一个argumentIdJava 即可反查回原始对象。测试子类RustWasmPluginAbstractWasmPluginTest 第 81~131 行通过重写initWasmCallJavaFunc向 Wasm 注册两个 Java 宿主函数正好对应 Rust 侧的两个导入get_args(argId, addr, len)Java 侧生成hello from java argId把字符串逐字节写入 Wasm 线性内存的addr起始位置通过super.getBuffer()拿到ByteBuffer视图即 WasmLoader 第 167~169 行 的memRef.buffer(store)返回写入长度——这正是「Java 向 Wasm 传数据」的线性内存拷贝模式put_result(argId, addr, len)Java 从线性内存addr处读回len字节断言内容为rust result并暂存到RESULTSMap——这是「Wasm 向 Java 传数据」的反向路径。doExecute随后从RESULTS中取出结果并断言其等于rust result同时继续chain.execute(exchange)走插件链。整个测试用一次executePluginTest就覆盖了双向数据传递与插件链流转。五、为什么目标平台是 wasm32-wasiWasmLoader 的 WASI 上下文lib.rs 中多次调用eprintln!输出调试日志如rust side- recv:{}。这在纯wasm32-unknown-unknown目标下是无法编译通过的没有标准 I/O 系统调用而在本模块中恰是被设计支持的能力WasmLoader 第 57 行 构造 WASI 上下文private final WasiCtx wasiCtx new WasiCtxBuilder().inheritStdout().inheritStderr().build();随后在链接阶段执行WasiCtx.addToLinker(linker)第 110 行将 WASI 导入符号注入模块使 Rust 侧eprintln!的输出被继承到宿主流水线。这也解释了callWASI注释中「WASI」一词的由来整个 Wasm 函数调用都运行在这套继承宿主 I/O 的 WASI 沙箱之上。另外一个隐含的加载前置条件模块必须导出名为memory的内存WasmLoader 第 119~123 行 会显式检查找不到即抛出memory function not find in wasm file异常。Rust 的cdylibWasm 产物天然导出memory因此能顺利通过该检查WasmLoader.getBuffer()也依赖这个内存引用来暴露线性内存视图。六、验证方式与既有产物完成构建与重命名覆盖src/test/resources/下的同名文件后运行模块内的AbstractWasmPluginTest即可验证Rust 侧日志出现rust side- recv:hello from java 0表明get_args的线性内存写入成功Java 侧RESULTS收到并断言rust result表明put_result的读回成功插件链shenyuPluginChain.execute(exchange)被正常调用测试完成即代表整个「Java → Wasm → Java」往返链路通畅。需要注意仓库中已经提交了预编译好的测试资源 AbstractWasmPluginTest$RustWasmPlugin.wasm因此在未改动 Rust 源码的情况下无需执行本文的构建流程即可直接运行该测试只有在修改了rust-wasm-plugin的 Rust 代码后才需要按本文第二节重新编译、重命名并覆盖资源文件。同仓库中shenyu-plugin-wasm-base模块的测试目录如 rust-shenyu-wasm-plugin 等 Rust 工程采用完全相同的构建与命名约定本文所述流程可平移到这些 Wasm 测试工程的重新构建中。赞分享后端API网关微服务【免费下载链接】shenyuApache ShenYu is a Java native API Gateway for service proxy, protocol conversion and API governance.项目地址https://gitcode.com/gh_mirrors/sh/shenyu点击查看免费下载相关推荐Higress wasm-go 插件开发实战Go 构建 Wasm 插件、部署与测试全流程Higress wasm go 插件开发实战Go 构建 Wasm 插件、部署与测试全流程 本篇技术指南围绕 Higress 的 Go 语言 Wasm 插件开发API网关后端云原生LLM 网关人工智能MCP 服务使用 Higress wasm-cpp SDK 开发 C Wasm 插件构建、发布与 E2E 测试实战指南使用 Higress wasm cpp SDK 开发 C Wasm 插件构建、发布与 E2E 测试实战指南 本篇技术指南围绕 Higress 官方提供的API网关后端云原生LLM 网关人工智能MCP 服务Higress WASM插件开发实战用Rust构建API网关认证插件还在为API网关的认证功能发愁一文教你用Rust快速开发Higress WASM插件实现强大的基础认证功能 读完本文你将掌握 ✅ WASM插件开发环境搭API网关后端云原生LLM 网关人工智能MCP 服务上一篇Minikube安装与配置从零开始搭建本地K8s集群下一篇Chaos Monkey 项目推荐创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门