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