Rust Axum WebSocket Example: A Comprehensive Guide
Rust is gaining traction as a powerful systems programming language, and frameworks like Axum simplify building web applications. One popular feature is WebSocket support, which enables real-time, bidirectional communication between the client and server. This guide walks you through setting up a WebSocket server using Axum in Rust.
What Is Axum?
Axum is a web framework built on Tokio and Hyper, designed for performance and ergonomics. It integrates seamlessly with async programming in Rust, making it ideal for real-time applications like WebSocket communication.
What Are WebSockets?
WebSockets provide a persistent, full-duplex communication channel between the client and server. Unlike HTTP, which requires a request-response model, WebSockets allow the server to send messages to the client without being prompted.
Setting Up a Rust Project with Axum
Step 1: Create a New Rust Project
Run the following command to create a new Rust project:
cargo new axum_websocket_example
cd axum_websocket_example
Step 2: Add Dependencies
Update the Cargo.toml
file with the required dependencies:
[dependencies]
axum = "0.6"
tokio = { version = "1.0", features = ["full"] }
tower = "0.4"
hyper = { version = "0.14", features = ["websocket"] }
Run cargo build
to download and compile the dependencies.
Building a WebSocket Server with Axum
Step 3: Create a WebSocket Handler
Add the following code in src/main.rs
:
use axum::{
extract::WebSocketUpgrade,
response::IntoResponse,
routing::get,
Router,
};
use tokio::net::TcpListener;
use tokio::sync::broadcast;
use tokio::sync::Mutex;
use tokio_tungstenite::tungstenite::Message;
use std::sync::Arc;
#[tokio::main]
async fn main() {
// Create a shared broadcast channel for messages
let (tx, _rx) = broadcast::channel::<String>(100);
let state = Arc::new(Mutex::new(tx));
// Build the Axum application
let app = Router::new()
.route("/ws", get({
let state = state.clone();
move |ws: WebSocketUpgrade| handle_websocket(ws, state.clone())
}));
// Start the server
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
println!("Listening on http://127.0.0.1:3000");
axum::Server::from_tcp(listener)
.unwrap()
.serve(app.into_make_service())
.await
.unwrap();
}
async fn handle_websocket(
ws: WebSocketUpgrade,
state: Arc<Mutex<broadcast::Sender<String>>>,
) -> impl IntoResponse {
ws.on_upgrade(move |socket| handle_socket(socket, state))
}
async fn handle_socket(
mut socket: axum::extract::ws::WebSocket,
state: Arc<Mutex<broadcast::Sender<String>>>,
) {
let tx = state.lock().await.clone();
let mut rx = tx.subscribe();
// Spawn a task to receive messages from other clients
tokio::spawn(async move {
while let Ok(msg) = rx.recv().await {
if let Ok(_) = socket.send(Message::Text(msg)).await {
continue;
}
break;
}
});
// Read messages from the client and broadcast them
while let Some(Ok(msg)) = socket.recv().await {
if let Message::Text(text) = msg {
let _ = tx.send(text);
}
}
}
How the Code Works
- WebSocketUpgrade: The
/ws
route upgrades HTTP requests to a WebSocket connection. - Broadcast Channel: Uses
tokio::sync::broadcast
to share messages between all connected WebSocket clients. - Async Communication:
- Listens for client messages and broadcasts them to all subscribers.
- Sends broadcast messages back to the client.
Testing the WebSocket Server
Using a WebSocket Client in JavaScript
To test the WebSocket server, create a simple HTML file (index.html
) with the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Test</title>
</head>
<body>
<h1>WebSocket Client</h1>
<textarea id="output" cols="50" rows="10" readonly></textarea><br>
<input type="text" id="input" placeholder="Type a message" />
<button onclick="sendMessage()">Send</button>
<script>
const ws = new WebSocket("ws://127.0.0.1:3000/ws");
const output = document.getElementById("output");
const input = document.getElementById("input");
ws.onmessage = (event) => {
output.value += `\n${event.data}`;
};
function sendMessage() {
const message = input.value;
ws.send(message);
input.value = "";
}
</script>
</body>
</html>
Run the WebSocket Client
- Open the HTML file in your browser.
- Connect to the WebSocket server (
ws://127.0.0.1:3000/ws
). - Send messages using the input box and observe real-time communication.
Best Practices
- Error Handling:
- Add proper error handling for connection issues and unexpected input.
- Scalability:
- Use a load balancer or scale horizontally for high traffic.
- Security:
- Implement authentication and SSL/TLS to secure WebSocket communication.
Conclusion
This example demonstrates how to create a WebSocket server using Axum in Rust. By leveraging async capabilities and tools like broadcast channels, you can efficiently build real-time, scalable applications. Experiment with this code to customize your WebSocket server for chat apps, live notifications, or other real-time services.