Skip to main content

flare_core/client/transports/
websocket.rs

1//! WebSocket 客户端实现
2//!
3//! 只处理协议层,连接状态管理、心跳、消息路由等功能委托给 ClientCore
4
5use crate::client::config::ClientConfig;
6use crate::client::transports::common::{ClientConnectionHelper, ClientMessageObserver};
7use crate::client::transports::{Client, ClientCore};
8#[cfg(not(target_arch = "wasm32"))]
9use crate::common::cert::create_client_config_with_tls;
10use crate::common::error::{FlareError, Result};
11use crate::common::generate_id;
12use crate::common::platform::{sleep, timeout};
13use crate::common::protocol::Frame;
14use crate::transport::connection::Connection;
15use crate::transport::events::{ArcObserver, ConnectionEvent};
16#[cfg(not(target_arch = "wasm32"))]
17use crate::transport::websocket::WebSocketTransport;
18#[cfg(target_arch = "wasm32")]
19use crate::transport::websocket_wasm::WebSocketTransport;
20use async_trait::async_trait;
21use std::sync::Arc;
22use tokio::sync::Mutex;
23#[cfg(not(target_arch = "wasm32"))]
24use tokio_tungstenite::{Connector, connect_async_tls_with_config};
25
26/// WebSocket 客户端
27///
28/// 只处理协议层,其他功能委托给 ClientCore
29pub struct WebSocketClient {
30    config: ClientConfig,
31    connection: Option<Arc<Mutex<Box<dyn Connection>>>>,
32    connection_id: String,
33    core: ClientCore,
34    reconnect_attempts: u32,
35}
36
37impl WebSocketClient {
38    /// 创建新的 WebSocket 客户端
39    pub fn new(config: ClientConfig) -> Self {
40        let connection_id = config.connection_id.clone().unwrap_or_else(generate_id);
41        let core = ClientCore::new(&config);
42
43        Self {
44            config,
45            connection: None,
46            connection_id,
47            core,
48            reconnect_attempts: 0,
49        }
50    }
51
52    /// 创建并连接
53    pub async fn connect_with_config(config: ClientConfig) -> Result<Self> {
54        let mut client = Self::new(config);
55        client.connect().await?;
56        Ok(client)
57    }
58
59    /// 使用 ClientCore 创建(用于 HybridClient)
60    pub fn with_core(config: ClientConfig, core: ClientCore) -> Self {
61        let connection_id = config.connection_id.clone().unwrap_or_else(generate_id);
62        Self {
63            config,
64            connection: None,
65            connection_id,
66            core,
67            reconnect_attempts: 0,
68        }
69    }
70
71    /// 仅建立网络连接(不发送 CONNECT 消息)
72    ///
73    /// 用于协议竞速:先建立网络连接,选择最快协议,然后再发送 CONNECT
74    pub async fn establish_network_connection(
75        &mut self,
76    ) -> Result<Arc<Mutex<Box<dyn Connection>>>> {
77        let connection = self.establish_websocket_connection().await?;
78        let connection_arc = Arc::new(Mutex::new(connection));
79        // 保存连接,以便后续 connect() 时使用
80        self.connection = Some(Arc::clone(&connection_arc));
81        Ok(connection_arc)
82    }
83
84    /// 内部连接实现
85    async fn internal_connect(&mut self) -> Result<()> {
86        // 建立 WebSocket 连接
87        let connection_arc = self.establish_network_connection().await?;
88
89        // 设置连接和观察者(会发送 CONNECT 消息)
90        self.setup_connection_with_observer(connection_arc.clone())
91            .await?;
92
93        // 心跳在 CONNECT_ACK 协商完成后由 ClientCore 启动
94
95        // 通知连接成功
96        self.core
97            .handle_connection_event(&ConnectionEvent::Connected);
98        self.reconnect_attempts = 0;
99
100        Ok(())
101    }
102
103    /// 建立 WebSocket 连接
104    async fn establish_websocket_connection(&self) -> Result<Box<dyn Connection>> {
105        let url_str = self
106            .config
107            .get_protocol_url(&crate::common::config_types::TransportProtocol::WebSocket);
108
109        #[cfg(not(target_arch = "wasm32"))]
110        {
111            // 入站消息上限 10MB,防超大帧 OOM;与 TCP/QUIC MAX_FRAME_LENGTH 及服务端
112            // max_message_size 对齐(tungstenite 默认 64MB 过大,也削弱 gzip 解压炸弹面)。
113            let ws_config = tokio_tungstenite::tungstenite::protocol::WebSocketConfig::default()
114                .max_message_size(Some(10 * 1024 * 1024))
115                .max_frame_size(Some(10 * 1024 * 1024));
116            let tls_connector = self.tls_connector(&url_str)?;
117            let ws_stream_result = timeout(
118                self.config.connect_timeout,
119                connect_async_tls_with_config(&url_str, Some(ws_config), false, tls_connector),
120            )
121            .await
122            .map_err(|_| FlareError::connection_timeout("Connection timeout".to_string()))?;
123
124            let (ws_stream, _) =
125                ws_stream_result.map_err(|e| FlareError::connection_failed(e.to_string()))?;
126
127            let transport = WebSocketTransport::new(ws_stream);
128            Ok(Box::new(transport))
129        }
130
131        #[cfg(target_arch = "wasm32")]
132        {
133            let transport = timeout(
134                self.config.connect_timeout,
135                WebSocketTransport::connect(&url_str),
136            )
137            .await
138            .map_err(|_| FlareError::connection_timeout("Connection timeout".to_string()))??;
139            Ok(Box::new(transport))
140        }
141    }
142
143    /// 设置连接和观察者
144    async fn setup_connection_with_observer(
145        &mut self,
146        connection: Arc<Mutex<Box<dyn Connection>>>,
147    ) -> Result<()> {
148        // 创建消息观察者(使用 Arc 包装,共享同一个 core 实例)
149        // 注意:ClientCore::clone 现在会共享 client_connection,所以可以安全使用
150        let core_arc = Arc::new(self.core.clone());
151        let message_observer = Arc::new(ClientMessageObserver::new(core_arc));
152
153        // 设置连接并发送 CONNECT 消息
154        ClientConnectionHelper::setup_connection_and_send_connect(
155            Arc::clone(&connection),
156            &mut self.core,
157            message_observer,
158        )
159        .await?;
160
161        self.connection = Some(connection);
162        Ok(())
163    }
164
165    /// 发送 Frame(内部实现)
166    async fn send_frame_internal(&self, frame: &Frame) -> Result<()> {
167        ClientConnectionHelper::send_frame_internal(&self.core, self.connection.as_ref(), frame)
168            .await
169    }
170
171    /// 尝试重连
172    async fn try_reconnect(&mut self) -> Result<()> {
173        // 检查重连次数限制
174        if let Some(max) = self.config.max_reconnect_attempts
175            && self.reconnect_attempts >= max
176        {
177            return Err(FlareError::connection_failed(format!(
178                "Max reconnect attempts ({}) exceeded",
179                max
180            )));
181        }
182
183        self.core.state_manager.start_connecting();
184        self.reconnect_attempts += 1;
185
186        // 等待重连间隔
187        sleep(self.config.reconnect_interval).await;
188
189        // 关闭旧连接
190        if let Some(conn) = self.connection.take() {
191            let mut c = conn.lock().await;
192            let _ = c.close().await;
193        }
194
195        // 执行连接
196        self.internal_connect().await
197    }
198
199    /// 获取 ClientCore(用于外部访问)
200    pub fn core(&self) -> &ClientCore {
201        &self.core
202    }
203
204    #[cfg(not(target_arch = "wasm32"))]
205    fn tls_connector(&self, url: &str) -> Result<Option<Connector>> {
206        if url.starts_with("ws://") {
207            return Ok(None);
208        }
209        if !self.config.tls.requires_custom_client_tls() {
210            return Ok(None);
211        }
212        if !url.starts_with("wss://") {
213            return Err(FlareError::connection_failed(format!(
214                "WebSocket URL must start with ws:// or wss://, got: {url}"
215            )));
216        }
217        let tls_config = create_client_config_with_tls(&self.config.tls)
218            .map_err(|error| FlareError::connection_failed(error.to_string()))?;
219        Ok(Some(Connector::Rustls(Arc::new(tls_config))))
220    }
221
222    /// 发送消息并等待服务端响应(按 Frame.message_id 匹配)
223    pub async fn send_frame_and_wait(
224        &mut self,
225        frame: &Frame,
226        timeout_duration: std::time::Duration,
227    ) -> Result<Frame> {
228        if frame.message_id.is_empty() {
229            return Err(FlareError::protocol_error(
230                "message_id is empty".to_string(),
231            ));
232        }
233
234        let rx = self.core.register_pending_response(&frame.message_id).await;
235        self.send_frame(frame).await?;
236
237        match timeout(timeout_duration, rx).await {
238            Ok(Ok(resp)) => Ok(resp),
239            Ok(Err(_)) => {
240                self.core.cancel_pending_response(&frame.message_id).await;
241                Err(FlareError::protocol_error(
242                    "Response channel closed".to_string(),
243                ))
244            }
245            Err(_) => {
246                self.core.cancel_pending_response(&frame.message_id).await;
247                Err(FlareError::protocol_error(format!(
248                    "Response timeout for message_id {}",
249                    frame.message_id
250                )))
251            }
252        }
253    }
254}
255
256#[cfg(target_arch = "wasm32")]
257unsafe impl Send for WebSocketClient {}
258#[cfg(target_arch = "wasm32")]
259unsafe impl Sync for WebSocketClient {}
260
261#[cfg(all(test, not(target_arch = "wasm32")))]
262mod tests {
263    use super::*;
264    use crate::common::config_types::TlsConfig;
265    use std::path::PathBuf;
266
267    #[test]
268    fn plain_ws_ignores_custom_client_tls() {
269        let mut config = ClientConfig::new("ws://127.0.0.1:60051/ws".to_string()).websocket();
270        config.tls = TlsConfig::default().with_ca_cert(PathBuf::from("/tmp/flare-ca.crt"));
271        let client = WebSocketClient::new(config);
272
273        let connector = client
274            .tls_connector("ws://127.0.0.1:60051/ws")
275            .expect("plain ws must not require a TLS connector");
276
277        assert!(connector.is_none());
278    }
279
280    #[test]
281    fn secure_wss_uses_custom_client_tls() {
282        let mut config = ClientConfig::new("wss://127.0.0.1:60051/ws".to_string()).websocket();
283        config.tls = TlsConfig::default()
284            .with_spki_sha256_pin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
285        let client = WebSocketClient::new(config);
286
287        let connector = client
288            .tls_connector("wss://127.0.0.1:60051/ws")
289            .expect("wss should accept custom TLS settings");
290
291        assert!(connector.is_some());
292    }
293}
294
295#[async_trait]
296impl Client for WebSocketClient {
297    async fn connect(&mut self) -> Result<()> {
298        if !self.core.can_connect() {
299            return Err(FlareError::protocol_error(
300                "Cannot connect: state is unavailable".to_string(),
301            ));
302        }
303
304        self.core.state_manager.start_connecting();
305
306        match self.internal_connect().await {
307            Ok(()) => Ok(()),
308            Err(e) => {
309                self.core.state_manager.set_failed();
310                // 如果允许重连,尝试重连
311                if ClientConnectionHelper::can_reconnect(self.config.max_reconnect_attempts) {
312                    self.try_reconnect().await
313                } else {
314                    Err(e)
315                }
316            }
317        }
318    }
319
320    fn set_disconnect_requested(&mut self, value: bool) {
321        self.core.set_disconnect_requested(value);
322    }
323
324    async fn disconnect(&mut self) -> Result<()> {
325        ClientConnectionHelper::disconnect_internal(self.connection.take(), &mut self.core).await
326    }
327
328    async fn send_frame(&mut self, frame: &Frame) -> Result<()> {
329        // 如果未连接,尝试重连
330        if !self.is_connected()
331            && ClientConnectionHelper::can_reconnect(self.config.max_reconnect_attempts)
332        {
333            self.try_reconnect().await?;
334        }
335
336        self.send_frame_internal(frame).await
337    }
338
339    fn is_connected(&self) -> bool {
340        matches!(
341            self.core.state(),
342            crate::client::connection::ConnectionState::Connected
343        ) && self.connection.is_some()
344            && self.core.is_negotiation_completed()
345    }
346
347    fn add_observer(&mut self, observer: ArcObserver) {
348        self.core.add_observer(observer);
349    }
350
351    fn remove_observer(&mut self, observer: ArcObserver) {
352        self.core.remove_observer(observer);
353    }
354
355    fn connection_id(&self) -> Option<String> {
356        Some(self.connection_id.clone())
357    }
358}