This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Request and Response Types
Loading…
Request and Response Types
Relevant source files
- src/rpc/rpc_dispatcher.rs
- src/rpc/rpc_internals/rpc_header.rs
- src/rpc/rpc_internals/rpc_respondable_session.rs
- src/rpc/rpc_request_response.rs
- tests/rpc_dispatcher_tests.rs
Purpose and Scope
This document defines the core data structures used to represent RPC requests and responses in the muxio framework: RpcRequest, RpcResponse, and RpcHeader. These types provide a runtime-agnostic, schemaless representation of method invocations and their corresponding replies. They serve as the fundamental building blocks for all RPC communication in the system.
For information about how these types are processed and routed, see RPC Dispatcher. For information about how they are serialized and transmitted at the binary level, see Binary Framing Protocol.
Type Hierarchy and Relationships
The request-response system is built on three primary types that work together to enable method invocation and correlation:
Sources:
graph TB
subgraph "Application Layer"
USER["User Code\nService Definitions"]
end
subgraph "RPC Protocol Layer Types"
REQ["RpcRequest\nsrc/rpc/rpc_request_response.rs"]
RESP["RpcResponse\nsrc/rpc/rpc_request_response.rs"]
HDR["RpcHeader\nsrc/rpc/rpc_internals/rpc_header.rs"]
end
subgraph "Processing Components"
DISP["RpcDispatcher::call()\nRpcDispatcher::respond()"]
SESSION["RpcRespondableSession"]
end
USER -->|constructs| REQ
USER -->|constructs| RESP
REQ -->|converted to| HDR
RESP -->|converted to| HDR
HDR -->|can be converted back to| RESP
DISP -->|processes| REQ
DISP -->|processes| RESP
DISP -->|delegates to| SESSION
style REQ fill:#f9f9f9
style RESP fill:#f9f9f9
style HDR fill:#f9f9f9
- src/rpc/rpc_request_response.rs:1-105
- src/rpc/rpc_internals/rpc_header.rs:1-25
- src/rpc/rpc_dispatcher.rs:1-457
RpcHeader Structure
RpcHeader is the internal protocol-level representation of RPC metadata transmitted in frame headers. It contains all information necessary to route and identify an RPC message.
Field Description
| Field | Type | Purpose |
|---|---|---|
rpc_msg_type | RpcMessageType | Discriminates between Call, Response, Event, etc. |
rpc_request_id | u32 | Unique identifier for request-response correlation |
rpc_method_id | u64 | Identifier (typically hash) of the method being invoked |
rpc_metadata_bytes | Vec<u8> | Schemaless encoded parameters or status information |
Type Definition
The complete structure is defined at src/rpc/rpc_internals/rpc_header.rs:3-24:
Metadata Semantics
The rpc_metadata_bytes field has different interpretations depending on the message type:
- For Calls : Contains serialized method parameters (e.g., bitcode-encoded arguments)
- For Responses : Contains a single-byte result status code, or is empty if no status is provided
- Empty Vector : Valid and indicates no metadata is present
Sources:
- src/rpc/rpc_internals/rpc_header.rs:1-25
- src/rpc/rpc_dispatcher.rs:250-257 (Call metadata handling)
- src/rpc/rpc_dispatcher.rs:311-319 (Response metadata handling)
RpcRequest Structure
RpcRequest represents an outbound RPC call initiated by a client. It is constructed by user code and passed to RpcDispatcher::call() for transmission.
Field Description
| Field | Type | Purpose |
|---|---|---|
rpc_method_id | u64 | Unique method identifier (typically xxhash of method name) |
rpc_param_bytes | Option<Vec<u8>> | Serialized method parameters (None if no parameters) |
rpc_prebuffered_payload_bytes | Option<Vec<u8>> | Optional payload to send after header |
is_finalized | bool | If true, stream ends immediately after header+payload |
Type Definition
Defined at src/rpc/rpc_request_response.rs:9-33:
Usage Patterns
Finalized Request (Single-Frame RPC)
When the entire request is known upfront:
This pattern is demonstrated at tests/rpc_dispatcher_tests.rs:42-49
Streaming Request (Multi-Frame RPC)
When payload will be written incrementally:
Conversion to RpcHeader
When RpcDispatcher::call() is invoked, the RpcRequest is converted to an RpcHeader at src/rpc/rpc_dispatcher.rs:249-257:
Sources:
- src/rpc/rpc_request_response.rs:3-33
- src/rpc/rpc_dispatcher.rs:227-286
- tests/rpc_dispatcher_tests.rs:30-203
RpcResponse Structure
RpcResponse represents the reply to a prior RPC request. It is constructed on the server side and passed to RpcDispatcher::respond() for transmission back to the client.
Field Description
| Field | Type | Purpose |
|---|---|---|
rpc_request_id | u32 | Must match the original request’s rpc_request_id for correlation |
rpc_method_id | u64 | Should match the original request’s rpc_method_id |
rpc_result_status | Option<u8> | Optional status byte (by convention, 0 = success) |
rpc_prebuffered_payload_bytes | Option<Vec<u8>> | Serialized response payload |
is_finalized | bool | If true, stream ends immediately after header+payload |
Type Definition
Defined at src/rpc/rpc_request_response.rs:40-76:
Construction from RpcHeader
The from_rpc_header() method provides a convenience constructor for server-side response creation at src/rpc/rpc_request_response.rs:90-103:
Usage Example
Server-side response construction from tests/rpc_dispatcher_tests.rs:161-167:
Conversion to RpcHeader
When RpcDispatcher::respond() is invoked, the RpcResponse is converted to an RpcHeader at src/rpc/rpc_dispatcher.rs:307-319:
Sources:
- src/rpc/rpc_request_response.rs:35-105
- src/rpc/rpc_dispatcher.rs:298-337
- tests/rpc_dispatcher_tests.rs:150-190
sequenceDiagram
participant Client as "Client Code"
participant Disp as "RpcDispatcher"
participant IDGen as "next_rpc_request_id\n(u32 counter)"
participant Queue as "rpc_request_queue\nArc<Mutex<VecDeque>>"
Note over Client,Queue: Request Path
Client->>Disp: call(RpcRequest)
Disp->>IDGen: Allocate ID
IDGen-->>Disp: rpc_request_id = N
Disp->>Disp: Build RpcHeader with\nrpc_request_id=N
Note over Disp: Transmit request frames...
Note over Client,Queue: Response Path
Disp->>Disp: Receive response frames
Disp->>Disp: Parse RpcHeader from frames
Disp->>Queue: Find request by rpc_request_id=N
Queue-->>Disp: Matched RpcRequest entry
Disp->>Disp: Invoke response handler
Disp->>Queue: Remove entry on End/Error
Request-Response Correlation Mechanism
The system correlates requests and responses using the rpc_request_id field, which is managed by the dispatcher’s monotonic ID generator.
ID Generation
The dispatcher maintains a monotonic counter at src/rpc/rpc_dispatcher.rs42:
Each call increments this counter using increment_u32_id() at src/rpc/rpc_dispatcher.rs:241-242:
Request Queue Management
Inbound requests (responses from the remote peer) are tracked in a shared queue at src/rpc/rpc_dispatcher.rs50:
The queue is populated by the catch-all response handler at src/rpc/rpc_dispatcher.rs:122-141:
- Header Event : Creates new
RpcRequestentry withrpc_request_id - PayloadChunk Event : Appends bytes to matching request’s payload
- End Event : Marks request as finalized
Requests can be retrieved and removed using:
get_rpc_request(header_id)at src/rpc/rpc_dispatcher.rs:381-394is_rpc_request_finalized(header_id)at src/rpc/rpc_dispatcher.rs:399-405delete_rpc_request(header_id)at src/rpc/rpc_dispatcher.rs:411-420
Sources:
- src/rpc/rpc_dispatcher.rs:31-51
- src/rpc/rpc_dispatcher.rs:227-286
- src/rpc/rpc_dispatcher.rs:99-209
- src/utils.rs (increment_u32_id implementation)
graph LR
subgraph "Client Side - Outbound Call"
C1["User constructs\nRpcRequest"]
C2["RpcDispatcher::call()"]
C3["Convert to RpcHeader\nrpc_msg_type=Call"]
C4["RpcStreamEncoder\nSerialize + Frame"]
C5["Bytes on wire"]
end
subgraph "Server Side - Inbound Call"
S1["Bytes received"]
S2["RpcSession::read_bytes()"]
S3["Decode to RpcHeader"]
S4["RpcStreamEvent::Header\n+PayloadChunk\n+End"]
S5["Reconstruct RpcRequest\nin rpc_request_queue"]
S6["User retrieves\ndelete_rpc_request()"]
end
subgraph "Server Side - Outbound Response"
R1["User constructs\nRpcResponse"]
R2["RpcDispatcher::respond()"]
R3["Convert to RpcHeader\nrpc_msg_type=Response"]
R4["RpcStreamEncoder\nSerialize + Frame"]
R5["Bytes on wire"]
end
subgraph "Client Side - Inbound Response"
R6["Bytes received"]
R7["RpcSession::read_bytes()"]
R8["Decode to RpcHeader"]
R9["RpcStreamEvent fired\nto response_handler"]
R10["User processes\nresponse payload"]
end
C1 --> C2
C2 --> C3
C3 --> C4
C4 --> C5
C5 -.network.-> S1
S1 --> S2
S2 --> S3
S3 --> S4
S4 --> S5
S5 --> S6
R1 --> R2
R2 --> R3
R3 --> R4
R4 --> R5
R5 -.network.-> R6
R6 --> R7
R7 --> R8
R8 --> R9
R9 --> R10
Data Flow Through Type Transformations
The following diagram illustrates how request and response data flows through type transformations:
Sources:
- src/rpc/rpc_dispatcher.rs:227-286 (Call path)
- src/rpc/rpc_dispatcher.rs:298-337 (Response path)
- src/rpc/rpc_dispatcher.rs:99-209 (Inbound request reconstruction)
- src/rpc/rpc_internals/rpc_respondable_session.rs:93-173 (Stream event handling)
Field Semantics and Special Cases
Prebuffered Payloads
Both RpcRequest and RpcResponse support rpc_prebuffered_payload_bytes:
- Purpose : Allows sending the entire payload in a single transmission without manual streaming
- Transmission : Sent immediately after header via
encoder.write_bytes()at src/rpc/rpc_dispatcher.rs:270-276 and src/rpc/rpc_dispatcher.rs:327-329 - Use Case : Suitable for small to medium-sized payloads where chunking overhead is undesirable
Finalization Flag
The is_finalized field controls stream lifecycle:
true: Stream is ended immediately after header and prebuffered payload are sentfalse: Caller retains theRpcStreamEncoderand must manually callend_stream()- Implementation : See src/rpc/rpc_dispatcher.rs:279-283 and src/rpc/rpc_dispatcher.rs:331-334
Result Status Conventions
While the core library does not enforce semantics for rpc_result_status, the following conventions are commonly used:
| Value | Meaning |
|---|---|
Some(0) | Success |
Some(1) | Generic error |
Some(2+) | Custom error codes |
None | No status information |
This convention is referenced in the documentation at src/rpc/rpc_request_response.rs:61-62
Sources:
- src/rpc/rpc_request_response.rs:1-105
- src/rpc/rpc_dispatcher.rs:227-337
- tests/rpc_dispatcher_tests.rs:30-203
Complete Request-Response Lifecycle Example
The following table illustrates a complete request-response cycle from the test suite at tests/rpc_dispatcher_tests.rs:42-198:
| Step | Location | Action | Data Structure |
|---|---|---|---|
| 1 | Client | Construct request | RpcRequest { method_id: ADD_METHOD_ID, param_bytes: Some(encoded), prebuffered_payload: None, is_finalized: true } |
| 2 | Client | Call dispatcher | client_dispatcher.call(rpc_request, 4, on_emit, on_response, true) |
| 3 | Client | Convert to header | RpcHeader { msg_type: Call, request_id: 1, method_id: ADD_METHOD_ID, metadata: encoded_params } |
| 4 | Transport | Transmit bytes | Binary frames written to outgoing_buf |
| 5 | Server | Receive bytes | server_dispatcher.read_bytes(chunk) |
| 6 | Server | Decode to events | RpcStreamEvent::Header, RpcStreamEvent::End |
| 7 | Server | Reconstruct request | Entry added to rpc_request_queue with (request_id, RpcRequest) |
| 8 | Server | Retrieve request | server_dispatcher.delete_rpc_request(request_id) |
| 9 | Server | Process and respond | RpcResponse { request_id: 1, method_id: ADD_METHOD_ID, result_status: Some(0), payload: encoded_result, is_finalized: true } |
| 10 | Server | Convert to header | RpcHeader { msg_type: Response, request_id: 1, method_id: ADD_METHOD_ID, metadata: [0] } |
| 11 | Transport | Transmit response | Binary frames via server_dispatcher.respond() |
| 12 | Client | Receive response | client_dispatcher.read_bytes() routes to on_response handler |
| 13 | Client | Process result | User code decodes payload from RpcStreamEvent::PayloadChunk |
Sources: