Skip to main content

trillium_websockets/
websocket_connection.rs

1use crate::{
2    Result, Role, WebSocketConfig,
3    driver::{Driver, MessageResult, default_config},
4};
5use futures_lite::{Stream, future};
6use split_compat::DriverSlot;
7use std::{
8    fmt::Debug,
9    net::IpAddr,
10    pin::Pin,
11    sync::Arc,
12    task::{self, Poll},
13};
14use swansong::Swansong;
15use trillium::{Headers, Method, TypeSet, Upgrade};
16use trillium_http::{HttpContext, type_set::entry::Entry};
17use tungstenite::Message;
18
19mod split_compat;
20
21/// A struct that represents an specific websocket connection.
22///
23/// This combines a websocket message stream with the request metadata and state of the
24/// [`trillium::Conn`] it was upgraded from.
25///
26/// The WebSocketConn implements `Stream<Item=Result<Message, Error>>`,
27/// and can be polled with `StreamExt::next`
28pub struct WebSocketConn {
29    request_headers: Headers,
30    path: String,
31    querystring: String,
32    method: Method,
33    state: TypeSet,
34    peer_ip: Option<IpAddr>,
35    context: Arc<HttpContext>,
36    driver: DriverSlot,
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            .finish_non_exhaustive()
50    }
51}
52
53impl WebSocketConn {
54    /// send a [`Message::Text`] variant
55    pub async fn send_string(&mut self, string: String) -> Result<()> {
56        self.send(Message::text(string)).await
57    }
58
59    /// send a [`Message::Binary`] variant
60    pub async fn send_bytes(&mut self, bin: Vec<u8>) -> Result<()> {
61        self.send(Message::binary(bin)).await
62    }
63
64    #[cfg(feature = "json")]
65    /// send a [`Message::Text`] that contains json
66    /// note that json messages are not actually part of the websocket specification
67    pub async fn send_json(&mut self, json: &impl serde::Serialize) -> Result<()> {
68        self.send_string(serde_json::to_string(json)?).await
69    }
70
71    /// Sends a [`Message`] to the client and flushes it to the socket
72    ///
73    /// When sending many messages in quick succession, [`feed`][Self::feed] coalesces them into
74    /// fewer socket writes.
75    pub async fn send(&mut self, message: Message) -> Result<()> {
76        self.feed(message).await?;
77        self.flush().await
78    }
79
80    /// Enqueues a [`Message`] without immediately writing it to the socket
81    ///
82    /// The message is encoded into an internal write buffer. The buffer is written out when it
83    /// fills, when this conn is next polled for an inbound message and none is immediately
84    /// available, or on [`flush`][Self::flush] or [`send`][Self::send]. If you feed a message and
85    /// then await anything other than this conn, call `flush` first.
86    pub async fn feed(&mut self, message: Message) -> Result<()> {
87        future::poll_fn(|cx| self.driver.poll_ready_to_write(cx)).await?;
88        self.driver.write(message)?;
89        Ok(())
90    }
91
92    /// Writes any buffered outbound messages to the socket
93    pub async fn flush(&mut self) -> Result<()> {
94        future::poll_fn(|cx| self.driver.poll_flush(cx))
95            .await
96            .map_err(Into::into)
97    }
98
99    /// Create a `WebSocketConn` from an HTTP upgrade, with optional config and the specified role
100    ///
101    /// You should not typically need to call this; the trillium client and server both provide
102    /// your code with a `WebSocketConn`.
103    #[doc(hidden)]
104    // Async only because published callers await it; make it sync at the next breaking release.
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 driver = Driver::new(
121            transport,
122            buffer,
123            role,
124            config.unwrap_or_else(default_config),
125        );
126        let driver = DriverSlot::Owned(context.swansong().interrupt(driver));
127
128        Self {
129            request_headers,
130            path,
131            querystring,
132            method,
133            state,
134            peer_ip,
135            context,
136            driver,
137        }
138    }
139
140    /// retrieve a clone of the server's [`Swansong`]
141    pub fn swansong(&self) -> Swansong {
142        self.context.swansong().clone()
143    }
144
145    /// close the websocket connection gracefully
146    ///
147    /// If the peer has already sent a close frame, this sends the reply that completes the
148    /// close handshake.
149    pub async fn close(&mut self) -> Result<()> {
150        self.driver.close()?;
151        self.flush().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
222impl AsMut<TypeSet> for WebSocketConn {
223    fn as_mut(&mut self) -> &mut TypeSet {
224        &mut self.state
225    }
226}
227
228impl AsRef<TypeSet> for WebSocketConn {
229    fn as_ref(&self) -> &TypeSet {
230        &self.state
231    }
232}
233
234impl Stream for WebSocketConn {
235    type Item = MessageResult;
236
237    fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
238        self.driver.poll_inbound(cx)
239    }
240}