rust 48 lines · 8 steps

How an Axum WebSocket echo server works

An Axum handler upgrades an HTTP request to a WebSocket, then loops over incoming frames and echoes them back to the client.

Explained by highlit
1use axum::{
2 extract::ws::{Message, WebSocket, WebSocketUpgrade},
3 response::IntoResponse,
4};
5use futures::{sink::SinkExt, stream::StreamExt};
6use std::net::SocketAddr;
7use tracing::{info, warn};
8 
9pub async fn echo_handler(
10 ws: WebSocketUpgrade,
11 axum::extract::ConnectInfo(addr): axum::extract::ConnectInfo<SocketAddr>,
12) -> impl IntoResponse {
13 ws.on_upgrade(move |socket| handle_socket(socket, addr))
14}
15 
16async fn handle_socket(socket: WebSocket, addr: SocketAddr) {
17 let (mut sender, mut receiver) = socket.split();
18 
19 while let Some(msg) = receiver.next().await {
20 let msg = match msg {
21 Ok(msg) => msg,
22 Err(err) => {
23 warn!(%addr, %err, "websocket receive error");
24 break;
25 }
26 };
27 
28 match msg {
29 Message::Text(text) => {
30 if sender.send(Message::Text(text)).await.is_err() {
31 break;
32 }
33 }
34 Message::Binary(bytes) => {
35 if sender.send(Message::Binary(bytes)).await.is_err() {
36 break;
37 }
38 }
39 Message::Ping(payload) => {
40 let _ = sender.send(Message::Pong(payload)).await;
41 }
42 Message::Close(_) => break,
43 _ => {}
44 }
45 }
46 
47 info!(%addr, "websocket connection closed");
48}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Axum's WebSocketUpgrade defers the protocol switch until you hand it an async task via on_upgrade.
  2. 2Splitting a socket into a sender and receiver lets you read and write the same connection independently.
  3. 3Explicitly matching each WebSocket frame type keeps control frames like Ping and Close handled correctly, not just data.

Related explainers

Share this explainer

Here's the card — post it anywhere.

How an Axum WebSocket echo server works — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code