//! WebSocket echo server (v0.4). //! //! cargo run --example ws_echo //! websocat ws://127.0.0.1:8080/echo //! //! Demonstrates the chunk-3 handler model: //! - `EchoHandler` runs INSIDE the connection actor's select loop — echo //! is exactly the workload that wants zero extra moving parts. //! - `/clock` shows the other shape: the handler spawns a producer actor //! at `on_message("start")` and hands it a `WsSender` clone — the //! SSE-producer pattern, verbatim. The producer exits when its send //! fails (`WsClosed`: client gone or server shutting down). //! //! Routing happens before the upgrade, so one server can host both //! endpoints — `Conn::upgrade(handler)` is just another thing a route //! handler returns. use std::time::Duration; use urus::{ serve_with_shutdown, shutdown_handle, Config, Conn, Message, Next, Pipeline, Router, WsHandler, WsSender, }; // --------------------------------------------------------------------------- // /echo — everything comes straight back // --------------------------------------------------------------------------- struct EchoHandler; impl WsHandler for EchoHandler { fn on_message(&mut self, msg: Message, sender: &WsSender) { if matches!(&msg, Message::Text(t) if t == "bye") { let _ = sender.close(1000, "you said bye"); return; } let _ = sender.send(msg); } fn on_close(&mut self, code: Option, reason: &str) { println!("ws_echo: /echo closed (code {code:?}, reason {reason:?})"); } } // --------------------------------------------------------------------------- // /clock — "start" spawns a producer actor ticking once a second // --------------------------------------------------------------------------- struct ClockHandler { started: bool, } impl WsHandler for ClockHandler { fn on_message(&mut self, msg: Message, sender: &WsSender) { match msg { Message::Text(t) if t == "start" && !self.started => { self.started = true; let out = sender.clone(); smarm::spawn(move || { let mut n = 0u64; loop { if out.text(format!("tick {n}")).is_err() { return; // WsClosed: connection over, we follow } n += 1; smarm::sleep(Duration::from_secs(1)); } }); } _ => { let _ = sender.text("send \"start\" to begin"); } } } } // --------------------------------------------------------------------------- // main // --------------------------------------------------------------------------- fn main() { let pipeline = Pipeline::new().plug( Router::new() .get("/echo", |c: Conn, _n: Next| c.upgrade(EchoHandler)) .get("/clock", |c: Conn, _n: Next| { c.upgrade(ClockHandler { started: false }) }), ); let cfg = Config::new("127.0.0.1:8080".parse().unwrap()); println!("ws_echo: ws://127.0.0.1:8080/echo and /clock — press Enter to shut down"); let (handle, signal) = shutdown_handle(); std::thread::spawn(move || { let mut line = String::new(); let _ = std::io::stdin().read_line(&mut line); println!("ws_echo: shutting down…"); handle.shutdown(); }); serve_with_shutdown(cfg, pipeline, signal).unwrap(); println!("ws_echo: bye"); }