- 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.
104 lines
3.5 KiB
Rust
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");
|
|
}
|