Quick reminders
Capsule?
Capsule is a runner (or launcher) of wasm functions. Capsule can serve the functions through HTTP, NATS and MQTT (it’s possible to use Capsule as a simple CLI).
The wasm functions are wasm modules importing the host functions provided by Capsule. All these modules are built with TinyGo thanks to its WASI support.
Capsule is developed thanks to the Wazero project: "the zero dependency WebAssembly runtime for Go developers".
Btw, Capsule is small (~17 mb), wasm modules too (~100 ko to 900 ko), then it runs like a charm on a Raspberry Pi 3 A+ 🤩
The NATS support in Capsule is very new (subject to change).
Install the last version of Capsule
I usually run these commands:
CAPSULE_VERSION="v0.2.6"
OS="linux"
ARCH="amd64"
wget https://github.com/bots-garden/capsule/releases/\
download/${CAPSULE_VERSION}/\
capsule-${CAPSULE_VERSION}-${OS}-${ARCH}.tar.gz
sudo tar -zxf \
capsule-${CAPSULE_VERSION}-${OS}-${ARCH}.tar.gz \
--directory /usr/local/bin
rm capsule-${CAPSULE_VERSION}-${OS}-${ARCH}.tar.gz
- You can download the appropriate release for your platform here: https://github.com/bots-garden/capsule/releases/tag/v0.2.6 (you only need the capsule archive).
- Capsule has been tested on MacOS M1+Intel, Linux amd64+arm64.
NATS? (very quickly)
The NATS protocol allows client applications to exchange data on subjects through a network. Subjects are like channels on Slack (his explanation engages only me 🙂). A clients could be a publisher of message (on a subject) or a subscriber (listening on a subject). A subscriber will be triggered if a message happens on the subject. So, you can see it as a "pub/sub" system.
Here we go 🚀
This post does not present all the NATS capabilities of Capsule but only a simple example of how to write a calc
function (the wasm module) and how to call it from JavaScript (with Node.js) thanks to the NATS protocol.
We needs a NATS server
The NATS server installation is pretty straightforward:
NATS_VERSION="2.9.0"
NATS_OS="linux-amd64"
curl -L https://github.com/nats-io/nats-server/releases/download/v${NATS_VERSION}/nats-server-v${NATS_VERSION}-${NATS_OS}.zip -o nats-server.zip
unzip nats-server.zip -d nats-server
sudo cp nats-server/nats-server-v${NATS_VERSION}-${NATS_OS}/nats-server /usr/bin
Of course adapt this for your own platform
- To start the NATS server:
nats-server
- To stop the NATS server:
nats-server --signal stop
(orCtrl+C
)
The calc
function (in Go, built with TinyGo)
The wasm calc
will operate a NATS subscriber.
A "NATS subscriber" function with Capsule will always have the following form:
package main
import (
hf "github.com/bots-garden/capsule/capsulemodule/hostfunctions"
)
func main() {
hf.OnNatsMessage(Handle)
}
func Handle(params []string) {
natsMessage := params[0]
// foo
}
The
Handle
method is called when a message happens on a specific subject.
The calc
module will receive json string messages like:
{"operation":"*","operand1":21,"operand2":2}
Let's write the calc
function:
package main
import (
/* string to json */
"github.com/tidwall/gjson"
/* create json string */
"github.com/tidwall/sjson"
hf "github.com/bots-garden/capsule/capsulemodule/hostfunctions"
)
func main() {
hf.OnNatsMessage(Handle)
}
func Handle(params []string) {
natsMessage := params[0] // 1️⃣
operation := gjson.Get(natsMessage, "operation").String()
operand1 := gjson.Get(natsMessage, "operand1").Float()
operand2 := gjson.Get(natsMessage, "operand2").Float()
var res float64
switch operation {
case "+":
res = operand1 + operand2
case "-":
res = operand1 - operand2
case "*":
res = operand1 * operand2
case "/":
res = operand1 / operand2
default:
res = 0.0
}
result := `{"result": ""}`
result, _ = sjson.Set(result, "result", res)
hf.NatsReply(result, 10) // 2️⃣
}
//export OnLoad
func OnLoad() {
hf.Log("🙂 I'm the calc function")
hf.Log("👂Listening on: " + hf.NatsGetSubject())
hf.Log("👋 NATS server: " + hf.NatsGetServer())
}
//export OnExit
func OnExit() {
hf.Log("👋🤗 Bye! Have a nice day")
}
- 1️⃣ the message is the first item of the array params.
- 2️⃣ the subscriber reply with
result
to the publisher (on the same subject),10
is a time out in seconds.
Build the module
Run the following command:
tinygo build -o calc.wasm -scheduler=none -target wasi ./calc.go
Serve the module
Run the following command:
capsule \
-wasm=./calc.wasm \
-mode=nats \
-natssrv=localhost:4222 \
-subject=faas
Capsule will load the wasm module and become a NATS subscriber, listening on
faas
subject. At every message onfaas
, Capsule will create an instance of the module and call the function.
Let's write the Node.js NATS publisher
Install the NATS dependency
In a directory create a package.json
file with the below content:
{
"dependencies": {
"nats": "^2.8.0"
},
"type": "module"
}
Then install the dependency by typing:
npm install
The source code of the publisher
Create a calc.js
file with this content:
import { connect, StringCodec } from "nats";
const nc = await connect({ servers: ["localhost:4222"] });
// create a codec
const sc = StringCodec();
// subject/topic
const subject = "faas";
const addition = {
operation: "+",
operand1: 30,
operand2: 12
};
const subtraction = {
operation: "-",
operand1: 50,
operand2: 8
};
const multiplication = {
operation: "*",
operand1: 21,
operand2: 2
};
const division = {
operation: "/",
operand1: 84,
operand2: 2
};
async function wasmCalc(operation) {
let jsonString = JSON.stringify(operation)
await nc.request(subject, sc.encode(jsonString), { timeout: 1000 }) // 1️⃣
.then((message) => {
let result = sc.decode(message.data)
console.log("🟢 Result:", result);
})
.catch((err) => {
console.log("🔴 Error:", err.message);
});
}
await wasmCalc(addition)
await wasmCalc(subtraction)
await wasmCalc(multiplication)
await wasmCalc(division)
await nc.close();
- 1️⃣ The client makes a request and receives a promise for a message. The request time out is of 1000 millis.
Run it
Run node calc.js
, you should get:
🟢 Result: {"result": 42}
🟢 Result: {"result": 42}
🟢 Result: {"result": 42}
🟢 Result: {"result": 42}
As you see, it's pretty simple, and you can easily run other Capsule subscriber processes to add new functions to your network.
Last but not least: create a wasm NATS publisher
You can use a Capsule module as a publisher:
nats-publisher.go
package main
import (
hf "github.com/bots-garden/capsule/capsulemodule/hostfunctions"
)
func main() {
hf.SetHandle(Handle)
}
func Handle(params []string) (string, error) {
natsSrv := "localhost:4222"
subject := "faas"
operation := `{"operation":"+", "operand1":40, "operand2":2}`
result, err := hf.NatsConnectRequest(natsSrv, subject, operation, 1)
return result, err
}
Build it:
tinygo build -o nats-publisher.wasm -scheduler=none \
-target wasi ./nats-publisher.go
Run it:
capsule \
-wasm=./nats-publisher.wasm \
-mode=cli
This time, we run Capsule with the
CLI
mode to execute the wasm module (like with Wasmer, Wasmtime, Wasmedge, ...)
You should get something like this:
{"result": 42}
That's all for today! Happy Wasi 🎉
You can find all the source code examples here: https://github.com/bots-garden/capsule-hello-universe/tree/main/nats-faas
If you use Gitpod, the project is "Gitpod compliant", so you will get of the necessary tools (Golang, TinyGo, NATS server, ...); otherwise, it should work with Dev Container.
Top comments (0)