bytecodealliance / bytecodealliance/wrpc

generate wrpc-server based on wit-bindings and homogenize the wrpc-client and wasmtime-bindings

Open
#1,424 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
Rust
Stars
336
Forks
47
Avg merge
2d 2h
Merged PRs (30d)
26

Description

Did a quick experiment to build an isomorphic wasm component that could be used locally or run remotely on a cluster. As I understand wrpc uses the same interface description but has a completely different binary serialization, and because of that the API's between wit-bindgen and wrpc-bindgen are completely different. However if the below example could be made less boilerplatte heavy (by creating a macro that implements a wrpc server based on the wit bindings) it would be cool. I can also imagine extending it to frontend components which export their html as a string. This way the component could be run server side for indexing or client side with a webview or wrapped into a browser module with jco.

```rust
use anyhow::Result;
use clap::Parser;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use wasi_hw_native::WasmCtx;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Mode {
Local,
Client,
Server,
}

impl std::str::FromStr for Mode {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result {
Ok(match s {
"local" => Mode::Local,
"client" => Mode::Client,
"server" => Mode::Server,
_ => anyhow::bail!("invalid mode {s}"),
})
}
}

#[derive(Parser)]
struct Opts {
#[arg(short, long, default_value = "local")]
mode: Mode,
#[arg(
short,
long,
default_value = "../target/wasm32-wasip2/release/wasi_hw.wasm"
)]
file: PathBuf,
#[arg(short, long, default_value = "127.0.0.1:8080")]
address: String,
}

mod client_bindings {
wit_bindgen_wrpc::generate!({
world: "hello-world-consumer",
path: "../wit",
});
}

mod server_bindings {
wit_bindgen_wrpc::generate!({
world: "hello-world-provider",
path: "../wit",
});
}

mod host_bindings {
wasmtime::component::bindgen!({
world: "hello-world-provider",
path: "../wit",
});
}

pub struct CustomWasmCtx {
ctx: WasmCtx,
}

impl CustomWasmCtx {
pub fn new(path: &Path) -> Result {
Ok(Self {
ctx: WasmCtx::new(path)?,
})
}

pub async fn local_hello_hello_world_call_hello_world(&self) -> Result {
let (mut store, instance) = self.ctx.instantiate().await?;
let bindings = host_bindings::HelloWorldProvider::new(&mut store, &instance)?;
let result = store
.run_concurrent(async |accessor| {
bindings
.local_hello_hello_world()
.call_hello_world(accessor)
.await
})
.await??;
Ok(result)
}
}

#[derive(Clone)]
struct Server {
ctx: Arc,
}

impl Server {
pub fn new(ctx: CustomWasmCtx) -> Self {
Self { ctx: Arc::new(ctx) }
}
}

impl server_bindings::exports::local::hello::hello_world::Handler for Server {
async fn hello_world(&self, _ctx: SocketAddr) -> anyhow::Result {
self.ctx.local_hello_hello_world_call_hello_world().await
}
}

#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let opts = Opts::parse();
if opts.mode == Mode::Client {
let transport = wrpc_transport::tcp::Client::from(opts.address);
let result =
client_bindings::local::hello::hello_world::hello_world(&transport, ()).await?;
println!("result: {}", result);
return Ok(());
}

let ctx = CustomWasmCtx::new(&opts.file)?;

if opts.mode == Mode::Local {
let result = ctx.local_hello_hello_world_call_hello_world().await?;
println!("result: {}", result);
return Ok(());
}

let server = Server::new(ctx);
let transport = Arc::new(wrpc_transport::Server::default());
let accept = wasi_hw_native::tcp_server(&opts.address, transport.clone()).await?;
let invocation = server_bindings::serve(transport.as_ref(), server).await?;
wasi_hw_native::wrpc_server(accept, invocation).await?;
Ok(())
}
```

Contributor guide

Open the contributing guide

Research direction

Start with the example's client_bindings, server_bindings, and host_bindings generation blocks and the ../wit interface definitions. Compare the generated wrpc client and server APIs with the wasmtime bindings, then define the smallest macro or generation change that can create a server from the WIT bindings. Done should mean less boilerplate and a consistent client/server binding interface, with the example still supporting local, client, and server modes.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, wasm
Domain
backend-api-design, distributed-systems
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.