Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ async fn handle_echo(req: Message<EchoRequest>) -> WireframeResult<EchoResponse>

WireframeServer::new(|| {
WireframeApp::new()
.serialization_format(SerializationFormat::Bincode)
.serializer(BincodeSerializer)
.route(MyMessageType::Echo, handle_echo)
})
.bind("127.0.0.1:8000")?
Expand All @@ -78,6 +78,14 @@ WireframeServer::new(|| {
This example showcases how derive macros and the framing abstraction simplify a
binary protocol server【F:docs/rust-binary-router-library-design.md†L1120-L1150】.

## Response Serialization and Framing

Handlers can return types implementing the `Responder` trait. These values are
encoded using the application's configured serializer and written
back through the `FrameProcessor`【F:docs/rust-binary-router-library-design.md†L718-L724】.
The included `LengthPrefixedProcessor` illustrates a simple framing strategy
based on a big‑endian length prefix【F:docs/rust-binary-router-library-design.md†L1076-L1117】.

## Current Limitations

Connection processing is not implemented yet. After the optional
Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ after formatting. Line numbers below refer to that file.
user-configured callbacks on decode success or failure. See
[preamble-validator](preamble-validator.md).

- [ ] Add response serialization and transmission. Encode handler responses
- [x] Add response serialization and transmission. Encode handler responses
using the selected serialization format and write them back through the
framing layer.

Expand Down
182 changes: 95 additions & 87 deletions docs/rust-binary-router-library-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,8 @@ mechanism to dispatch this message to the appropriate user-defined handler.
.message_guarded(
MessageType::GenericCommand,


<!-- markdownlint-disable MD009 MD025 MD033 MD024 -->
````

|msg_header: &CommandHeader| msg_header.sub_type == CommandSubType::Special,
Expand Down Expand Up @@ -718,7 +720,10 @@ messages and optionally producing responses.

- A specific message type that implements a `wireframe::Responder` trait
(analogous to Actix Web's `Responder` trait 4). This trait defines how the
returned value is serialized and sent back to the client.
returned value is serialized and sent back to the client. When a handler
yields such a value, `wireframe` encodes it using the application’s
configured serializer and passes the resulting bytes to the `FrameProcessor`
for transmission back to the peer.
- `Result<ResponseType, ErrorType>`: For explicit error handling. If
`Ok(response_message)`, the message is sent. If `Err(error_value)`, the
error is processed by "wireframe's" error handling mechanism (see Section
Expand Down Expand Up @@ -1115,55 +1120,65 @@ examples are invaluable. They make the abstract design tangible and showcase how

````

`````

<!-- markdownlint-disable MD009 MD025 MD033 MD024 -->

3. **Server Setup and Handler**:

Rust

````rustrust
```rustrust
// Crate: main.rs
use wireframe::{WireframeApp, WireframeServer, Message, error::Result as WireframeResult, config::SerializationFormat};
use my_protocol_messages::{EchoRequest, EchoResponse};
use my_frame_processor::LengthPrefixedCodec; // Or wireframe's abstraction
use std::time::{SystemTime, UNIX_EPOCH};
```

// Define a message ID enum if not using type-based routing directly
#
enum MyMessageType { Echo = 1 }

// Handler function
async fn handle_echo(req: Message<EchoRequest>) -> WireframeResult<EchoResponse> {
println!("Received echo request with payload: {}", req.payload);
Ok(EchoResponse {
original_payload: req.payload.clone(),
echoed_at: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
})
}
use wireframe::{WireframeApp, WireframeServer, Message, error::Result as
WireframeResult, serializer::BincodeSerializer}; use
my_protocol_messages::{EchoRequest, EchoResponse}; use
my_frame_processor::LengthPrefixedCodec; // Or wireframe's abstraction use
std::time::{SystemTime, UNIX_EPOCH};

#[tokio::main]
async fn main() -> std::io::Result<()> {
println!("Starting echo server on 127.0.0.1:8000");

WireframeServer::new(|| {
WireframeApp::new()
//.frame_processor(LengthPrefixedCodec) // Simplified
.serialization_format(SerializationFormat::Bincode) // Specify serializer
.route(MyMessageType::Echo, handle_echo) // Route based on ID
// OR if type-based routing is supported and EchoRequest has an ID:
//.service(handle_echo_typed) where handle_echo_typed takes Message<EchoRequest>
})
.bind("127.0.0.1:8000")?
.run()
.await
}
// Define a message ID enum if not using type-based routing directly

```rust
#

This example, even in outline, demonstrates how derive macros for messages,
a separable framing component, and a clear handler signature with
extractors (`Message<EchoRequest>`) and a return type
(`WireframeResult<EchoResponse>`) simplify server implementation.
enum MyMessageType { Echo = 1 }

````
// Handler function async fn handle_echo(req: Message<EchoRequest>) ->
WireframeResult<EchoResponse> { println!("Received echo request with payload:
{}", req.payload); Ok(EchoResponse { original_payload: req.payload.clone(),
echoed_at: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), })
}

#[tokio::main] async fn main() -> std::io::Result\<()> { println!("Starting
echo server on 127.0.0.1:8000");

```
WireframeServer::new(|| {
WireframeApp::new()
//.frame_processor(LengthPrefixedCodec) // Simplified
.serializer(BincodeSerializer) // Specify serializer
.route(MyMessageType::Echo, handle_echo) // Route based on ID
// OR if type-based routing is supported and EchoRequest has an ID:
//.service(handle_echo_typed) where handle_echo_typed takes Message<EchoRequest>
})
.bind("127.0.0.1:8000")?
.run()
.await
```

<!-- markdownlint-enable MD009 MD025 MD033 MD024 -->

}

```rust

This example, even in outline, demonstrates how derive macros for messages,
a separable framing component, and a clear handler signature with
extractors (`Message<EchoRequest>`) and a return type
(`WireframeResult<EchoResponse>`) simplify server implementation.

```

- **Example 2: Basic Chat Message Protocol**

Expand Down Expand Up @@ -1217,62 +1232,53 @@ examples are invaluable. They make the abstract design tangible and showcase how

Rust

````rustrust
```rustrust
// Crate: main.rs
use wireframe::{WireframeApp, WireframeServer, Message, ConnectionInfo, error::Result as WireframeResult, config::SerializationFormat};
use my_chat_messages::{ClientMessage, ServerMessage};
//... use ChatRoomState, SharedChatRoomState...
use std::sync::Arc;

#
enum ChatMessageType { ClientJoin = 10, ClientPost = 11 }


async fn handle_join(
msg: Message<ClientMessage>, // Assume it's ClientMessage::Join
conn_info: ConnectionInfo,
state: SharedChatRoomState,
) -> WireframeResult<Option<ServerMessage>> { // Optional direct response
if let ClientMessage::Join { user_name } = msg.into_inner() {
let mut room = state.lock().await;
//... logic to add user, check for name conflicts...
// room.add_user(conn_info.id(), user_name.clone());
// Broadcast ServerMessage::UserJoined to other users (not shown)
println!("User '{}' joined from {}", user_name, conn_info.peer_addr());
return Ok(None); // No direct response, or maybe an Ack
}
Ok(Some(ServerMessage::JoinError { reason: "Invalid Join message".to_string() }))
}

async fn handle_post(
msg: Message<ClientMessage>, // Assume it's ClientMessage::Post
conn_info: ConnectionInfo,
state: SharedChatRoomState,
) { // No direct response needed
if let ClientMessage::Post { content } = msg.into_inner() {
let room = state.lock().await;
// let user_name = room.get_user_name(conn_info.id()).unwrap_or_default();
// Broadcast ServerMessage::NewMessage to other users (not shown)
// println!("User '{}' posted: {}", user_name, content);
}
}
```

#[tokio::main]
async fn main() -> std::io::Result<()> {
let chat_state = Arc::new(Mutex::new(ChatRoomState { users: HashMap::new() }));
WireframeServer::new(move |
use wireframe::{WireframeApp, WireframeServer, Message, ConnectionInfo,
error::Result as WireframeResult, serializer::BincodeSerializer}; use
my_chat_messages::{ClientMessage, ServerMessage}; //... use ChatRoomState,
SharedChatRoomState... use std::sync::Arc;

#

enum ChatMessageType { ClientJoin = 10, ClientPost = 11 }

async fn handle_join( msg: Message<ClientMessage>, // Assume it's
ClientMessage::Join conn_info: ConnectionInfo, state: SharedChatRoomState, )
-> WireframeResult\<Option<ServerMessage>> { // Optional direct response if
let ClientMessage::Join { user_name } = msg.into_inner() { let mut room =
state.lock().await; //... logic to add user, check for name conflicts... //
room.add_user(conn_info.id(), user_name.clone()); // Broadcast
ServerMessage::UserJoined to other users (not shown) println!("User '{}'
joined from {}", user_name, conn_info.peer_addr()); return Ok(None); // No
direct response, or maybe an Ack } Ok(Some(ServerMessage::JoinError { reason:
"Invalid Join message".to_string() })) }

async fn handle_post( msg: Message<ClientMessage>, // Assume it's
ClientMessage::Post conn_info: ConnectionInfo, state: SharedChatRoomState, ) {
// No direct response needed if let ClientMessage::Post { content } =
msg.into_inner() { let room = state.lock().await; // let user_name =
room.get_user_name(conn_info.id()).unwrap_or_default(); // Broadcast
ServerMessage::NewMessage to other users (not shown) // println!("User '{}'
posted: {}", user_name, content); } }

#[tokio::main] async fn main() -> std::io::Result\<()> { let chat_state =
Arc::new(Mutex::new(ChatRoomState { users: HashMap::new() }));
WireframeServer::new(move |

```rust
```rust

````
```

| {
| { |

WireframeApp::new()

//.frame_processor(...)

.serialization_format(SerializationFormat::Bincode)
.serializer(BincodeSerializer)

.app_data(SharedChatRoomState::new(chat_state.clone()))

Expand All @@ -1290,7 +1296,9 @@ WireframeApp::new()

}

\`\`\`
`````

<!-- markdownlint-enable MD009 MD025 MD033 MD024 -->

This chat example hints at how shared state (SharedChatRoomState) and connection
information (ConnectionInfo) would be used, and how handlers might not always
Expand Down
Loading