A WireframeApp collects route handlers and middleware. Each handler is stored
as an Arc pointing to an async function that receives a packet reference and
returns (). The builder caches these registrations until handle_connection
constructs the middleware chain for an accepted stream.[^2]
use std::sync::Arc;
use wireframe::app::{Envelope, Handler, WireframeApp};
async fn ping(_env: &Envelope) {}
fn build_app() -> wireframe::Result<WireframeApp> {
let handler: Handler<Envelope> = Arc::new(|env: &Envelope| {
let _ = env; // inspect payload here
Box::pin(ping(env))
});
WireframeApp::new()?
.route(1, handler)?
.wrap(wireframe::middleware::from_fn(|req, next| async move {
let mut response = next.call(req).await?;
response.frame_mut().extend_from_slice(b" pong");
Ok(response)
}))
}
The snippet below wires the builder into a Tokio runtime, decodes inbound
payloads, and emits a serialised response. It showcases the typical main
function for a microservice that listens on localhost and responds to a Ping
message with a Pong payload.[^2][^10][^15]
use std::{net::SocketAddr, sync::Arc};
use wireframe::{
app::{Envelope, Handler, WireframeApp},
middleware,
message::Message,
server::{ServerError, WireframeServer},
};
#[derive(bincode::Encode, bincode::BorrowDecode, Debug)]
struct Ping {
body: String,
}
#[derive(bincode::Encode, bincode::BorrowDecode, Debug, PartialEq)]
struct Pong {
body: String,
}
async fn ping(env: &Envelope) {
log::info!("received correlation id: {:?}", env.clone().into_parts().correlation_id());
}
fn build_app() -> wireframe::app::Result<WireframeApp> {
let handler: Handler<Envelope> = Arc::new(|env: &Envelope| Box::pin(ping(env)));
WireframeApp::new()?
.route(1, handler)?
.wrap(middleware::from_fn(|req, next| async move {
let ping = Ping::from_bytes(req.frame()).map(|(msg, _)| msg).ok();
let mut response = next.call(req).await?;
if let Some(ping) = ping {
let payload = Pong {
body: format!("pong {}", ping.body),
}
.to_bytes()
.expect("encode Pong message");
response.frame_mut().clear();
response.frame_mut().extend_from_slice(&payload);
}
Ok(response)
}))
}
fn app_factory() -> WireframeApp {
build_app().expect("configure Wireframe application")
}
#[tokio::main]
async fn main() -> Result<(), ServerError> {
let addr: SocketAddr = "127.0.0.1:4000".parse().expect("valid socket address");
let server = WireframeServer::new(app_factory).bind(addr)?;
server.run().await
}
Route identifiers must be unique; the builder returns
WireframeError::DuplicateRoute when you try to register a handler twice,
keeping the dispatch table unambiguous.[^2][^5] New applications default to the
bundled bincode serializer, a 1024-byte frame buffer, and a 100 ms read
timeout. Clamp these limits with buffer_capacity and read_timeout_ms, or
swap the serializer with with_serializer when you need a different encoding
strategy.[^3][^4]
Once a stream is accepted—either from a manual accept loop or via
WireframeServer—handle_connection(stream) builds (or reuses) the middleware
chain, wraps the transport in a length-delimited codec, enforces per-frame read
timeouts, and writes responses. Serialization helpers send_response and
send_response_framed return typed SendError variants when encoding or I/O
fails, and the connection closes after ten consecutive deserialization
errors.[^6][^7]