Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub

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

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

ComponentRolePlatform Dependency
RpcMethodPrebuffered traitDefines method contractNone - pure Rust traits
METHOD_IDCompile-time generated hashNone - const expression
encode_request / decode_requestParameter serializationNone - uses bitcode
encode_response / decode_responseResult serializationNone - 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&lt;RpcWasmClient&gt;"]
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

AspectNative (RpcClient)WASM (RpcWasmClient)
Connection establishmentRpcClient::new() creates WebSocketJavaScript creates WebSocket, then init_static_client()
HeartbeatAutomatic via background taskManaged by JavaScript
ReconnectionMust create new RpcClient instanceManaged by JavaScript
Disconnection detectionReceive loop detects broken connectionJavaScript calls handle_disconnect()
State change notificationAutomatic 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_connected is an AtomicBool managed internally
  • shutdown_async() and shutdown_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_connected is an AtomicBool updated by explicit calls
  • JavaScript must call handle_connect() and handle_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 tokio and tokio-tungstenite
  • WASM client imports wasm-bindgen and js-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 TypeNative ClientWASM ClientServer
Connection lifecycletransport_state_tests.rs:36-165Simulated via manual state calls✓ Tokio server
Request cancellationtransport_state_tests.rs:169-292Simulated✓ Tokio server
Concurrent requestsVia 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:

  1. Shared Service Definitions : RpcMethodPrebuffered trait enables type-safe, platform-agnostic service contracts
  2. Abstract Client Interface : RpcServiceCallerInterface allows application code to work with any client implementation
  3. Platform-Specific Transports : RpcClient for native Tokio and RpcWasmClient for WASM, both implementing the same interface
  4. Minimal Platform Code : Only client instantiation and connection management are platform-specific
  5. 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