1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3mod codec;
4mod config;
5mod error;
6mod handler;
7#[cfg(feature = "mocking")]
8mod mock_server;
9mod socket_loop;
10mod split;
11
12pub use bytes::Bytes;
13#[cfg(feature = "msgpack")]
14pub use codec::MsgPackCodec;
15pub use codec::{Codec, JsonCodec, RawCodec};
16pub use config::{ConnectOptions, ConnectOptionsBuilder};
17pub use error::Error;
18pub use handler::{ConnectionHandler, HandshakeContext, NoopHandler};
19#[cfg(all(feature = "mocking", feature = "msgpack"))]
20pub use mock_server::msgpack_echo_server;
21#[cfg(feature = "mocking")]
22pub use mock_server::{EchoControlMessage, auth_echo_server, echo_server, get_mock_address};
23pub use split::{ReuniteError, SocketeerRx, SocketeerTx};
24pub use tokio_tungstenite::tungstenite::{self, Message, http};
25
26pub use socket_loop::WebSocketStreamType;
31use socket_loop::{
32 TerminalError, TxChannelPayload, poll_recv_raw, recv_raw, send_close, send_confirmed,
33 socket_loop_split,
34};
35
36use std::pin::Pin;
37use std::sync::{Arc, Mutex, PoisonError};
38use std::task::{Context, Poll};
39
40use futures::{Stream, StreamExt};
41use tokio::sync::mpsc;
42use tokio_tungstenite::connect_async;
43
44#[cfg(feature = "tracing")]
45use tracing::{debug, info, instrument};
46use url::Url;
47
48pub struct Socketeer<C: Codec, Handler = NoopHandler, const CHANNEL_SIZE: usize = 256>
65where
66 Handler: ConnectionHandler<C>,
67{
68 url: Url,
69 options: ConnectOptions,
70 codec: C,
71 handler: Handler,
72 receiver: mpsc::Receiver<Message>,
73 sender: mpsc::Sender<TxChannelPayload>,
74 socket_handle: tokio::task::JoinHandle<()>,
75 terminal_error: TerminalError,
76}
77
78impl<C: Codec, Handler, const CHANNEL_SIZE: usize> std::fmt::Debug
79 for Socketeer<C, Handler, CHANNEL_SIZE>
80where
81 Handler: ConnectionHandler<C>,
82{
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.debug_struct("Socketeer")
85 .field("url", &self.url)
86 .finish_non_exhaustive()
87 }
88}
89
90impl<C: Codec + Unpin, Handler, const CHANNEL_SIZE: usize> Stream
91 for Socketeer<C, Handler, CHANNEL_SIZE>
92where
93 Handler: ConnectionHandler<C> + Unpin,
94{
95 type Item = Result<C::Rx, Error>;
96
97 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
98 let this = self.get_mut();
99 poll_recv_raw(&mut this.receiver, &this.terminal_error, cx)
100 .map(|frame| frame.map(|result| result.and_then(|message| this.codec.decode(&message))))
101 }
102}
103
104impl<C, const CHANNEL_SIZE: usize> Socketeer<C, NoopHandler, CHANNEL_SIZE>
105where
106 C: Codec + Default,
107{
108 #[cfg_attr(feature = "tracing", instrument)]
114 pub async fn connect(url: &str) -> Result<Self, Error> {
115 Self::connect_with(url, ConnectOptions::default()).await
116 }
117
118 #[cfg_attr(feature = "tracing", instrument(skip(options)))]
123 pub async fn connect_with(url: &str, options: ConnectOptions) -> Result<Self, Error> {
124 Socketeer::connect_with_codec(url, options, C::default(), NoopHandler).await
125 }
126}
127
128impl<C, Handler, const CHANNEL_SIZE: usize> Socketeer<C, Handler, CHANNEL_SIZE>
129where
130 C: Codec,
131 Handler: ConnectionHandler<C>,
132{
133 #[cfg_attr(feature = "tracing", instrument(skip(options, codec, handler)))]
143 pub async fn connect_with_codec(
144 url: &str,
145 options: ConnectOptions,
146 codec: C,
147 mut handler: Handler,
148 ) -> Result<Self, Error> {
149 let url = Url::parse(url).map_err(|source| Error::UrlParse {
150 url: url.to_string(),
151 source,
152 })?;
153
154 let request = options.build_request(&url)?;
155 #[allow(unused_variables)]
156 let (socket, response) = connect_async(request).await?;
157 #[cfg(feature = "tracing")]
158 debug!("Connection Successful, connection info: \n{:#?}", response);
159
160 let (mut sink, mut stream) = socket.split();
161 {
162 let mut ctx = HandshakeContext::new(&mut sink, &mut stream, &codec);
163 handler.on_connected(&mut ctx).await?;
164 }
165
166 let keepalive_interval = options.keepalive_interval;
167 let keepalive_message = options.custom_keepalive_message.clone();
168
169 let (tx_tx, tx_rx) = mpsc::channel::<TxChannelPayload>(CHANNEL_SIZE);
170 let (rx_tx, rx_rx) = mpsc::channel::<Message>(CHANNEL_SIZE);
171
172 let terminal_error: TerminalError = Arc::new(Mutex::new(None));
173 let loop_terminal_error = Arc::clone(&terminal_error);
174 let socket_handle = tokio::spawn(async move {
175 socket_loop_split(
176 tx_rx,
177 rx_tx,
178 sink,
179 stream,
180 keepalive_interval,
181 keepalive_message,
182 loop_terminal_error,
183 )
184 .await;
185 });
186 Ok(Socketeer {
187 url,
188 options,
189 codec,
190 handler,
191 receiver: rx_rx,
192 sender: tx_tx,
193 socket_handle,
194 terminal_error,
195 })
196 }
197
198 #[allow(clippy::too_many_arguments)]
201 pub(crate) fn from_parts(
202 url: Url,
203 options: ConnectOptions,
204 codec: C,
205 handler: Handler,
206 receiver: mpsc::Receiver<Message>,
207 sender: mpsc::Sender<TxChannelPayload>,
208 socket_handle: tokio::task::JoinHandle<()>,
209 terminal_error: TerminalError,
210 ) -> Self {
211 Self {
212 url,
213 options,
214 codec,
215 handler,
216 receiver,
217 sender,
218 socket_handle,
219 terminal_error,
220 }
221 }
222
223 #[cfg_attr(feature = "tracing", instrument(skip(self)))]
231 pub async fn next_message(&mut self) -> Result<C::Rx, Error> {
232 let message = recv_raw(&mut self.receiver, &self.terminal_error).await?;
233 self.codec.decode(&message)
234 }
235
236 #[cfg_attr(feature = "tracing", instrument(skip(self, message)))]
244 pub async fn send(&self, message: C::Tx) -> Result<(), Error> {
245 let encoded = self.codec.encode(&message)?;
246 self.send_raw(encoded).await
247 }
248
249 pub async fn next_raw_message(&mut self) -> Result<Message, Error> {
259 recv_raw(&mut self.receiver, &self.terminal_error).await
260 }
261
262 pub async fn send_raw(&self, message: Message) -> Result<(), Error> {
270 send_confirmed(&self.sender, &self.terminal_error, message).await
271 }
272
273 pub async fn reconnect(self) -> Result<Self, Error> {
284 let url = self.url.as_str().to_owned();
285 let options = self.options.clone();
286 let codec = self.codec;
287 let mut handler = self.handler;
288 #[cfg(feature = "tracing")]
289 info!("Reconnecting");
290 handler.on_disconnected().await;
291 match send_close(&self.sender).await {
293 Ok(()) => (),
294 #[allow(unused_variables)]
295 Err(e) => {
296 #[cfg(feature = "tracing")]
297 debug!("Socket loop already stopped during reconnect: {}", e);
298 }
299 }
300 Self::connect_with_codec(&url, options, codec, handler).await
301 }
302
303 #[cfg_attr(feature = "tracing", instrument(skip(self)))]
309 pub async fn close_connection(self) -> Result<(), Error> {
310 #[cfg(feature = "tracing")]
311 debug!("Closing Connection");
312 let close_sent = send_close(&self.sender).await;
313 if self.socket_handle.await.is_err() {
315 unreachable!("Socket loop does not panic, and is not cancelled");
316 }
317 let terminal = self
322 .terminal_error
323 .lock()
324 .unwrap_or_else(PoisonError::into_inner)
325 .take();
326 match terminal {
327 Some(error) => Err(error),
328 None => close_sent,
329 }
330 }
331}
332
333impl<C, Handler, const CHANNEL_SIZE: usize> Socketeer<C, Handler, CHANNEL_SIZE>
334where
335 C: Codec + Clone,
336 Handler: ConnectionHandler<C>,
337{
338 #[must_use]
345 pub fn split(self) -> (SocketeerTx<C>, SocketeerRx<C, Handler, CHANNEL_SIZE>) {
346 let tx = SocketeerTx {
347 sender: self.sender,
348 codec: self.codec.clone(),
349 terminal_error: Arc::clone(&self.terminal_error),
350 };
351 let rx = SocketeerRx {
352 receiver: self.receiver,
353 codec: self.codec,
354 terminal_error: self.terminal_error,
355 socket_handle: self.socket_handle,
356 url: self.url,
357 options: self.options,
358 handler: self.handler,
359 };
360 (tx, rx)
361 }
362}