rws_server/channels/mod.rs
1//! Channels (18.35.0, #94): named topics that pages and other clients
2//! subscribe to over SSE or WebSocket, and that server code publishes to —
3//! "a comment was added", "the import finished". In-process: replicas
4//! don't see each other's messages (publish from where the change happens,
5//! or bridge a queue into [`Hub::publish`]).
6//!
7//! ```rust
8//! # extern crate rws_server as rust_web_server;
9//! use rust_web_server::app::App;
10//! use rust_web_server::channels::Hub;
11//!
12//! let hub = Hub::new();
13//! let app = App::with_state(hub.clone())
14//! // EventSource("/rooms/lobby/events") receives every message.
15//! .get("/rooms/:room/events", |_, params, _, hub| hub.sse(params.get("room").unwrap_or("lobby")))
16//! // A WebSocket client receives them too; what it sends is published.
17//! .websocket("/rooms/:room/ws", |ws, params, hub| {
18//! let room = params.get("room").unwrap_or("lobby").to_string();
19//! hub.serve_websocket(ws, &room, |hub, room, text| { hub.publish(room, text); });
20//! });
21//!
22//! let delivered = hub.publish("lobby", "hello");
23//! assert_eq!(0, delivered); // nobody is listening yet
24//! ```
25//!
26//! Delivery is best effort and never blocks on a slow SSE client: a
27//! message that doesn't fit its buffer is dropped for that client.
28//! WebSocket messages are written by the publishing thread. Subscribers
29//! that have gone away are removed on the next publish to their topic.
30
31#[cfg(test)]
32mod tests;
33
34use std::collections::HashMap;
35use std::sync::mpsc::{channel, Receiver, Sender};
36use std::sync::{Arc, Mutex};
37
38use crate::response::Response;
39use crate::sse::{SseEvent, SseSender, SseStream};
40use crate::websocket::{Message, WebSocketConnection, WebSocketSender};
41
42enum Subscriber {
43 Sse(SseSender),
44 WebSocket(WebSocketSender),
45 Channel(Sender<String>),
46}
47
48impl Subscriber {
49 /// Deliver `message`: `Some(true)` delivered, `Some(false)` dropped
50 /// (full), `None` the subscriber is gone.
51 fn deliver(&self, topic: &str, message: &str) -> Option<bool> {
52 match self {
53 Subscriber::Sse(tx) => tx.try_send(SseEvent::data(message).event_type(topic)).ok(),
54 Subscriber::WebSocket(ws) => (!ws.is_closed() && ws.send_text(message).is_ok()).then_some(true),
55 Subscriber::Channel(tx) => tx.send(message.to_string()).ok().map(|_| true),
56 }
57 }
58}
59
60/// Topics and their subscribers; cheap to clone (all clones share them).
61#[derive(Clone, Default)]
62pub struct Hub {
63 topics: Arc<Mutex<HashMap<String, Vec<Subscriber>>>>,
64}
65
66impl Hub {
67 pub fn new() -> Self {
68 Hub::default()
69 }
70
71 fn add(&self, topic: &str, subscriber: Subscriber) {
72 let mut topics = self.topics.lock().unwrap_or_else(|e| e.into_inner());
73 topics.entry(topic.to_string()).or_default().push(subscriber);
74 }
75
76 /// Send `message` to every subscriber of `topic`; the number it was
77 /// delivered to. SSE clients get it as an event named after the topic
78 /// (`EventSource.addEventListener(topic, …)`), WebSocket clients as a
79 /// text message.
80 pub fn publish(&self, topic: &str, message: &str) -> usize {
81 // Take the subscribers out so a slow WebSocket write doesn't hold
82 // the lock; put the live ones back after.
83 let subscribers = {
84 let mut topics = self.topics.lock().unwrap_or_else(|e| e.into_inner());
85 match topics.get_mut(topic) {
86 Some(list) => std::mem::take(list),
87 None => return 0,
88 }
89 };
90 let mut delivered = 0;
91 let mut live = Vec::with_capacity(subscribers.len());
92 for subscriber in subscribers {
93 if let Some(sent) = subscriber.deliver(topic, message) {
94 delivered += sent as usize;
95 live.push(subscriber);
96 }
97 }
98 let mut topics = self.topics.lock().unwrap_or_else(|e| e.into_inner());
99 let list = topics.entry(topic.to_string()).or_default();
100 // Subscribers that joined while this publish ran come after.
101 live.append(list);
102 if live.is_empty() {
103 topics.remove(topic);
104 } else {
105 *list = live;
106 }
107 delivered
108 }
109
110 /// A streaming SSE response subscribed to `topic`: return it from the
111 /// handler.
112 pub fn sse(&self, topic: &str) -> Response {
113 let (tx, response) = SseStream::new().start();
114 self.add(topic, Subscriber::Sse(tx));
115 response
116 }
117
118 /// Subscribe a WebSocket client (its sender) to `topic`.
119 pub fn subscribe_websocket(&self, topic: &str, sender: WebSocketSender) {
120 self.add(topic, Subscriber::WebSocket(sender));
121 }
122
123 /// Subscribe `ws` to `topic` and read its messages until it closes,
124 /// handing each text message to `on_message(hub, topic, text)`. Run it
125 /// in a `.websocket(…)` handler.
126 pub fn serve_websocket<F>(&self, mut ws: WebSocketConnection, topic: &str, on_message: F)
127 where
128 F: Fn(&Hub, &str, &str),
129 {
130 self.subscribe_websocket(topic, ws.sender());
131 while let Ok(Some(message)) = ws.recv() {
132 if let Message::Text(text) = message {
133 on_message(self, topic, &text);
134 }
135 }
136 let _ = ws.close(1000, "");
137 }
138
139 /// Subscribe in-process: every message to `topic` arrives on the
140 /// receiver until it's dropped.
141 pub fn subscribe(&self, topic: &str) -> Receiver<String> {
142 let (tx, rx) = channel();
143 self.add(topic, Subscriber::Channel(tx));
144 rx
145 }
146
147 /// How many subscribers `topic` has (some may have gone away since the
148 /// last publish).
149 pub fn subscribers(&self, topic: &str) -> usize {
150 self.topics.lock().unwrap_or_else(|e| e.into_inner()).get(topic).map_or(0, Vec::len)
151 }
152
153 /// The topics that have subscribers, sorted.
154 pub fn topics(&self) -> Vec<String> {
155 let mut topics: Vec<String> = self.topics.lock().unwrap_or_else(|e| e.into_inner()).keys().cloned().collect();
156 topics.sort();
157 topics
158 }
159}