WebSockets
Volter supports WebSocket upgrades via the WebSocketUpgrade extractor.
Enabling the Feature
WebSocket support is gated behind the ws feature flag (not enabled by
default):
cargo add volter --features ws
Basic Echo Server
#![allow(unused)]
fn main() {
use volter::*;
use volter::ws::{Message, WebSocketUpgrade};
async fn echo(ws: WebSocketUpgrade) -> impl IntoResponse {
ws.on_upgrade(|mut socket| async move {
while let Some(Ok(msg)) = socket.recv().await {
if socket.send(msg).await.is_err() {
break;
}
}
})
}
}
The handler receives a WebSocketUpgrade extractor. Calling
on_upgrade(callback) immediately returns a 101 Switching Protocols
response, and the callback runs in a spawned tokio task once hyper completes
the upgrade.
The WebSocket Upgrade Flow
- The handler receives a
WebSocketUpgradeparameter - Volter checks if the request has the
Upgrade: websocketheader - If yes,
on_upgrade()produces a101 Switching Protocolsresponse - The callback receives a
WebSocketwithrecv()/send()methods - If the
Upgradeheader is missing,426 Upgrade Requiredis returned - If
Sec-WebSocket-Keyis missing,400 Bad Requestis returned
Sending and Receiving Messages
#![allow(unused)]
fn main() {
use volter::ws::{Message, WebSocketUpgrade};
async fn handle_ws(ws: WebSocketUpgrade) -> impl IntoResponse {
ws.on_upgrade(|mut socket| async move {
// Send a text message
let _ = socket.send(Message::Text("Welcome!".into())).await;
// Read messages
while let Some(Ok(msg)) = socket.recv().await {
match msg {
Message::Text(text) => {
let _ = socket.send(Message::Text(format!("Echo: {text}"))).await;
}
Message::Binary(data) => {
let _ = socket.send(Message::Binary(data)).await;
}
Message::Ping(_) => {
let _ = socket.send(Message::Pong(vec![])).await;
}
Message::Close(frame) => {
let _ = socket.send(Message::Close(frame)).await;
break;
}
_ => {}
}
}
})
}
}
WebSocket with State
#![allow(unused)]
fn main() {
use volter::*;
use volter::ws::WebSocketUpgrade;
#[derive(Clone)]
struct AppState {
max_message_size: usize,
}
async fn ws_handler(
State(state): State<AppState>,
ws: WebSocketUpgrade,
) -> impl IntoResponse {
ws.on_upgrade(move |mut socket| async move {
// state is available here
})
}
}
WebSocket with Query Parameters
#![allow(unused)]
fn main() {
use serde::Deserialize;
use volter::*;
use volter::ws::WebSocketUpgrade;
#[derive(Deserialize)]
struct WsParams {
room: String,
token: String,
}
async fn chat(
Query(params): Query<WsParams>,
ws: WebSocketUpgrade,
) -> impl IntoResponse {
// Validate token and join room
ws.on_upgrade(move |mut socket| async move {
// ...
})
}
}
Important Notes
-
WebSocketUpgradeimplementsFromRequestParts, so it runs before the body is consumed -
The
on_upgradecallback runs in a spawned tokio task — state must be'staticif captured by the closure -
See the
websocketexample for a complete, runnable server:cargo run -p websocket