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}