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}