Skip to content

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

Description

@dvc94ch

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.

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<Self> {
        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<Self> {
        Ok(Self {
            ctx: WasmCtx::new(path)?,
        })
    }

    pub async fn local_hello_hello_world_call_hello_world(&self) -> Result<String> {
        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<CustomWasmCtx>,
}

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

impl server_bindings::exports::local::hello::hello_world::Handler<SocketAddr> for Server {
    async fn hello_world(&self, _ctx: SocketAddr) -> anyhow::Result<String> {
        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(())
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions