Files
urus/examples/ws_echo.rs
T
Claude b37888ec2c docs+examples: v0.3 endpoint API; crud converted to the app-owns-the-tree shape
- crud is now the demonstrator: app owns smarm runtime + root supervisor,
  store actor and urus::endpoint as ordered siblings (store first, so
  reverse-order shutdown drains HTTP before stopping the store),
  Shutdown::Infinity on the endpoint, shutdown via
  rt.handle().request_shutdown(root_sup) from the stdin thread.
  Deletes two kludges the old shape forced:
    * static OnceLock<Sender> spawn-on-first-use -> a supervised child
      that self-registers a typed Name; handlers use smarm::send per
      request and turn 'between incarnations' into a 503 instead of
      panicking on a dropped store.
    * static SHUTTING_DOWN AtomicBool + 250ms recv_timeout poll in the
      store loop and in the SSE ticker -> a plain park; the tree stops
      both. Smoke-tested live: CRUD round-trips, SSE stream, clean drain
      with the stream open, port closed after.
- Other examples stay short and on serve*, updated for the split config
  (serve_with(cfg, smarm::Config, pipe) / serve_with_shutdown(..., signal)).
  plain_serve's URUS_SCHED_THREADS now builds a smarm::Config.
  ws_chat's doc block explains the OnceLock is a serve*-only workaround
  and points at crud for the clean shape.
- README: new 'Your Own Supervision Tree' section (endpoint as the real
  API), graceful shutdown reframed as the serve*-only path, Config table
  loses scheduler_threads and gains name, PubSub rule 1 notes the
  supervised-sibling alternative.

111 lib + 50 integration + 2 doc tests green; clippy clean.
2026-08-20 13:20:28 +00:00

104 lines
3.5 KiB
Rust

//! 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<u16>, 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, smarm::Config::default(), pipeline, signal).unwrap();
println!("ws_echo: bye");
}