//! Example websocket server. //! //! Run the server with //! ```not_rust //! cargo run -p example-websockets --bin example-websockets //! ``` //! //! Run a browser client with //! ```not_rust //! firefox http://localhost:3000 //! ``` //! //! Alternatively you can run the rust client (showing two //! concurrent websocket connections being established) with //! ```not_rust //! cargo run -p example-websockets --bin example-client //! ``` use axum::{ extract::ws::{Message, WebSocket, WebSocketUpgrade}, response::IntoResponse, routing::get, Router, }; use axum_extra::TypedHeader; use std::borrow::Cow; use std::ops::ControlFlow; use std::{net::SocketAddr, path::PathBuf}; use tower_http::{ services::ServeDir, trace::{DefaultMakeSpan, TraceLayer}, }; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; //allows to extract the IP of connecting user use axum::extract::connect_info::ConnectInfo; use axum::extract::ws::CloseFrame; //allows to split the websocket stream into separate TX and RX branches use futures::{sink::SinkExt, stream::StreamExt}; #[tokio::main] async fn main() { tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| "example_websockets=debug,tower_http=debug".into()), ) .with(tracing_subscriber::fmt::layer()) .init(); let assets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets"); // build our application with some routes let app = Router::new() .fallback_service(ServeDir::new(assets_dir).append_index_html_on_directories(true)) .route("/ws", get(ws_handler)) // logging so we can see whats going on .layer( TraceLayer::new_for_http() .make_span_with(DefaultMakeSpan::default().include_headers(true)), ); // run it with hyper let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .unwrap(); tracing::debug!("listening on {}", listener.local_addr().unwrap()); axum::serve( listener, app.into_make_service_with_connect_info::(), ) .await .unwrap(); } /// The handler for the HTTP request (this gets called when the HTTP GET lands at the start /// of websocket negotiation). After this completes, the actual switching from HTTP to /// websocket protocol will occur. /// This is the last point where we can extract TCP/IP metadata such as IP address of the client /// as well as things from HTTP headers such as user-agent of the browser etc. async fn ws_handler( ws: WebSocketUpgrade, user_agent: Option>, ConnectInfo(addr): ConnectInfo, ) -> impl IntoResponse { let user_agent = if let Some(TypedHeader(user_agent)) = user_agent { user_agent.to_string() } else { String::from("Unknown browser") }; println!("`{user_agent}` at {addr} connected."); // finalize the upgrade process by returning upgrade callback. // we can customize the callback by sending additional info such as address. ws.on_upgrade(move |socket| handle_socket(socket, addr)) } /// Actual websocket statemachine (one will be spawned per connection) async fn handle_socket(mut socket: WebSocket, who: SocketAddr) { //send a ping (unsupported by some browsers) just to kick things off and get a response if socket.send(Message::Ping(vec![1, 2, 3])).await.is_ok() { println!("Pinged {}...", who); } else { println!("Could not send ping {}!", who); // no Error here since the only thing we can do is to close the connection. // If we can not send messages, there is no way to salvage the statemachine anyway. return; } // receive single message from a client (we can either receive or send with socket). // this will likely be the Pong for our Ping or a hello message from client. // waiting for message from a client will block this task, but will not block other client's // connections. if let Some(msg) = socket.recv().await { if let Ok(msg) = msg { if process_message(msg, who).is_break() { return; } } else { println!("client {who} abruptly disconnected"); return; } } // Since each client gets individual statemachine, we can pause handling // when necessary to wait for some external event (in this case illustrated by sleeping). // Waiting for this client to finish getting its greetings does not prevent other clients from // connecting to server and receiving their greetings. for i in 1..5 { if socket .send(Message::Text(format!("Hi {i} times!"))) .await .is_err() { println!("client {who} abruptly disconnected"); return; } tokio::time::sleep(std::time::Duration::from_millis(100)).await; } // By splitting socket we can send and receive at the same time. In this example we will send // unsolicited messages to client based on some sort of server's internal event (i.e .timer). let (mut sender, mut receiver) = socket.split(); // Spawn a task that will push several messages to the client (does not matter what client does) let mut send_task = tokio::spawn(async move { let n_msg = 20; for i in 0..n_msg { // In case of any websocket error, we exit. if sender .send(Message::Text(format!("Server message {i} ..."))) .await .is_err() { return i; } tokio::time::sleep(std::time::Duration::from_millis(300)).await; } println!("Sending close to {who}..."); if let Err(e) = sender .send(Message::Close(Some(CloseFrame { code: axum::extract::ws::close_code::NORMAL, reason: Cow::from("Goodbye"), }))) .await { println!("Could not send Close due to {}, probably it is ok?", e); } n_msg }); // This second task will receive messages from client and print them on server console let mut recv_task = tokio::spawn(async move { let mut cnt = 0; while let Some(Ok(msg)) = receiver.next().await { cnt += 1; // print message and break if instructed to do so if process_message(msg, who).is_break() { break; } } cnt }); // If any one of the tasks exit, abort the other. tokio::select! { rv_a = (&mut send_task) => { match rv_a { Ok(a) => println!("{} messages sent to {}", a, who), Err(a) => println!("Error sending messages {:?}", a) } recv_task.abort(); }, rv_b = (&mut recv_task) => { match rv_b { Ok(b) => println!("Received {} messages", b), Err(b) => println!("Error receiving messages {:?}", b) } send_task.abort(); } } // returning from the handler closes the websocket connection println!("Websocket context {} destroyed", who); } /// helper to print contents of messages to stdout. Has special treatment for Close. fn process_message(msg: Message, who: SocketAddr) -> ControlFlow<(), ()> { match msg { Message::Text(t) => { println!(">>> {} sent str: {:?}", who, t); } Message::Binary(d) => { println!(">>> {} sent {} bytes: {:?}", who, d.len(), d); } Message::Close(c) => { if let Some(cf) = c { println!( ">>> {} sent close with code {} and reason `{}`", who, cf.code, cf.reason ); } else { println!(">>> {} somehow sent close message without CloseFrame", who); } return ControlFlow::Break(()); } Message::Pong(v) => { println!(">>> {} sent pong with {:?}", who, v); } // You should never need to manually handle Message::Ping, as axum's websocket library // will do so for you automagically by replying with Pong and copying the v according to // spec. But if you need the contents of the pings you can see them here. Message::Ping(v) => { println!(">>> {} sent ping with {:?}", who, v); } } ControlFlow::Continue(()) }