Skip to main content

rainier_websocket/
socket.rs

1//! The handle a handler holds — [`Socket`].
2
3use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
4use std::sync::Arc;
5
6use serde::Serialize;
7use tokio::sync::mpsc;
8
9use rainier_support::{Error, Result};
10
11use crate::message::Message;
12
13/// A unique id for one connection, for the life of the process.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct SocketId(u64);
16
17impl SocketId {
18    /// The next id. Monotonic, so ordering by it is arrival order.
19    pub fn next() -> Self {
20        static NEXT: AtomicU64 = AtomicU64::new(1);
21        Self(NEXT.fetch_add(1, Ordering::Relaxed))
22    }
23
24    /// The id as a number, for logs and for keying a map.
25    pub fn get(self) -> u64 {
26        self.0
27    }
28}
29
30impl std::fmt::Display for SocketId {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        write!(f, "{}", self.0)
33    }
34}
35
36/// One connected client.
37///
38/// Cheap to clone and safe to keep: sending goes through a channel, so a handle
39/// stored in a room registry does not hold the connection's task or its
40/// buffers. Sending to a socket that has gone away is an error, not a panic.
41///
42/// ```ignore
43/// socket.send("hello").await?;
44/// socket.send_json(&Update { count: 3 }).await?;
45/// socket.close().await;
46/// ```
47#[derive(Debug, Clone)]
48pub struct Socket {
49    id: SocketId,
50    path: Arc<str>,
51    params: Arc<Vec<(String, String)>>,
52    outbound: mpsc::UnboundedSender<Outbound>,
53    closed: Arc<AtomicBool>,
54}
55
56/// What the connection task pulls off the queue.
57#[derive(Debug)]
58pub enum Outbound {
59    /// Send this frame.
60    Send(Message),
61    /// Close the connection, with an optional reason.
62    Close(Option<String>),
63}
64
65impl Socket {
66    /// Build a handle over `outbound`.
67    ///
68    /// The transport owns the receiving half; a handler is only ever given
69    /// this side.
70    pub fn new(
71        id: SocketId,
72        path: impl Into<Arc<str>>,
73        params: Vec<(String, String)>,
74        outbound: mpsc::UnboundedSender<Outbound>,
75    ) -> Self {
76        Self {
77            id,
78            path: path.into(),
79            params: Arc::new(params),
80            outbound,
81            closed: Arc::new(AtomicBool::new(false)),
82        }
83    }
84
85    /// This connection's id.
86    pub fn id(&self) -> SocketId {
87        self.id
88    }
89
90    /// The path it connected to.
91    pub fn path(&self) -> &str {
92        &self.path
93    }
94
95    /// A route parameter captured from the path — `rooms/{room}` gives
96    /// `room`.
97    pub fn param(&self, name: &str) -> Option<&str> {
98        self.params.iter().find(|(key, _)| key == name).map(|(_, value)| value.as_str())
99    }
100
101    /// A route parameter, parsed.
102    ///
103    /// A **bad request** when it will not parse: the value came from the URL
104    /// the client asked for.
105    pub fn parse_param<T>(&self, name: &str) -> Result<T>
106    where
107        T: std::str::FromStr,
108        T::Err: std::fmt::Display,
109    {
110        let raw = self
111            .param(name)
112            .ok_or_else(|| Error::internal(format!("the route captured no `{name}`")))?;
113
114        raw.parse::<T>()
115            .map_err(|e| Error::bad_request(format!("`{name}` in the path is invalid: {e}")))
116    }
117
118    /// Every captured parameter.
119    pub fn params(&self) -> &[(String, String)] {
120        &self.params
121    }
122
123    /// Send a frame.
124    ///
125    /// Queued rather than written: a handler that awaited the socket would
126    /// block on a slow client, and one slow client must not hold up the task
127    /// reading everyone else's messages.
128    pub fn send(&self, message: impl Into<Message>) -> Result<()> {
129        self.push(Outbound::Send(message.into()))
130    }
131
132    /// Send a value as a JSON text frame.
133    pub fn send_json(&self, value: &impl Serialize) -> Result<()> {
134        self.send(Message::json(value)?)
135    }
136
137    /// Close the connection.
138    ///
139    /// Idempotent: closing twice is not an error, because two halves of a
140    /// handler deciding to close at once is normal.
141    pub fn close(&self) -> Result<()> {
142        if self.closed.swap(true, Ordering::SeqCst) {
143            return Ok(());
144        }
145        self.push(Outbound::Close(None))
146    }
147
148    /// Close with a reason the peer will see.
149    pub fn close_with(&self, reason: impl Into<String>) -> Result<()> {
150        if self.closed.swap(true, Ordering::SeqCst) {
151            return Ok(());
152        }
153        self.push(Outbound::Close(Some(reason.into())))
154    }
155
156    /// Whether this handle has been asked to close, or the connection has
157    /// gone.
158    pub fn is_closed(&self) -> bool {
159        self.closed.load(Ordering::SeqCst) || self.outbound.is_closed()
160    }
161
162    fn push(&self, outbound: Outbound) -> Result<()> {
163        self.outbound.send(outbound).map_err(|_| {
164            // The peer disconnected. Routine — a registry holding stale
165            // handles finds out this way — so it is an error to handle, not a
166            // panic and not a silent success.
167            Error::internal(format!("socket {} has gone away", self.id))
168        })
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    fn socket() -> (Socket, mpsc::UnboundedReceiver<Outbound>) {
177        let (tx, rx) = mpsc::unbounded_channel();
178        (Socket::new(SocketId::next(), "/ws/rooms/7", vec![("room".into(), "7".into())], tx), rx)
179    }
180
181    #[test]
182    fn a_send_is_queued_not_written() {
183        let (socket, mut rx) = socket();
184
185        socket.send("hello").unwrap();
186
187        match rx.try_recv().unwrap() {
188            Outbound::Send(Message::Text(text)) => assert_eq!(text, "hello"),
189            other => panic!("{other:?}"),
190        }
191    }
192
193    #[test]
194    fn route_parameters_come_through() {
195        let (socket, _rx) = socket();
196
197        assert_eq!(socket.param("room"), Some("7"));
198        assert_eq!(socket.parse_param::<u64>("room").unwrap(), 7);
199        assert_eq!(socket.param("nope"), None);
200    }
201
202    #[test]
203    fn a_parameter_that_will_not_parse_is_a_bad_request() {
204        let (tx, _rx) = mpsc::unbounded_channel();
205        let socket =
206            Socket::new(SocketId::next(), "/ws/rooms/x", vec![("room".into(), "x".into())], tx);
207
208        assert_eq!(socket.parse_param::<u64>("room").unwrap_err().status(), 400);
209    }
210
211    #[test]
212    fn closing_twice_is_not_an_error() {
213        let (socket, mut rx) = socket();
214
215        socket.close().unwrap();
216        socket.close().unwrap();
217
218        assert!(matches!(rx.try_recv(), Ok(Outbound::Close(None))));
219        assert!(rx.try_recv().is_err(), "only one close should be queued");
220        assert!(socket.is_closed());
221    }
222
223    #[test]
224    fn sending_to_a_socket_that_went_away_is_an_error_not_a_panic() {
225        let (socket, rx) = socket();
226        drop(rx);
227
228        let err = socket.send("hello").unwrap_err();
229        assert!(err.message().contains("gone away"), "{}", err.message());
230        assert!(socket.is_closed());
231    }
232
233    #[test]
234    fn ids_are_unique_and_ordered() {
235        let first = SocketId::next();
236        let second = SocketId::next();
237
238        assert!(second > first);
239    }
240}