Candle Corporation
Published in

Candle Corporation

Photo by Jason Pischke on Unsplash

Async Streams in WebAssembly with WasmRS

What’s WasmRS?

WasmRS is an implementation of the RSocket protocol in WebAssembly with some reactive stream concepts thrown in for usability. With WasmRS you treat WebAssembly modules like tiny services you open bidirectional sockets into.

  • Fire & Forget: a request that is sent where the response is ignored.
  • RequestResponse: an asynchronous request with a single payload returning a single payload.
  • RequestStream: a request with a single payload that returns a stream of payloads.
  • RequestChannel: a request that takes a stream that returns a stream.

How do I use it?

Using wasmRS directly is a bit like using WebAssembly directly. There’s a lot of boilerplate and many esoteric details to get right before you get what you want. If you’re the sort that likes those details, check out the baseline Rust implementation in the repository. If you’re like me, the details are great but getting started rapidly is more important.

apex new git@github.com:nanobus/iota.git -p templates/rust example

Hello, World!

namespace "example"

interface MyApi @service {
greet(target: string): string
}
$ apex generate
INFO Writing file ./src/actions/my_api/greet.rs (mode:644)
INFO Writing file ./src/lib.rs (mode:644)
INFO Writing file ./src/error.rs (mode:644)
INFO Writing file ./src/actions/mod.rs (mode:644)
INFO Formatting file ./src/error.rs
INFO Formatting file ./src/lib.rs
INFO Formatting file ./src/actions/mod.rs
INFO Formatting file ./src/actions/my_api/greet.rs
use crate::actions::my_api_service::greet::*;

pub(crate) async fn task(input: Inputs) -> Result<Outputs, crate::Error> {
todo!("Add implementation");
}
use crate::actions::my_api_service::greet::*;

pub(crate) async fn task(input: Inputs) -> Result<Outputs, crate::Error> {
Ok(format!("Hello, {}!", input.target))
}
cargo build --target=wasm32-unknown-unknown

Running our WebAssembly with wasmrs-request

To use the wasmrs-request tool, first install it with the command:

cargo install wasmrs-request
wasmrs-request ./build/example.wasm example.MyApi greet '{"target":"World"}'
Hello, World!

Running our WebAssembly with NanoBus

id: example
version: 0.0.1
main: target/wasm32-unknown-unknown/release/example.wasm
# Or, if you're using `just build`:
# main: build/example.wasm
echo '{"target":"World"}' | nanobus invoke iota.yaml example.MyApi::greet
"Hello, World!"

Streams!

Now that you’re familiar with building wasmRS WebAssembly and running it with NanoBus or wasmrs-request, let's get to streaming.

namespace "example"

interface MyApi @service {
greet(target: string): string
reverse(input: stream string): stream string
}
$ apex generate
INFO Writing file ./src/actions/my_api/reverse.rs (mode:644)
INFO Writing file ./src/actions/mod.rs (mode:644)
INFO Formatting file ./src/actions/my_api/reverse.rs
INFO Formatting file ./src/actions/mod.rs
use crate::actions::my_api_service::reverse::*;

pub(crate) async fn task(
mut input: FluxReceiver<Inputs, PayloadError>,
outputs: Flux<Outputs, PayloadError>,
) -> Result<Flux<Outputs, PayloadError>, crate::Error> {
todo!("Add implementation");
}
use crate::actions::my_api_service::reverse::*;

pub(crate) async fn task(
mut input: FluxReceiver<Inputs, PayloadError>,
outputs: Flux<Outputs, PayloadError>,
) -> Result<Flux<Outputs, PayloadError>, crate::Error> {
while let Some(line) = input.next().await {
match line {
Ok(line) => {
outputs.send(line.chars().rev().collect()).unwrap();
}
Err(e) => outputs.error(PayloadError::application_error(e.to_string())).unwrap(),
}
}
outputs.complete();
Ok(outputs)
}
cargo build --release --target=wasm32-unknown-unknown
# or `just build`
cat Cargo.toml |  wasmrs-request --channel ./build/example.wasm example.MyApi reverse
]egakcap[
"elpmaxe" = eman
"0.1.0" = noisrev
"1202" = noitide

]bil[
]"bilydc"[ = epyt-etarc

]esaeler.eliforp[
"slobmys" = pirts
1 = stinu-negedoc
eslaf = gubed
eurt = otl
"z" = level-tpo
"troba" = cinap

]seicnedneped[
"2.0" = tseug-srmsaw
"0.1" = rorresiht
} ]"evired"[ = serutaef ,eslaf = serutaef-tluafed ,"1" = noisrev { = edres
"1.0" = tiart-cnysa
"0.82.0" = ajnijinim

]seicnedneped-ved[]esaeler.eliforp[
"slobmys" = pirts
1 = stinu-negedoc
eslaf = gubed
eurt = otl
"z" = level-tpo
"troba" = cinap

Where to go next?

WasmRS is the protocol we’re using for iota dependencies. Iotas are libraries, microservices, and WebAssembly modules that use a common protocol so they can be swapped out, integrated, composed, and tested without changing your application.

More links

--

--

Blogs from Candle Employees. https://candle.dev

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Jarrod Overson

I write about JavaScript, Reverse Engineering, Security, and Credential Stuffing. Also a speaker, O'Reilly Author, creator of Plato, Director at Shape Security.