Expand description
Bidirectional RPC for browsing contexts, web workers, and message channels.
This crate allows you to define a service as a trait and annotate it with
#[web_rpc::service]. The macro then produces a *Client, a *Service,
and a forwarding trait that you can implement on the server side.
Routing is inferred from each type: anything implementing
AsRef<JsValue> is
posted through postMessage directly and everything that is serializable is first encoded
via bincode. There is special support for Option<T> and Result<T, E> to allow Javascript
types to be embedded within these types. This behaviour is recursive.
§Quickstart
#[web_rpc::service]
pub trait Calculator {
fn add(&self, left: u32, right: u32) -> u32;
}
struct Calc;
impl Calculator for Calc {
fn add(&self, left: u32, right: u32) -> u32 { left + right }
}Wire up over a MessageChannel, Worker,
or any MessagePort. Each
Each call to Interface::new is async because temporary listeners need to detect when
both ends are ready.
let channel = web_sys::MessageChannel::new().unwrap();
let (server_iface, client_iface) = futures_util::future::join(
web_rpc::Interface::new(channel.port1()),
web_rpc::Interface::new(channel.port2()),
).await;
let server = web_rpc::Builder::new(server_iface)
.with_service::<CalculatorService<_>>(Calc)
.build();
wasm_bindgen_futures::spawn_local(server);
let client = web_rpc::Builder::new(client_iface)
.with_client::<CalculatorClient>()
.build();
assert_eq!(client.add(41, 1).await, 42);§Routing
#[web_rpc::service]
pub trait Routing {
// Plain types that implement Serialize go through bincode.
fn add(&self, l: u32, r: u32) -> u32;
// Anything `AsRef<JsValue>` is posted through the JS array.
fn echo(&self, s: js_sys::JsString) -> js_sys::JsString;
// `Option`/`Result` recurse: Ok(Some(_)) is posted, Ok(None) is one byte,
// Err carries a bincoded `String`.
fn lookup(&self, k: u32) -> Result<Option<js_sys::JsString>, String>;
// `&str` / `&[u8]` deserialize zero-copy on the server.
fn count(&self, data: &[u8]) -> usize;
// References to JS types are accepted too and are decoded via JsCast::dyn_ref.
fn len(&self, s: &js_sys::JsString) -> u32;
}§Async, notifications, streaming
use futures_core::Stream;
#[web_rpc::service]
pub trait Misc {
// `async` here makes the server impl async; the client side is also async because we return a u32.
async fn slow(&self, ms: u32) -> u32;
// No return type means the method is a notification.
fn fire(&self, msg: String);
// `impl Stream<Item = T>` makes the method a streaming RPC.
fn items(&self, n: u32) -> impl Stream<Item = u32>;
}On the client side, RPC methods that have a return type are async and yield a
client::RequestFuture<T> which you await for the response. Methods without a return type
are sync and act as fire-and-forget notifications. This is independent of whether the trait
method itself is marked async, which only affects the server implementation. Dropping the
RequestFuture cancels the request, so notifications cannot be cancelled.
Streaming methods return a client::StreamReceiver<T> that yields each item the server
produces. Dropping the receiver aborts the stream on the server, while
close lets buffered items finish arriving instead.
Streaming methods can also be async and the items they yield can be wrapper types like
Result<JsT, E>.
§Transfer
Anything that should be transferred to the other side rather than copied with the structured
clone algorithm can be specified inside a #[transfer(...)] attribute as a comma-separated
list. The simplest case is to list the parameter that holds the transferable value, but if
that value is wrapped or derived from a parameter, you can use a parameter-name expression
(name => expr, evaluated with name in scope), a closure with a refutable pattern
(name => |pat| body), or a match-block (name => match { arm, ... }). The same forms also
work for the return value via return.
#[web_rpc::service]
pub trait Transfer {
// Bare param + derived expression + return closure.
#[transfer(
canvas,
data => data.buffer(),
return => |Ok(buf)| buf.buffer(),
)]
fn render(
&self,
canvas: web_sys::OffscreenCanvas,
data: js_sys::Uint8Array,
) -> Result<js_sys::Uint8Array, String>;
// Match-block: useful when several variants need transferring.
#[transfer(return => match { Some(buf) => buf.buffer(), })]
fn maybe(&self) -> Option<js_sys::Uint8Array>;
}§Bi-directional
Both sides of a channel can be set up to act as both client and server at the same time. To
do this, stack with_service and
with_client on the same Builder before calling build(), which
then returns a (C, Server) tuple instead of one or the other.
let (client, server) = web_rpc::Builder::new(iface)
.with_service::<CalculatorService<_>>(Calc)
.with_client::<CalculatorClient>()
.build();Re-exports§
pub use interface::Interface;
Modules§
Structs§
- Builder
- This struct allows one to configure the RPC interface prior to creating it.
To get an instance of this struct, call
Builder<C, S>::newwith anInterface. - Server
Serveris the server that is returned from theBuilder::buildmethod given you configured the RPC interface with a service. Note thatServerimplements future and needs to be polled in order to execute and respond to inbound RPC requests.
Attribute Macros§
- service
- This attribute macro should applied to traits that need to be turned into RPCs. The
macro will consume the trait and output three items in its place. For example,
a trait
Calculatorwill be replaced with two structsCalculatorClientandCalculatorServiceand a new trait by the same name. All methods must include&selfas their first parameter.