Skip to main content

trillium_websockets/
websocket_connection.rs

1use crate::{Result, Role, WebSocketConfig};
2use async_tungstenite::{
3    WebSocketReceiver, WebSocketSender, WebSocketStream,
4    tungstenite::{self, Message},
5};
6use futures_lite::{Stream, StreamExt, future};
7use futures_sink::Sink;
8use std::{
9    fmt::Debug,
10    net::IpAddr,
11    pin::Pin,
12    sync::Arc,
13    task::{self, Poll},
14};
15use swansong::{Interrupt, Swansong};
16use trillium::{Headers, Method, Transport, TypeSet, Upgrade};
17use trillium_http::{HttpContext, type_set::entry::Entry};
18
19/// A struct that represents an specific websocket connection.
20///
21/// This can be thought of as a combination of a [`async_tungstenite::WebSocketStream`] and a
22/// [`trillium::Conn`], as it contains a combination of their fields and
23/// associated functions.
24///
25/// The WebSocketConn implements `Stream<Item=Result<Message, Error>>`,
26/// and can be polled with `StreamExt::next`
27pub struct WebSocketConn {
28    request_headers: Headers,
29    path: String,
30    querystring: String,
31    method: Method,
32    state: TypeSet,
33    peer_ip: Option<IpAddr>,
34    context: Arc<HttpContext>,
35    sink: WebSocketSender<Box<dyn Transport>>,
36    stream: Option<WStream>,
37}
38
39impl Debug for WebSocketConn {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        f.debug_struct("WebSocketConn")
42            .field("request_headers", &self.request_headers)
43            .field("path", &self.path)
44            .field("querystring", &self.querystring)
45            .field("method", &self.method)
46            .field("state", &self.state)
47            .field("peer_ip", &self.peer_ip)
48            .field("context", &self.context)
49            .field("stream", &self.stream)
50            .finish_non_exhaustive()
51    }
52}
53
54impl WebSocketConn {
55    /// send a [`Message::Text`] variant
56    pub async fn send_string(&mut self, string: String) -> Result<()> {
57        self.send(Message::text(string)).await
58    }
59
60    /// send a [`Message::Binary`] variant
61    pub async fn send_bytes(&mut self, bin: Vec<u8>) -> Result<()> {
62        self.send(Message::binary(bin)).await
63    }
64
65    #[cfg(feature = "json")]
66    /// send a [`Message::Text`] that contains json
67    /// note that json messages are not actually part of the websocket specification
68    pub async fn send_json(&mut self, json: &impl serde::Serialize) -> Result<()> {
69        self.send_string(serde_json::to_string(json)?).await
70    }
71
72    /// Sends a [`Message`] to the client and flushes it to the socket
73    ///
74    /// When sending many messages in quick succession, [`feed`][Self::feed] coalesces them into
75    /// fewer socket writes.
76    pub async fn send(&mut self, message: Message) -> Result<()> {
77        self.feed(message).await?;
78        self.flush().await
79    }
80
81    /// Enqueues a [`Message`] without immediately writing it to the socket
82    ///
83    /// The message is encoded into an internal write buffer. The buffer is written out when it
84    /// fills, when this conn is next polled for an inbound message and none is immediately
85    /// available, or on [`flush`][Self::flush] or [`send`][Self::send]. If you feed a message and
86    /// then await anything other than this conn, call `flush` first.
87    pub async fn feed(&mut self, message: Message) -> Result<()> {
88        future::poll_fn(|cx| Pin::new(&mut self.sink).poll_ready(cx)).await?;
89        Pin::new(&mut self.sink).start_send(message)?;
90        Ok(())
91    }
92
93    /// Writes any buffered outbound messages to the socket
94    pub async fn flush(&mut self) -> Result<()> {
95        future::poll_fn(|cx| Pin::new(&mut self.sink).poll_flush(cx))
96            .await
97            .map_err(Into::into)
98    }
99
100    /// Create a `WebSocketConn` from an HTTP upgrade, with optional config and the specified role
101    ///
102    /// You should not typically need to call this; the trillium client and server both provide
103    /// your code with a `WebSocketConn`.
104    #[doc(hidden)]
105    pub async fn new(
106        upgrade: impl Into<Upgrade>,
107        config: Option<WebSocketConfig>,
108        role: Role,
109    ) -> Self {
110        let mut upgrade = upgrade.into();
111        let request_headers = upgrade.take_request_headers();
112        let path = upgrade.path().to_string();
113        let querystring = upgrade.querystring().to_string();
114        let method = upgrade.method();
115        let state = upgrade.take_state();
116        let context = upgrade.context().clone();
117        let peer_ip = upgrade.peer_ip();
118        let (buffer, transport) = upgrade.into_transport();
119
120        let wss = if buffer.is_empty() {
121            WebSocketStream::from_raw_socket(transport, role, config).await
122        } else {
123            WebSocketStream::from_partially_read(transport, buffer, role, config).await
124        };
125
126        let (sink, stream) = wss.split();
127        let stream = Some(WStream {
128            stream: context.swansong().interrupt(stream),
129        });
130
131        Self {
132            request_headers,
133            path,
134            querystring,
135            method,
136            state,
137            peer_ip,
138            sink,
139            stream,
140            context,
141        }
142    }
143
144    /// retrieve a clone of the server's [`Swansong`]
145    pub fn swansong(&self) -> Swansong {
146        self.context.swansong().clone()
147    }
148
149    /// close the websocket connection gracefully
150    pub async fn close(&mut self) -> Result<()> {
151        self.send(Message::Close(None)).await
152    }
153
154    /// retrieve the request headers for this conn
155    pub fn headers(&self) -> &Headers {
156        &self.request_headers
157    }
158
159    /// retrieves the peer ip for this conn, if available
160    pub fn peer_ip(&self) -> Option<IpAddr> {
161        self.peer_ip
162    }
163
164    /// Sets the peer ip for this conn
165    pub fn set_peer_ip(&mut self, peer_ip: Option<IpAddr>) -> &mut Self {
166        self.peer_ip = peer_ip;
167        self
168    }
169
170    /// retrieves the path part of the request url, up to and excluding
171    /// any query component
172    pub fn path(&self) -> &str {
173        &self.path
174    }
175
176    /// Retrieves the query component of the path, excluding `?`. Returns
177    /// an empty string if there is no query component.
178    pub fn querystring(&self) -> &str {
179        &self.querystring
180    }
181
182    /// retrieve the request method for this conn
183    pub fn method(&self) -> Method {
184        self.method
185    }
186
187    /// retrieve state from the state set that has been accumulated by
188    /// trillium handlers run on the [`trillium::Conn`] before it
189    /// became a websocket. see [`trillium::Conn::state`] for more
190    /// information
191    pub fn state<T: Send + Sync + 'static>(&self) -> Option<&T> {
192        self.state.get()
193    }
194
195    /// retrieve a mutable borrow of the state from the state set
196    pub fn state_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
197        self.state.get_mut()
198    }
199
200    /// inserts new state
201    ///
202    /// returns the previously set state of the same type, if any existed
203    pub fn insert_state<T: Send + Sync + 'static>(&mut self, state: T) -> Option<T> {
204        self.state.insert(state)
205    }
206
207    /// Returns an [`Entry`] for the state typeset that can be used with functions like
208    /// [`Entry::or_insert`], [`Entry::or_insert_with`], [`Entry::and_modify`], and others.
209    pub fn state_entry<T: Send + Sync + 'static>(&mut self) -> Entry<'_, T> {
210        self.state.entry()
211    }
212
213    /// take some type T out of the state set that has been
214    /// accumulated by trillium handlers run on the [`trillium::Conn`]
215    /// before it became a websocket. see [`trillium::Conn::take_state`]
216    /// for more information
217    pub fn take_state<T: Send + Sync + 'static>(&mut self) -> Option<T> {
218        self.state.take()
219    }
220
221    pub(crate) fn poll_flush_sink(
222        &mut self,
223        cx: &mut task::Context<'_>,
224    ) -> Poll<std::result::Result<(), tungstenite::Error>> {
225        Pin::new(&mut self.sink).poll_flush(cx)
226    }
227
228    /// take the inbound Message stream from this conn
229    pub fn take_inbound_stream(&mut self) -> Option<impl Stream<Item = MessageResult> + use<>> {
230        self.stream.take()
231    }
232
233    /// borrow the inbound Message stream from this conn
234    pub fn inbound_stream(&mut self) -> Option<impl Stream<Item = MessageResult> + '_> {
235        self.stream.as_mut()
236    }
237}
238
239type MessageResult = std::result::Result<Message, tungstenite::Error>;
240
241pub struct WStream {
242    stream: Interrupt<WebSocketReceiver<Box<dyn Transport>>>,
243}
244
245impl Debug for WStream {
246    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247        f.debug_struct("WStream").finish_non_exhaustive()
248    }
249}
250
251impl Stream for WStream {
252    type Item = MessageResult;
253
254    fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
255        self.stream.poll_next(cx)
256    }
257}
258
259impl AsMut<TypeSet> for WebSocketConn {
260    fn as_mut(&mut self) -> &mut TypeSet {
261        &mut self.state
262    }
263}
264
265impl AsRef<TypeSet> for WebSocketConn {
266    fn as_ref(&self) -> &TypeSet {
267        &self.state
268    }
269}
270
271impl Stream for WebSocketConn {
272    type Item = MessageResult;
273
274    fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
275        let this = &mut *self;
276        let poll = match this.stream.as_mut() {
277            Some(stream) => Pin::new(stream).poll_next(cx),
278            None => Poll::Ready(None),
279        };
280
281        // About to yield to the caller with nothing to process — write out anything `send`
282        // buffered. Errors are deliberately dropped here; they resurface on the next send or
283        // flush, or as stream termination.
284        if !matches!(poll, Poll::Ready(Some(_)))
285            && let Poll::Ready(Err(e)) = this.poll_flush_sink(cx)
286        {
287            log::debug!("websocket flush error: {e}");
288        }
289
290        poll
291    }
292}