Cookbook / Realtime
Broadcast across instances
Publish a message once and stream it to every subscriber on every instance, with resume after a reconnect.
With one server, sse::channel is enough. With several instances behind a load balancer, the browser might reconnect to a different one, and a message published on one instance must reach clients connected to the others. A broadcast::Hub does both. Publishing writes to a shared log; every instance streams from it, and a browser that reconnects with Last-Event-ID gets what it missed from whichever instance it reaches.
use axeon::broadcast::{Hub, MemoryBackend, Message};
use axeon::{Json, Server, ServerError};
use axeon::json;
fn main() {
// One process: MemoryBackend. Several: RedisBroadcast or PgBroadcast.
let hub = Hub::new(MemoryBackend::new());
let mut app = Server::new();
app.state(hub);
// Clients subscribe to a topic. `last_event_id` resumes after a reconnect.
app.get("/runs/:run/events", |req| async move {
let run: u64 = req.param("run")?;
let hub = req.state::<Hub>().unwrap();
hub.sse(&format!("run:{}", run), req.last_event_id()).await
});
// Anything can publish: a handler, a background job, another instance.
app.post("/runs/:run/steps/:step", |req| async move {
let run: u64 = req.param("run")?;
let step: u32 = req.param("step")?;
let hub = req.state::<Hub>().unwrap();
let message = Message::new(format!(r#"{{"step":{}}}"#, step)).event("step");
hub.publish(&format!("run:{}", run), message)
.await
.map_err(|e| ServerError::InternalError(e.to_string()))?;
Ok::<_, ServerError>(Json(json!({ "published": step })))
});
app.listen("127.0.0.1:3000").expect("server failed to start");
}
curl -N http://127.0.0.1:3000/runs/42/events &
curl -X POST http://127.0.0.1:3000/runs/42/steps/3
Notes#
- If the client's last message has already been trimmed from the log, the stream starts with a
resetevent so the client can reload its state instead of showing a gap. - Slow subscribers catch up from the log instead of dropping messages, and each instance uses one wake-up connection no matter how many clients it serves.
MemoryBackendis for one process and tests. For several instances useaxeon_redis::RedisBroadcast(see Redis) oraxeon_orm::broadcast::PgBroadcast, which publishes inside your database transaction so a message goes out only if the commit succeeds.- For a single instance with no resume, plain server-sent events are simpler.