Skip to main content

trillium_websockets/
websocket_handler.rs

1use crate::{Error, WebSocketConn};
2use async_tungstenite::tungstenite::{Message, protocol::CloseFrame};
3use futures_lite::stream::{Pending, Stream};
4use std::future::Future;
5
6/// # This is the trait that defines a handler for trillium websockets.
7///
8/// There are several mutually-exclusive ways to use this trait, and it is
9/// intended to be flexible for different use cases. If the trait does not
10/// support your use case, please open a discussion and/or build a trait
11/// on top of this trait to add additional functionality.
12///
13/// ## Simple Example
14/// ```
15/// use futures_lite::stream::{Pending, pending};
16/// use trillium_websockets::{Message, WebSocket, WebSocketConn, WebSocketHandler};
17///
18/// struct EchoServer;
19/// impl WebSocketHandler for EchoServer {
20///     type OutboundStream = Pending<Message>;
21///
22///     // we don't use an outbound stream in this example
23///
24///     async fn connect(
25///         &self,
26///         conn: WebSocketConn,
27///     ) -> Option<(WebSocketConn, Self::OutboundStream)> {
28///         Some((conn, pending()))
29///     }
30///
31///     async fn inbound(&self, message: Message, conn: &mut WebSocketConn) {
32///         let path = conn.path().to_string();
33///         if let Message::Text(input) = message {
34///             let reply = format!("received your message: {} at path {}", &input, &path);
35///             conn.send_string(reply).await;
36///         }
37///     }
38/// }
39///
40/// let handler = WebSocket::new(EchoServer);
41/// # // tests at tests/tests.rs for example simplicity
42/// ```
43///
44///
45/// ## Using [`WebSocketHandler::connect`] only
46///
47/// If you have needs that are not supported by this trait, you can either
48/// pass an `Fn(WebSocketConn) -> impl Future<Output=()>` as a handler, or
49/// implement your own connect-only trait implementation that takes the
50/// WebSocketConn and returns None. The tcp connection will remain intact
51/// until the WebSocketConn is dropped, so you can store it in any data
52/// structure or move it between threads as needed.
53///
54/// ## Using Streams
55///
56/// If you define an associated OutboundStream type and return it from
57/// `connect`, every message in that Stream will be sent to the connected
58/// websocket client. This is useful for sending messages that are
59/// triggered by other events in the application, using whatever channel
60/// mechanism is appropriate for your application. The websocket
61/// connection will be closed if the stream ends, yielding None.
62///
63/// If you do not need to use streams, set `OutboundStream =
64/// futures_lite::stream::Pending<Message>` or a similar stream
65/// implementation that never yields. If associated type defaults were
66/// stable, we would use that.
67///
68/// ## Receiving client-sent messages
69///
70/// Implement [`WebSocketHandler::inbound`] to receive client-sent
71/// messages. Currently inbound messages are not represented as a stream,
72/// but this may change in the future.
73///
74/// ## Holding data inside of the implementing type
75///
76/// As this is a trait you implement for your own type, you can hold
77/// additional data or structs inside of your struct. There will be
78/// exactly one of these structs shared throughout the application, so
79/// async concurrency types can be used to mutate shared data.
80///
81/// This example holds a shared BroadcastChannel that is cloned for each
82/// OutboundStream. Any message that a connected clients sends is
83/// broadcast to every other connected client.
84///
85/// Importantly, this means that the dispatch and fanout of messages is
86/// managed entirely by your implementation. For an opinionated layer on
87/// top of this, see the trillium-channels crate.
88///
89/// ```
90/// use broadcaster::BroadcastChannel;
91/// use trillium_websockets::{Message, WebSocket, WebSocketConn, WebSocketHandler};
92///
93/// struct EchoServer {
94///     channel: BroadcastChannel<Message>,
95/// }
96/// impl EchoServer {
97///     fn new() -> Self {
98///         Self {
99///             channel: BroadcastChannel::new(),
100///         }
101///     }
102/// }
103///
104/// impl WebSocketHandler for EchoServer {
105///     type OutboundStream = BroadcastChannel<Message>;
106///
107///     async fn connect(
108///         &self,
109///         conn: WebSocketConn,
110///     ) -> Option<(WebSocketConn, Self::OutboundStream)> {
111///         Some((conn, self.channel.clone()))
112///     }
113///
114///     async fn inbound(&self, message: Message, _conn: &mut WebSocketConn) {
115///         if let Message::Text(input) = message {
116///             let message = Message::text(format!("received message: {}", &input));
117///             trillium::log_error!(self.channel.send(&message).await);
118///         }
119///     }
120/// }
121///
122/// // fn main() {
123/// //    trillium_smol::run(WebSocket::new(EchoServer::new()));
124/// // }
125/// ```
126#[allow(unused_variables)]
127pub trait WebSocketHandler: Send + Sync + Sized + 'static {
128    /// A [`Stream`] type that represents [`Message`]s to be sent to this
129    /// client. It is built in your implementation code, in
130    /// [`WebSocketHandler::connect`]. Use `Pending<Message>` or another
131    /// stream that never returns if you do not need to use this aspect of
132    /// the trait.
133    type OutboundStream: Stream<Item = Message> + Send + Sync + 'static;
134
135    /// This interface is the only mandatory function in
136    /// WebSocketHandler. It receives an owned WebSocketConn and
137    /// optionally returns it along with an `OutboundStream`
138    /// type.
139    fn connect(
140        &self,
141        conn: WebSocketConn,
142    ) -> impl Future<Output = Option<(WebSocketConn, Self::OutboundStream)>> + Send;
143
144    /// This interface function is called once with every message received
145    /// from a connected websocket client.
146    fn inbound(
147        &self,
148        message: Message,
149        conn: &mut WebSocketConn,
150    ) -> impl Future<Output = ()> + Send {
151        async {}
152    }
153
154    /// This interface function is called once with every outbound message
155    /// in the OutboundStream. You likely do not need to implement this,
156    /// but if you do, you must call `conn.send(message).await` or the
157    /// message will not be sent.
158    fn send(
159        &self,
160        message: Message,
161        conn: &mut WebSocketConn,
162    ) -> impl Future<Output = Result<(), Error>> + Send {
163        async { conn.send(message).await }
164    }
165
166    /// This interface function is called with the websocket conn and, in
167    /// the case of a clean disconnect, the [`CloseFrame`] if one is sent
168    /// available.
169    fn disconnect(
170        &self,
171        conn: &mut WebSocketConn,
172        close_frame: Option<CloseFrame>,
173    ) -> impl Future<Output = ()> + Send {
174        async {}
175    }
176}
177
178impl<H, Fut> WebSocketHandler for H
179where
180    H: Fn(WebSocketConn) -> Fut + Send + Sync + 'static,
181    Fut: Future<Output = ()> + Send + 'static,
182{
183    type OutboundStream = Pending<Message>;
184
185    async fn connect(&self, wsc: WebSocketConn) -> Option<(WebSocketConn, Self::OutboundStream)> {
186        self(wsc).await;
187
188        None
189    }
190}