This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Cross-Platform Deployment
Relevant source files
- README.md
- extensions/muxio-rpc-service-caller/src/lib.rs
- extensions/muxio-rpc-service-caller/tests/dynamic_channel_tests.rs
- extensions/muxio-tokio-rpc-client/src/lib.rs
- extensions/muxio-tokio-rpc-client/src/rpc_client.rs
- extensions/muxio-tokio-rpc-client/tests/transport_state_tests.rs
- extensions/muxio-wasm-rpc-client/src/lib.rs
- extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs
- extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs
Purpose and Scope
This document explains how to deploy rust-muxio RPC services across multiple platforms—specifically native environments using Tokio and web browsers using WebAssembly. The core principle is "write once, deploy everywhere": the same service definitions and application logic can be used by both native clients and WASM clients without modification.
For information about implementing custom transports beyond the provided Tokio and WASM clients, see Custom Transport Implementation. For details on JavaScript/WASM integration patterns, see JavaScript/WASM Integration. For service definition mechanics, see Service Definitions.
Sources : README.md:1-166 [Diagram 2 from high-level architecture]
Cross-Platform Architecture Overview
The rust-muxio system achieves cross-platform deployment through careful separation of concerns. The architecture layers are designed so that platform-specific code is isolated to the transport implementations, while the core multiplexing logic, RPC protocol, and service definitions remain platform-agnostic.
graph TB
subgraph "Platform Agnostic"
SERVICE_DEF["Service Definitions\nRpcMethodPrebuffered trait\nexample-muxio-rpc-service-definition"]
CORE["muxio Core\nRpcDispatcher\nBinary Framing"]
RPC_PROTOCOL["RPC Protocol\nRpcRequest/RpcResponse\nMethod ID routing"]
end
subgraph "Native Platform"
TOKIO_SERVER["muxio-tokio-rpc-server\nRpcServer"]
TOKIO_CLIENT["muxio-tokio-rpc-client\nRpcClient"]
TOKIO_RT["tokio runtime\ntokio-tungstenite"]
end
subgraph "Web Platform"
WASM_CLIENT["muxio-wasm-rpc-client\nRpcWasmClient"]
JS_BRIDGE["wasm-bindgen\nJavaScript WebSocket"]
BROWSER["Browser Environment"]
end
SERVICE_DEF --> CORE
CORE --> RPC_PROTOCOL
RPC_PROTOCOL --> TOKIO_SERVER
RPC_PROTOCOL --> TOKIO_CLIENT
RPC_PROTOCOL --> WASM_CLIENT
TOKIO_SERVER --> TOKIO_RT
TOKIO_CLIENT --> TOKIO_RT
WASM_CLIENT --> JS_BRIDGE
JS_BRIDGE --> BROWSER
TOKIO_CLIENT -.WebSocket.-> TOKIO_SERVER
WASM_CLIENT -.WebSocket.-> TOKIO_SERVER
Layered Abstraction Model
The critical architectural insight is that both RpcClient and RpcWasmClient implement the same RpcServiceCallerInterface trait extensions/muxio-rpc-service-caller/src/caller_interface.rs:1-11 This allows application code to be written against the interface rather than a specific implementation.
Sources : README.md:34-48 extensions/muxio-tokio-rpc-client/src/rpc_client.rs:278-335 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:154-181
Shared Service Definitions
Cross-platform deployment relies on shared service definitions that work identically on all platforms. Service definitions are typically placed in a separate crate that both client and server depend on.
Service Definition Structure
| Component | Role | Platform Dependency |
|---|---|---|
RpcMethodPrebuffered trait | Defines method contract | None - pure Rust traits |
METHOD_ID | Compile-time generated hash | None - const expression |
encode_request / decode_request | Parameter serialization | None - uses bitcode |
encode_response / decode_response | Result serialization | None - uses bitcode |
The service definition crate is a standard Rust library with no platform-specific dependencies. Here's how different platforms use it:
graph LR
subgraph "example-muxio-rpc-service-definition"
ADD["Add::METHOD_ID\nAdd::encode_request\nAdd::decode_request"]
MULT["Mult::METHOD_ID\nMult::encode_request\nMult::decode_request"]
ECHO["Echo::METHOD_ID\nEcho::encode_request\nEcho::decode_request"]
end
subgraph "Native Client"
TOKIO_APP["Application Code"]
TOKIO_CLIENT["RpcClient"]
end
subgraph "WASM Client"
WASM_APP["Application Code"]
WASM_CLIENT["RpcWasmClient"]
end
subgraph "Server"
SERVER["RpcServer"]
ENDPOINT["RpcServiceEndpoint"]
end
ADD --> TOKIO_APP
ADD --> WASM_APP
ADD --> ENDPOINT
MULT --> TOKIO_APP
MULT --> WASM_APP
MULT --> ENDPOINT
ECHO --> TOKIO_APP
ECHO --> WASM_APP
ECHO --> ENDPOINT
TOKIO_APP --> TOKIO_CLIENT
WASM_APP --> WASM_CLIENT
ENDPOINT --> SERVER
Both native and WASM clients use identical invocation code. The only difference is how the client instance is created.
Sources : README.md:49-50 README.md:69-160
Native Deployment with Tokio
Native deployment uses the Tokio async runtime and provides full-featured client and server implementations.
graph TB
APP["Application main"]
SERVER["RpcServer::new"]
ENDPOINT["endpoint.register_prebuffered"]
LISTENER["TcpListener::bind"]
SERVE["server.serve_with_listener"]
APP --> SERVER
SERVER --> ENDPOINT
ENDPOINT --> |"handler: |bytes, ctx| async {...}"|ENDPOINT
APP --> LISTENER
LISTENER --> SERVE
subgraph "Per-Connection"
ACCEPT["Accept WebSocket"]
DISPATCHER["RpcDispatcher"]
HANDLER["Handler invocation"]
RESPOND["Send response"]
ACCEPT --> DISPATCHER
DISPATCHER --> HANDLER
HANDLER --> RESPOND
end
SERVE --> ACCEPT
Server Setup
The RpcServer [extensions/muxio-tokio-rpc-server/] uses Axum and Tokio-Tungstenite for WebSocket transport:
Server handlers are registered by METHOD_ID and receive deserialized requests. The server is platform-agnostic in its handler logic—handlers work with bytes and don't know if the client is native or WASM.
Client Setup
The RpcClient extensions/muxio-tokio-rpc-client/src/rpc_client.rs:54-271 establishes a WebSocket connection and manages concurrent RPC calls:
The client spawns three background tasks: heartbeat for connection health, receive loop for incoming data, and send loop for outgoing data. The Arc<RpcClient> is returned, allowing concurrent RPC calls from multiple tasks.
Sources : extensions/muxio-tokio-rpc-client/src/rpc_client.rs:110-271 extensions/muxio-tokio-rpc-client/src/rpc_client.rs:278-335
WASM Deployment for Web Browsers
WASM deployment compiles the client to WebAssembly and bridges to JavaScript's WebSocket API. The key difference from native deployment is that the WASM client does not manage the WebSocket connection—JavaScript does.
graph TB
subgraph "Rust WASM"
WASM_CLIENT["RpcWasmClient::new(emit_callback)"]
DISPATCHER["RpcDispatcher"]
ENDPOINT["RpcServiceEndpoint"]
READ_BYTES["read_bytes(bytes)"]
HANDLE_CONNECT["handle_connect()"]
HANDLE_DISCONNECT["handle_disconnect()"]
end
subgraph "JavaScript Host"
WS["WebSocket"]
ON_OPEN["onopen"]
ON_MESSAGE["onmessage"]
ON_CLOSE["onclose"]
EMIT_FN["emit function"]
end
subgraph "Application Code"
INIT["init_static_client()"]
RPC_CALL["Method::call()"]
end
INIT --> WASM_CLIENT
WASM_CLIENT --> |callback|EMIT_FN
EMIT_FN --> WS
WS --> ON_OPEN
WS --> ON_MESSAGE
WS --> ON_CLOSE
ON_OPEN --> HANDLE_CONNECT
ON_MESSAGE --> READ_BYTES
ON_CLOSE --> HANDLE_DISCONNECT
RPC_CALL --> DISPATCHER
READ_BYTES --> DISPATCHER
READ_BYTES --> ENDPOINT
WASM Client Architecture
The RpcWasmClient extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:17-181 is constructed with an emit_callback that sends bytes to JavaScript. JavaScript manages the WebSocket lifecycle and calls Rust methods when events occur.
graph LR
JS_INIT["JavaScript: init()"]
RUST_INIT["init_static_client()"]
STATIC_REF["MUXIO_STATIC_RPC_CLIENT_REF\nthread_local RefCell"]
CLIENT["Arc<RpcWasmClient>"]
subgraph "Application Code"
GET["get_static_client()"]
WITH_ASYNC["with_static_client_async(closure)"]
RPC["Method::call()"]
end
JS_INIT --> RUST_INIT
RUST_INIT --> STATIC_REF
STATIC_REF --> CLIENT
GET --> STATIC_REF
WITH_ASYNC --> STATIC_REF
WITH_ASYNC --> RPC
Static Client Pattern
For WASM, a common pattern is to use a static global client instance initialized once at application startup:
The static client pattern extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:9-81 provides init_static_client() for initialization, get_static_client() for synchronous access, and with_static_client_async() for async operations that return JavaScript promises.
Sources : extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:26-152 extensions/muxio-wasm-rpc-client/src/static_lib/static_client.rs:13-81
Platform-Specific Implementation Details
While service definitions and application logic are shared, each platform has implementation differences in how it manages connections and state.
Connection Management
| Aspect | Native (RpcClient) | WASM (RpcWasmClient) |
|---|---|---|
| Connection establishment | RpcClient::new() creates WebSocket | JavaScript creates WebSocket, then init_static_client() |
| Heartbeat | Automatic via background task | Managed by JavaScript |
| Reconnection | Must create new RpcClient instance | Managed by JavaScript |
| Disconnection detection | Receive loop detects broken connection | JavaScript calls handle_disconnect() |
| State change notification | Automatic via shutdown_async() | Manual via handle_connect() / handle_disconnect() |
State Handling Differences
Both clients implement RpcServiceCallerInterface, but state management differs:
Native Client extensions/muxio-tokio-rpc-client/src/rpc_client.rs:56-108:
is_connectedis anAtomicBoolmanaged internallyshutdown_async()andshutdown_sync()handle disconnection- Background tasks automatically trigger state transitions
- State change handler invoked from background tasks
WASM Client extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:37-143:
is_connectedis anAtomicBoolupdated by explicit calls- JavaScript must call
handle_connect()andhandle_disconnect() - No background tasks—all events are synchronous from JavaScript
- State change handler invoked from explicit lifecycle methods
Error Propagation
Both clients fail pending requests on disconnection using fail_all_pending_requests() extensions/muxio-tokio-rpc-client/src/rpc_client.rs102 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:130-133 This ensures that RPC calls awaiting responses receive errors rather than hanging indefinitely.
Sources : extensions/muxio-tokio-rpc-client/src/rpc_client.rs:54-108 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:26-143
Build Process for Different Targets
Native Build
For native deployment, build is straightforward using standard Cargo:
# Server (includes Tokio server and service definition)
cargo build --release -p example-muxio-ws-rpc-app
# Client (includes Tokio client and service definition)
cargo build --release -p muxio-tokio-rpc-client
Both server and client depend on the same service definition crate. The binary includes the full Tokio runtime and WebSocket libraries.
WASM Build
WASM deployment requires building with the wasm32-unknown-unknown target:
# Install wasm32 target if not present
rustup target add wasm32-unknown-unknown
# Build WASM client
cargo build --release --target wasm32-unknown-unknown -p muxio-wasm-rpc-client
# Generate JavaScript bindings
wasm-bindgen target/wasm32-unknown-unknown/release/muxio_wasm_rpc_client.wasm \
--out-dir ./output \
--target web
The WASM build excludes Tokio dependencies and uses wasm-bindgen for JavaScript interop. The resulting .wasm file and JavaScript glue code can be loaded in any modern browser.
Conditional Compilation
The codebase uses feature flags and conditional compilation to handle platform differences. For example:
- Native client imports
tokioandtokio-tungstenite - WASM client imports
wasm-bindgenandjs-sys - Service definitions have no platform-specific imports
Sources : README.md:53-61 extensions/muxio-wasm-rpc-client/src/rpc_wasm_client.rs:1-11 extensions/muxio-tokio-rpc-client/src/rpc_client.rs:1-19
Application Code Portability
The key benefit of cross-platform deployment is that application code can be written once and used on all platforms. Here's how this works in practice:
Generic Application Code Pattern
Application code can be written against RpcServiceCallerInterface:
The same do_work function can accept either RpcClient or RpcWasmClient because both implement RpcServiceCallerInterface. The only platform-specific code is client instantiation.
Integration Example from Tests
The integration tests demonstrate cross-platform compatibility by running the same test logic against different client types extensions/muxio-tokio-rpc-client/tests/transport_state_tests.rs:1-292:
Both test paths use identical RPC invocation code and assertion logic. The test validates that both clients produce identical results when communicating with the same server.
Sources : README.md:46-48 extensions/muxio-tokio-rpc-client/tests/transport_state_tests.rs:15-165 extensions/muxio-rpc-service-caller/tests/dynamic_channel_tests.rs:15-88
Testing Cross-Platform Compatibility
The system provides multiple mechanisms for testing cross-platform code:
Mock Client Pattern
For unit tests, a mock client can implement RpcServiceCallerInterface without actual network communication extensions/muxio-rpc-service-caller/tests/dynamic_channel_tests.rs:19-88:
The mock client allows testing application logic without starting actual servers or WebSocket connections.
Integration Test Strategy
Integration tests validate that both client types work correctly with a real server:
| Test Type | Native Client | WASM Client | Server |
|---|---|---|---|
| Connection lifecycle | ✓ transport_state_tests.rs:36-165 | Simulated via manual state calls | ✓ Tokio server |
| Request cancellation | ✓ transport_state_tests.rs:169-292 | Simulated | ✓ Tokio server |
| Concurrent requests | ✓ | Via JavaScript concurrency | ✓ |
| Error propagation | ✓ | ✓ | ✓ |
The integration tests ensure that cross-platform abstractions work correctly in practice, not just in theory.
Sources : extensions/muxio-rpc-service-caller/tests/dynamic_channel_tests.rs:15-167 extensions/muxio-tokio-rpc-client/tests/transport_state_tests.rs:15-292
Summary
Cross-platform deployment in rust-muxio is achieved through:
- Shared Service Definitions :
RpcMethodPrebufferedtrait enables type-safe, platform-agnostic service contracts - Abstract Client Interface :
RpcServiceCallerInterfaceallows application code to work with any client implementation - Platform-Specific Transports :
RpcClientfor native Tokio andRpcWasmClientfor WASM, both implementing the same interface - Minimal Platform Code : Only client instantiation and connection management are platform-specific
- Consistent Testing : Mock clients and integration tests validate cross-platform compatibility
The architecture ensures that developers write service logic once and deploy to native and web environments without modification.
Dismiss
Refresh this wiki
Enter email to refresh