workflow_websocket/client/
native.rs1use super::{
2 Ack, ConnectOptions, ConnectResult, ConnectStrategy, Handshake, Resolver, WebSocketConfig,
3 error::Error, message::Message, result::Result,
4};
5use futures::{
6 FutureExt, select_biased,
7 stream::{SplitSink, SplitStream},
8};
9use futures_util::{SinkExt, StreamExt};
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::sync::{Arc, Mutex};
12#[allow(unused_imports)]
13use std::time::Instant;
14use tokio::net::TcpStream;
15use tokio::time::timeout;
16use tokio_tungstenite::{
17 MaybeTlsStream, WebSocketStream, connect_async_with_config,
18 tungstenite::protocol::Message as TsMessage,
19};
20use tungstenite::protocol::WebSocketConfig as TsWebSocketConfig;
21pub use workflow_core as core;
22use workflow_core::channel::*;
23pub use workflow_log::*;
24
25impl From<Message> for tungstenite::Message {
26 fn from(message: Message) -> Self {
27 match message {
28 Message::Text(text) => text.into(),
29 Message::Binary(data) => data.into(),
30 _ => {
31 panic!("From<Message> for tungstenite::Message - invalid message type: {message:?}",)
32 }
33 }
34 }
35}
36
37impl From<tungstenite::Message> for Message {
38 fn from(message: tungstenite::Message) -> Self {
39 match message {
40 TsMessage::Text(text) => Message::Text(text.to_string()),
41 TsMessage::Binary(data) => Message::Binary(data.to_vec()),
42 TsMessage::Close(_) => Message::Close,
43 _ => panic!(
44 "TryFrom<tungstenite::Message> for Message - invalid message type: {message:?}",
45 ),
46 }
47 }
48}
49
50impl From<WebSocketConfig> for TsWebSocketConfig {
51 fn from(config: WebSocketConfig) -> Self {
52 TsWebSocketConfig::default()
53 .write_buffer_size(config.write_buffer_size)
54 .max_write_buffer_size(config.max_write_buffer_size)
55 .max_message_size(config.max_message_size)
56 .max_frame_size(config.max_frame_size)
57 .accept_unmasked_frames(config.accept_unmasked_frames)
58 }
59}
60
61#[derive(Default)]
62struct Settings {
63 default_url: Option<String>,
64 current_url: Option<String>,
65}
66
67pub struct WebSocketInterface {
68 settings: Mutex<Settings>,
69 config: Mutex<WebSocketConfig>,
70 reconnect: AtomicBool,
71 is_connected: AtomicBool,
72 receiver_channel: Channel<Message>,
73 sender_channel: Channel<(Message, Ack)>,
74 shutdown: DuplexChannel<()>,
75}
76
77impl WebSocketInterface {
78 pub fn new(
79 url: Option<&str>,
80 config: Option<WebSocketConfig>,
81 sender_channel: Channel<(Message, Ack)>,
82 receiver_channel: Channel<Message>,
83 ) -> Result<WebSocketInterface> {
84 let settings = Settings {
85 default_url: url.map(String::from),
86 ..Default::default()
87 };
88
89 let iface = WebSocketInterface {
90 settings: Mutex::new(settings),
91 config: Mutex::new(config.unwrap_or_default()),
92 receiver_channel,
93 sender_channel,
94 reconnect: AtomicBool::new(true),
95 is_connected: AtomicBool::new(false),
96 shutdown: DuplexChannel::unbounded(),
97 };
98
99 Ok(iface)
100 }
101
102 pub fn default_url(self: &Arc<Self>) -> Option<String> {
103 self.settings.lock().unwrap().default_url.clone()
104 }
105
106 pub fn current_url(self: &Arc<Self>) -> Option<String> {
107 self.settings.lock().unwrap().current_url.clone()
108 }
109
110 pub fn set_default_url(self: &Arc<Self>, url: &str) {
111 self.settings
112 .lock()
113 .unwrap()
114 .default_url
115 .replace(url.to_string());
116 }
117
118 pub fn set_current_url(self: &Arc<Self>, url: &str) {
119 self.settings
120 .lock()
121 .unwrap()
122 .current_url
123 .replace(url.to_string());
124 }
125
126 pub fn is_connected(self: &Arc<Self>) -> bool {
127 self.is_connected.load(Ordering::SeqCst)
128 }
129
130 fn resolver(&self) -> Option<Arc<dyn Resolver>> {
131 self.config.lock().unwrap().resolver.clone()
132 }
133
134 fn handshake(&self) -> Option<Arc<dyn Handshake>> {
135 self.config.lock().unwrap().handshake.clone()
136 }
137
138 pub fn configure(&self, config: WebSocketConfig) {
139 *self.config.lock().unwrap() = config;
140 }
141
142 fn config(&self) -> WebSocketConfig {
143 self.config.lock().unwrap().clone()
144 }
145
146 async fn resolve_url(self: &Arc<Self>, options: &ConnectOptions) -> Result<String> {
147 let url = if let Some(url) = options.url.as_ref().or(self.default_url().as_ref()) {
148 url.clone()
149 } else {
150 match self.resolver() {
151 Some(resolver) => resolver.resolve_url().await?,
152 _ => {
153 return Err(Error::MissingUrl);
154 }
155 }
156 };
157 self.set_current_url(&url);
158 Ok(url)
159 }
160
161 pub async fn connect(self: &Arc<Self>, options: ConnectOptions) -> ConnectResult<Error> {
162 let this = self.clone();
163
164 if self.is_connected.load(Ordering::SeqCst) {
165 return Err(Error::AlreadyConnected);
166 }
167
168 let (connect_trigger, connect_listener) = oneshot::<Result<()>>();
169 let mut connect_trigger = Some(connect_trigger);
170
171 this.reconnect.store(true, Ordering::SeqCst);
172
173 let block_async_connect = options.block_async_connect;
174 let ts_websocket_config = Some(self.config().into());
175
176 core::task::spawn(async move {
177 'outer: loop {
178 match this.resolve_url(&options).await {
179 Ok(url) => {
180 let connect_future =
181 connect_async_with_config(&url, ts_websocket_config, false);
182 let timeout_future = timeout(options.connect_timeout(), connect_future);
183
184 match timeout_future.await {
185 Ok(Ok(stream)) => {
187 this.is_connected.store(true, Ordering::SeqCst);
190 let (mut ws_stream, _) = stream;
191
192 if connect_trigger.is_some() {
193 connect_trigger.take().unwrap().try_send(Ok(())).ok();
194 }
195
196 if let Err(err) = this.dispatcher(&mut ws_stream, &options).await {
197 log_trace!("WebSocket dispatcher error: {}", err);
198 }
199
200 this.is_connected.store(false, Ordering::SeqCst);
201 }
202 Ok(Err(e)) => {
204 log_trace!("WebSocket failed to connect to {}: {}", url, e);
205 if matches!(options.strategy, ConnectStrategy::Fallback) {
206 if options.block_async_connect && connect_trigger.is_some() {
207 connect_trigger
208 .take()
209 .unwrap()
210 .try_send(Err(e.into()))
211 .ok();
212 }
213 break;
214 }
215 workflow_core::task::sleep(options.retry_interval()).await;
216 }
217 Err(_) => {
219 log_trace!(
220 "WebSocket connection timeout while connecting to {}",
221 url
222 );
223 if matches!(options.strategy, ConnectStrategy::Fallback) {
224 if options.block_async_connect && connect_trigger.is_some() {
225 connect_trigger
226 .take()
227 .unwrap()
228 .try_send(Err(Error::ConnectionTimeout))
229 .ok();
230 }
231 break;
232 }
233 workflow_core::task::sleep(options.retry_interval()).await;
234 }
235 };
236
237 if !this.reconnect.load(Ordering::SeqCst) {
238 break 'outer;
239 };
240 }
241 Err(err) => {
242 log_trace!("WebSocket failed to get session URL: {}", err);
243 if !this.reconnect.load(Ordering::SeqCst) {
244 break 'outer;
245 } else {
246 workflow_core::task::sleep(options.retry_interval()).await;
247 }
248 }
249 }
250 }
251 });
252
253 match block_async_connect {
254 true => match connect_listener.recv().await? {
255 Ok(_) => Ok(None),
256 Err(e) => Err(e),
257 },
258 false => Ok(Some(connect_listener)),
259 }
260 }
261
262 async fn handshake_impl(
263 self: &Arc<Self>,
264 ws_sender: &mut SplitSink<&mut WebSocketStream<MaybeTlsStream<TcpStream>>, TsMessage>,
265 ws_receiver: &mut SplitStream<&mut WebSocketStream<MaybeTlsStream<TcpStream>>>,
266 ) -> Result<()> {
267 if let Some(handshake) = self.handshake() {
268 let (sender_tx, sender_rx) = unbounded();
269 let (receiver_tx, receiver_rx) = unbounded();
270 let (accept_tx, accept_rx) = oneshot();
271
272 core::task::spawn(async move {
273 accept_tx
274 .send(handshake.handshake(&sender_tx, &receiver_rx).await)
275 .await
276 .unwrap_or_else(|err| {
277 log_trace!("WebSocket handshake unable to send completion: `{}`", err)
278 });
279 });
280
281 loop {
282 select_biased! {
283 result = accept_rx.recv().fuse() => {
284 return result?;
285 },
286 msg = sender_rx.recv().fuse() => {
287 if let Ok(msg) = msg {
288 ws_sender.send(msg.into()).await?;
289 }
290 },
291 msg = ws_receiver.next().fuse() => {
292 if let Some(Ok(msg)) = msg {
293 receiver_tx.send(msg.into()).await?;
294 } else {
295 return Err(Error::NegotiationFailure);
296 }
297 }
298 }
299 }
300 }
301
302 Ok(())
303 }
304
305 async fn dispatcher(
306 self: &Arc<Self>,
307 ws_stream: &mut WebSocketStream<MaybeTlsStream<TcpStream>>,
308 _options: &ConnectOptions,
309 ) -> Result<()> {
310 let (mut ws_sender, mut ws_receiver) = ws_stream.split();
311
312 self.handshake_impl(&mut ws_sender, &mut ws_receiver)
313 .await?;
314
315 #[cfg(feature = "delay-reconnect")]
316 let connection_start = Instant::now();
317 #[cfg(feature = "delay-reconnect")]
318 let mut closed_ungracefully = false;
319
320 self.receiver_channel.send(Message::Open).await?;
321
322 loop {
323 select_biased! {
324 dispatch = self.sender_channel.recv().fuse() => {
325 if let Ok((msg,ack)) = dispatch {
326 if let Some(ack_sender) = ack {
327 let result = ws_sender.send(msg.into()).await
328 .map(Arc::new)
329 .map_err(|err|Arc::new(err.into()));
330 ack_sender.send(result).await?;
331 } else {
332 ws_sender.send(msg.into()).await?;
333 }
334 }
335 }
336 msg = ws_receiver.next().fuse() => {
337 match msg {
338 Some(Ok(msg)) => {
339 match msg {
340 TsMessage::Binary(_) | TsMessage::Text(_) | TsMessage::Close(_) => {
341 self
342 .receiver_channel
343 .send(msg.into())
344 .await?;
345 }
346 TsMessage::Ping(data) => {
347 ws_sender.send(TsMessage::Pong(data)).await?;
348 },
349 TsMessage::Pong(_) => { },
350 TsMessage::Frame(_frame) => { },
351 }
352 }
353 Some(Err(e)) => {
354 self.receiver_channel.send(Message::Close).await?;
355 log_trace!("WebSocket error: {}", e);
356 #[cfg(feature = "delay-reconnect")] {
357 closed_ungracefully = true;
358 }
359 break;
360 }
361 None => {
362 self.receiver_channel.send(Message::Close).await?;
363 log_trace!("WebSocket connection closed");
364 #[cfg(feature = "delay-reconnect")] {
365 closed_ungracefully = true;
366 }
367 break;
368 }
369 }
370 }
371 _ = self.shutdown.request.receiver.recv().fuse() => {
372 self.receiver_channel.send(Message::Close).await?;
373 self.shutdown.response.sender.send(()).await?;
374 break;
375 }
376 }
377 }
378
379 #[cfg(feature = "delay-reconnect")]
381 if closed_ungracefully && connection_start.elapsed().as_millis() < 1_000 {
382 workflow_core::task::sleep(_options.retry_interval()).await;
383 }
384
385 Ok(())
386 }
387
388 pub async fn close(self: &Arc<Self>) -> Result<()> {
389 if self.is_connected.load(Ordering::SeqCst) {
391 self.shutdown
393 .request
394 .sender
395 .send(())
396 .await
397 .unwrap_or_else(|err| {
398 log_error!("Unable to signal WebSocket dispatcher shutdown: {}", err)
399 });
400 self.shutdown
401 .response
402 .receiver
403 .recv()
404 .await
405 .unwrap_or_else(|err| {
406 log_error!("Unable to receive WebSocket dispatcher shutdown: {}", err)
407 });
408 }
409
410 Ok(())
411 }
412
413 pub async fn disconnect(self: &Arc<Self>) -> Result<()> {
414 self.reconnect.store(false, Ordering::SeqCst);
415 self.close().await?;
416 Ok(())
417 }
418
419 pub fn trigger_abort(self: &Arc<Self>) -> Result<()> {
420 if self.is_connected.load(Ordering::SeqCst) {
421 self.receiver_channel.try_send(Message::Close)?;
422 }
423 Ok(())
424 }
425}