wRPC
Component-native transport-agnostic RPC protocol and framework based on WebAssembly Interface Types (WIT)
A Bytecode Alliance hosted projectwRPC facilitates execution of arbitrary functionality defined in WIT over network or other means of communication.
Main use cases for wRPC are:
Even though wRPC is designed for Wasm components first and foremost, it is fully usable outside of WebAssembly context and can serve as a general-purpose RPC framework.
wRPC uses component model value definiton encoding on the wire.
wRPC supports both dynamic (based on e.g. runtime WebAssembly component type introspection) and static use cases.
For static use cases, wRPC provides WIT binding generators for:
wRPC fully supports the unreleased native WIT stream
and future
data types along with all currently released WIT functionality.
wRPC usage examples for different programming languages can be found at examples.
There are 2 different kinds of examples:
wrpc-wasmtime-nats
, to polyfill imports at runtime and serve exports using wRPC.hello
exampleIn this example we will serve and invoke a simple hello
application using:
nats-server
>= 2.10.20 or docker
>= 24.0.6 (or any other OCI runtime)rust
>= 1.80.1In the steps below, wasm32-wasip1
target will be used, because it is currently available in stable Rust and also conveniently configured in rust-toolchain.toml
in the root of this repository.
wasm32-wasip2
can be used instead, if desired.
Build Wasm hello
client:
cargo build --release -p hello-component-client --target wasm32-wasip1
Output is in target/wasm32-wasip1/release/hello-component-client.wasm
Build Wasm hello
server:
cargo build --release -p hello-component-server --target wasm32-wasip1
Output is in target/wasm32-wasip1/release/hello_component_server.wasm
NB: Rust uses
_
separators in the filename, because a component is built as a reactor-style library
Build the wRPC Wasm runtime:
cargo build --release --bin wrpc-wasmtime-nats
Output is in target/release/wrpc-wasmtime-nats or target/release/wrpc-wasmtime-nats.exe on Windows
Run NATS (more thorough documentation available here):
using standalone binary:
nats-server
using Docker:
docker run --rm -it --name nats-server -p 4222:4222 nats:2.10.20-alpine3.20
Serve Wasm hello
server via NATS:
./target/release/wrpc-wasmtime-nats serve rust rust ./target/wasm32-wasip1/release/hello_component_server.wasm
INFO async_nats: event: connected
INFO wrpc_wasmtime_nats_cli: serving instance function name="hello"
Call Wasm hello
server using a Wasm hello
client via NATS:
./target/release/wrpc-wasmtime-nats run rust ./target/wasm32-wasip1/release/hello-component-client.wasm
Sample output in the client:
INFO async_nats: event: connected
hello from Rust
Sample output in the server:
INFO wrpc_wasmtime_nats_cli: serving instance function invocation headers=None
INFO wrpc_wasmtime_nats_cli: successfully served instance function invocation
Call the Wasm hello
server using a native wRPC hello
client:
cargo run -p hello-nats-client rust
Serve native wRPC hello
server:
cargo run -p hello-nats-server native
Call both the native wRPC hello
server and Wasm hello
server using native wRPC hello
client:
cargo run -p hello-nats-client rust native
Call native wRPC hello
server using Wasm hello
client via NATS:
./target/release/wrpc-wasmtime-nats run native ./target/wasm32-wasip1/release/hello-component-client.wasm
wRPC transport is the core abstraction on top of which all the other functionality is built.
A transport represents a multiplexed bidirectional communication channel, over which wRPC invocations are transmitted.
wRPC operates under assumption that transport communication channels can be "indexed" by a sequence of unsigned 32-bit integers, which represent a reflective structural path.
As part of every wRPC invocation at least 2 independent, directional byte streams will be established by the chosen transport:
wRPC transport implementations MAY (and are encouraged to) provide two more directional communication channels:
Error channels are the only channels that are typed, in particular, values sent on these channels are strings.
If async
values are being transmitted as parameters or results of an invocation, wRPC MAY send those values on an indexed path asynchronously.
Consider the invocation of WIT function foo
from instance wrpc-example:doc/example@0.1.0
:
package wrpc-example:doc@0.1.0;
interface example {
record rec {
a: stream<u8>,
b: u32,
}
foo: func(v: rec) -> stream<u8>;
}
foo
parameter 0
is a record
, which contains an async
type (stream
) as the first field, wRPC will communicate to the transport that apart from the "root" parameter channel, it may need to receive results at index path 0
(first return value).rec.a
are not available at the time of encoding, the stream will be encoded as option::none
.2.
stream was not fully available) wRPC will transfer the contents of the stream<u8>
on parameter byte stream at index 0->0
(first field of the record, which is the first parameter) as they become available.stream<u8>
from the "root" result byte stream.4.
decoded an option::none
for the stream
value) wRPC will attempt to decode stream<u8>
from result byte stream at index 0
Note, that the handler of foo
(server) MAY:
rec.b
value before rec.a
is sent or even availablefoo
(client) before it has received rec.a
This repository contains (for all supported languages):
wit-bindgen-wrpc
aims to closely match UX of wit-bindgen
and therefore includes a subtree merge of the project, which is occasionally merged into this tree.
wit-bindgen
test suitewit-bindgen
documentation is reused where applicable👋 Welcome, new contributors!
Whether you're a seasoned developer or just getting started, your contributions are valuable to us. Don't hesitate to jump in, explore the project, and make an impact. To start contributing, please check out our Contribution Guidelines.