Node.js SDK
- host 应用程序是一个用 JavaScript 写的 Node.js web 应用程序,它可以调用 WebAssembly 函数。
- WebAssembly 字节码程序是用 Rust 写的,运行在 WasmEdge Runtime,可以被 Node.js web 应用程序调用。
为了搭建一个包含 Rust 和 WebAssembly 的高性能 Node.js 环境,你需要如下准备:
- 一个现代的 Linux 发行版, 比如 Ubuntu Server 20.04 LTS
- Node.js 的 WasmEdge Runtime
最简单的启动方式就是使用 Docker 来搭建开发环境。只需要克隆这个模板到你的电脑,然后运行如下 Docker 命令:
好了,你现在可以编译和运行代码了。
命令如下。
# 安装 Rust $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh $ source $HOME/.cargo/env $ rustup override set 1.50.0 # 安装 Node.js 和 npm $ curl -sL https://deb.nodesource.com/setup_14.x | bash $ sudo apt-get install -y nodejs npm # 安装 rustwasmc 工具链 $ npm install -g rustwasmc # 如果权限有问题,加上 --unsafe-perm # WasmEdge 需要的系统依赖 $ sudo apt-get update $ sudo apt-get -y upgrade $ sudo apt install -y build-essential curl wget git vim libboost-all-dev llvm-dev liblld-10-dev # 安装 WasmEdge 需要的 nodejs addon $ npm install wasmedge-core $ npm install wasmedge-extensions
然后,克隆示例源代码仓库。
git clone https://github.com/second-state/wasmedge-nodejs-starter cd wasmedge-nodejs-starter
第一个示例是一个 hello world,向你展示应用程序的各个部分如何组合在一起。
在这个例子中,Rust 程序将输入的字符串添加到 “hello” 后面。下面是 Rust 程序内容,位于 src/lib.rs。你可以在这个库文件中定义多个外部方法,所有的这些方法都可以在 host JavaScript 应用中通过 WebAssembly 调用。记得需要给每个函数添加 #[wasm_bindgen]
注解,这样 就知道在构建时为这些函数生成正确的 JavaScript 到 Rust 接口。
然后你可以将 Rust 源代码编译成 WebAssembly 字节码,并且生成相应的 JavaScript 模块供 Node.js host 环境调用。
生成的文件在 pkg/
目录下,.wasm
文件是 WebAssembly 字节码程序,.js
文件是 JavaScript 模块。
然后进入 node
文件夹下,检查 JavaScript 程序 app.js
。有了生成的 wasmedge_nodejs_starter_lib.js
模块,就很容易写出调用 WebAssembly 函数的 JavaScript 了。下面是 node 应用程序 app.js
。简单的从生成的模块中引入 say()
函数。 node 应用程序从 HTTP GET 请求中拿到 name
参数后返回 “hello name
”。
const { say } = require('../pkg/wasmedge_nodejs_starter_lib.js'); const http = require('http'); const url = require('url'); const hostname = '127.0.0.1'; const port = 3000; const server = http.createServer((req, res) => { const queryObject = url.parse(req.url,true).query; res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end(say(queryObject['name'])); }); server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); });
像下面一样启动 Node.js 应用程序。
$ node node/app.js Server running at http://127.0.0.1:3000/
然后,你可以在另外一个终端窗口中测试。
$ curl http://127.0.0.1:3000/?name=Wasm hello Wasm
下面的例子展示了一个计算二次方程根的 web 应用程序,请在这里查看完整源代码.
用户在 web 表单中输入 a
, , c
三个值,web 应用程序调用 web 服务 /solve
,计算出二次方程的根。
a*X^2 + b*X + c = 0
X
的根展示在输入表单下面。
包含提交 web 表单到 /solve
的客户端 JavaScript,并且将结果放到页面的 #roots
HTML 元素里。
/solve
URL 端点后的 Node.js 应用程序如下所示。他从输入表单中读取数据,将他们作为数组传递给 solve
函数,将返回结果放到 HTTP 返回内容中。
app.post('/solve', function (req, res) { var a = parseFloat(req.body.a); var b = parseFloat(req.body.b); var c = parseFloat(req.body.c); res.send(solve([a, b, c])) })
,运行在 WasmEdge Runtime。如果 JavaScript 端的调用参数是数组,Rust 函数接收到一个封装数组的 JSON 对象。在 Rust 代码中,我们首先解码 JSON,执行计算,然后返回一个 JSON 字符串的结果。
让我们试试。
rustwasmc build npm install express # 这个应用程序需要 Node.js 的 express 框架 node node/server.js
在 web 浏览器中,输入 http://ip-addr:8080/
来获取应用程序。注意:如果你使用的是 Docker,确保 Docker 容器中的 8080 端口映射到宿主的 8080 端口。
这就是二次方程的例子。
在 Rust 和 JavaScript 之间除了可以传递字符串值外, rustwasmc
工具支持下面的数据类型。
- Rust 调用参数可以是
i32
、String
、&str
、Vec<u8>
和&[u8]
的组合。 - 返回值可能是
i32
或者 或者Vec<u8>
或者 void。
函数示例中的 Rust 程序 src/lib.rs
演示了如何传递多个不同类型的调用参数和返回结果。
最有意思的可能是 create_line()
函数。它需要两个 JSON 字符串,每一个都代表一个 Point
结构,返回一个 JSON 字符串代表 Line
结构。注意,Point
和 Line
结构都使用了 Serialize
和 Deserialize
注解,这样 Rust 编译器就会自动生成必要的代码来支持和 JSON 字符串之间的转换。
然后,让我们来检查下 JavaScript 程序 ,它展示了如何调用 Rust 函数。你可以看到,String
和 &str
在 JavaScript 是简单的字符串,i32
是数字,Vec<u8>
或者 &[8]
是 JavaScript Uint8Array
。JavaScript 对象在传入或者从 Rust 函数结果返回需要通过 JSON.stringify()
或者 JSON.parse()
转换。
const { say, obfusticate, lowest_common_denominator, sha3_digest, keccak_digest, create_line } = require('./functions_lib.js'); var util = require('util'); const encoder = new util.TextEncoder(); console.hex = (d) => console.log((Object(d).buffer instanceof ArrayBuffer ? new Uint8Array(d.buffer) : typeof d === 'string' ? (new util.TextEncoder('utf-8')).encode(d) : new Uint8ClampedArray(d)).reduce((p, c, i, a) => p + (i % 16 === 0 ? i.toString(16).padStart(6, 0) + ' ' : ' ') + c.toString(16).padStart(2, 0) + (i === a.length - 1 || i % 16 === 15 ? ' '.repeat((15 - i % 16) * 3) + Array.from(a).splice(i - i % 16, 16).reduce((r, v) => r + (v > 31 && v < 127 || v > 159 ? String.fromCharCode(v) : '.'), ' ') + '\n' : ''), '')); console.log( say("WasmEdge") ); console.log( obfusticate("A quick brown fox jumps over the lazy dog") ); console.log( lowest_common_denominator(123, 2) ); console.hex( sha3_digest(encoder.encode("This is an important message")) ); console.hex( keccak_digest(encoder.encode("This is an important message")) ); var p1 = {x:1.5, y:3.8}; var p2 = {x:2.5, y:5.8}; var line = JSON.parse(create_line(JSON.stringify(p1), JSON.stringify(p2), "A thin red line")); console.log( line );
$ rustwasmc build ... Building the wasm file and JS shim file in pkg/ ... $ node node/app.js hello WasmEdge N dhvpx oebja sbk whzcf bire gur ynml qbt 246 000000 57 1b e7 d1 bd 69 fb 31 9f 0a d3 fa 0f 9f 9a b5 W.çѽiû1..Óú...µ 000010 2b da 1a 8d 38 c7 19 2d 3c 0a 14 a3 36 d3 c3 cb +Ú..8Ç.-<..£6ÓÃË 000000 7e c2 f1 c8 97 74 e3 21 d8 63 9f 16 6b 03 b1 a9 ~ÂñÈ.tã!Øc..k.±© 000010 d8 bf 72 9c ae c1 20 9f f6 e4 f5 85 34 4b 37 1b Ø¿r.®Á .öäõ.4K7. { points: [ { x: 1.5, y: 3.8 }, { x: 2.5, y: 5.8 } ], valid: true, length: 2.2360682, desc: 'A thin red line' }