This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Examples and Tutorials
Relevant source files
- README.md
- assets/Muxio-logo.svg
- extensions/README.md
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs
This page provides an overview of practical examples demonstrating rust-muxio usage patterns. It covers the core example application, common testing patterns, and basic usage workflows. For a detailed walkthrough of the WebSocket RPC application, see WebSocket RPC Application. For a step-by-step tutorial on building services, see Simple Calculator Service.
The examples in this system demonstrate:
- Service definition patterns using
RpcMethodPrebuffered - Server-side handler registration with
RpcServiceEndpointInterface - Client-side invocation using
RpcServiceCallerInterface - Cross-platform testing with native Tokio and WASM clients
- Error handling and large payload streaming
Sources: README.md:1-166 extensions/README.md:1-4
Available Example Resources
The codebase includes several resources demonstrating different aspects of the system:
| Resource | Location | Purpose |
|---|---|---|
| Main Example Application | examples/example-muxio-ws-rpc-app/ | Complete WebSocket RPC server and client |
| Shared Service Definitions | examples/example-muxio-rpc-service-definition/ | Reusable service contracts |
| Tokio Client Tests | extensions/muxio-tokio-rpc-client/tests/ | Native client integration tests |
| WASM Client Tests | extensions/muxio-wasm-rpc-client/tests/ | WASM client integration tests |
Sources: README.md:67-68 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-241 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-313
Service Definition to Client Call Flow
This diagram maps the complete flow from defining a service method to executing it on a client, using actual type and function names from the codebase:
Sources: README.md:69-161 extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:1-99 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:34-61
graph TB
subgraph "Service Definition Crate"
AddStruct["Add struct"]
RpcMethodPrebuffered["impl RpcMethodPrebuffered"]
METHOD_ID["Add::METHOD_ID"]
encode_request["Add::encode_request()"]
decode_response["Add::decode_response()"]
end
subgraph "Server Registration"
endpoint["RpcServiceEndpointInterface"]
register_prebuffered["endpoint.register_prebuffered()"]
handler["Handler closure"]
decode_req["Add::decode_request()"]
encode_resp["Add::encode_response()"]
end
subgraph "Client Invocation"
RpcClient["RpcClient or RpcWasmClient"]
RpcCallPrebuffered["RpcCallPrebuffered trait"]
call_method["Add::call()"]
call_rpc_buffered["client.call_rpc_buffered()"]
end
AddStruct --> RpcMethodPrebuffered
RpcMethodPrebuffered --> METHOD_ID
RpcMethodPrebuffered --> encode_request
RpcMethodPrebuffered --> decode_response
METHOD_ID --> register_prebuffered
register_prebuffered --> handler
handler --> decode_req
handler --> encode_resp
encode_request --> call_method
decode_response --> call_method
call_method --> RpcCallPrebuffered
RpcCallPrebuffered --> call_rpc_buffered
call_rpc_buffered --> RpcClient
Basic Server Setup Pattern
The standard pattern for creating and configuring an RPC server involves these steps:
sequenceDiagram
participant App as "Application Code"
participant TcpListener as "TcpListener"
participant RpcServer as "RpcServer::new()"
participant Endpoint as "server.endpoint()"
participant Task as "tokio::spawn()"
App->>TcpListener: bind("127.0.0.1:0")
App->>RpcServer: Create with optional config
App->>Endpoint: Get endpoint handle
loop For each RPC method
Endpoint->>Endpoint: register_prebuffered(METHOD_ID, handler)
end
App->>Task: Spawn server task
Task->>RpcServer: serve_with_listener(listener)
Note over RpcServer: Server now accepting\nWebSocket connections
The server is wrapped in Arc<RpcServer> to allow sharing between the registration code and the spawned task. The endpoint() method returns a handle implementing RpcServiceEndpointInterface for registering handlers before the server starts.
Sources: README.md:86-128 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:21-71
Handler Registration Code Pattern
Handler registration uses register_prebuffered() with the compile-time generated METHOD_ID and an async closure:
graph LR
subgraph "Handler Components"
METHOD_ID["Add::METHOD_ID\n(compile-time hash)"]
closure["Async closure:\n/request_bytes, _ctx/"]
decode["Add::decode_request()"]
logic["Business logic:\nrequest_params.iter().sum()"]
encode["Add::encode_response()"]
end
METHOD_ID --> register["endpoint.register_prebuffered()"]
closure --> register
closure --> decode
decode --> logic
logic --> encode
encode --> return["Ok(response_bytes)"]
Example handler registration from the tests:
- Decode request bytes using
Add::decode_request(&request_bytes)? - Execute business logic (e.g.,
request_params.iter().sum()) - Encode response using
Add::encode_response(result)? - Return
Ok(response_bytes)orErr(...)for errors
Sources: README.md:100-117 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:35-60
Client Connection and Call Pattern
Client setup and RPC invocation follows this structure:
The client implements RpcServiceCallerInterface, which is used by the RpcCallPrebuffered trait to handle all encoding, transmission, and decoding automatically.
Sources: README.md:130-161 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:75-96
Complete Integration Test Structure
Integration tests demonstrate the full client-server setup in a single test function:
graph TB
subgraph "Test Setup Phase"
bind["TcpListener::bind()"]
create_server["Arc::new(RpcServer::new())"]
get_endpoint["server.endpoint()"]
register["Register all handlers"]
spawn_server["tokio::spawn(server.serve())"]
end
subgraph "Test Execution Phase"
sleep["tokio::time::sleep()"]
create_client["RpcClient::new()"]
make_calls["Execute RPC calls with join!()"]
assertions["Assert results"]
end
bind --> create_server
create_server --> get_endpoint
get_endpoint --> register
register --> spawn_server
spawn_server --> sleep
sleep --> create_client
create_client --> make_calls
make_calls --> assertions
The join! macro enables concurrent RPC calls over a single connection, demonstrating multiplexing in action. All requests are sent and responses are awaited in parallel.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:16-97
Example: Concurrent RPC Calls
The test files demonstrate making multiple concurrent RPC calls using tokio::join!:
All six calls execute concurrently over the same WebSocket connection. The RpcDispatcher assigns unique request IDs and correlates responses back to the correct futures.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:81-96 README.md:144-151
Example: Error Handling
The integration tests show how RPC errors propagate from server to client:
Example error handling code from tests:
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:99-152
Example: Large Payload Handling
The system automatically chunks large payloads. The integration tests verify this with payloads 200x larger than the chunk size:
| Test Scenario | Payload Size | Mechanism |
|---|---|---|
| Small request | < DEFAULT_SERVICE_MAX_CHUNK_SIZE | Sent in rpc_param_bytes field |
| Large request | ≥ DEFAULT_SERVICE_MAX_CHUNK_SIZE | Sent via rpc_prebuffered_payload_bytes with automatic chunking |
| Large response | Any size | Automatically chunked by RpcDispatcher |
Test code creates a payload of DEFAULT_SERVICE_MAX_CHUNK_SIZE * 200 (approximately 12.8 MB) and verifies it round-trips correctly:
The chunking and reassembly happen transparently in the RpcDispatcher layer.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:154-203 extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:30-72
WASM Client Testing Pattern
WASM client tests use a bridge pattern to connect the client to a real server:
The bridge creates an unbounded_channel to receive bytes from the WASM client's output callback, then forwards them through a real WebSocket connection. Responses flow back through spawn_blocking to avoid blocking the async runtime when calling the synchronous dispatcher.blocking_lock().
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-142
Example: Method Not Found Error
When a client calls a method that hasn't been registered on the server, the system returns RpcServiceErrorCode::NotFound:
This demonstrates the system's ability to distinguish between different error types: NotFound for missing handlers versus System for handler execution failures.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:205-240
State Change Callbacks
Clients can register callbacks to monitor connection state changes:
Example from the README:
Sources: README.md:138-141
Service Definition Module Structure
Service definitions are typically organized in a separate crate shared by both client and server:
| Component | Purpose | Example |
|---|---|---|
RpcMethodPrebuffered impl | Defines method contract | impl RpcMethodPrebuffered for Add |
METHOD_ID constant | Compile-time generated hash | Add::METHOD_ID |
Input type | Request parameters type | Vec<f64> |
Output type | Response result type | f64 |
encode_request() | Serializes input | Uses bitcode::encode() |
decode_request() | Deserializes input | Uses bitcode::decode() |
encode_response() | Serializes output | Uses bitcode::encode() |
decode_response() | Deserializes output | Uses bitcode::decode() |
This shared definition ensures compile-time type safety between client and server implementations.
Sources: README.md:49-50 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-6
Testing Utilities
The muxio-ext-test crate provides utilities for testing, though the primary testing pattern uses real server and client instances as shown in the integration tests. The tests demonstrate:
- Using
TcpListener::bind("127.0.0.1:0")for random available ports - Extracting host and port with
tcp_listener_to_host_port() - Using
tokio::time::sleep()for synchronization - Spawning server tasks with
tokio::spawn() - Making concurrent calls with
tokio::join!()
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:9-14
Common Patterns Summary
| Pattern | Implementation | Use Case |
|---|---|---|
| Server setup | Arc::new(RpcServer::new()) | Share between registration and serving |
| Handler registration | endpoint.register_prebuffered() | Before calling serve_with_listener() |
| Client creation | RpcClient::new(host, port) | Tokio-based native client |
| WASM client | RpcWasmClient::new(callback) | Browser/WASM environment |
| RPC invocation | Method::call(&client, params) | Type-safe method calls |
| Concurrent calls | tokio::join!(...) | Multiple simultaneous requests |
| Error handling | match RpcServiceError | Distinguish error types |
| Large payloads | Automatic chunking | Transparent for > 64KB data |
Sources: README.md:69-161 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:18-97
Dismiss
Refresh this wiki
Enter email to refresh