Skip to main content

mf_collab/
ws_server.rs

1use std::sync::Arc;
2use crate::{YrsManager, SyncService};
3use crate::sync_service::{RoomInfo, RoomStatus};
4use warp::ws::{WebSocket, Ws};
5use warp::{Filter, Rejection, Reply};
6use yrs_warp::broadcast::BroadcastGroup;
7use yrs_warp::ws::{WarpSink, WarpStream};
8use tokio::sync::Mutex;
9use futures_util::StreamExt;
10use serde_json::json;
11
12/// 自定义错误类型用于房间不存在的情况
13#[derive(Debug)]
14pub struct RoomNotFoundError {
15    room_id: String,
16}
17
18impl warp::reject::Reject for RoomNotFoundError {}
19
20impl RoomNotFoundError {
21    pub fn new(room_id: String) -> Self {
22        Self { room_id }
23    }
24
25    pub fn room_id(&self) -> &str {
26        &self.room_id
27    }
28}
29
30/// YrsManager 的包装器,用于处理动态房间创建和广播组管理
31#[derive(Clone)]
32pub struct CollaborationServer {
33    yrs_manager: Arc<YrsManager>,
34    sync_service: Arc<SyncService>,
35    port: u16,
36}
37
38impl CollaborationServer {
39    pub fn new(
40        yrs_manager: Arc<YrsManager>,
41        port: u16,
42    ) -> Self {
43        let sync_service = Arc::new(SyncService::new(yrs_manager.clone()));
44        Self { yrs_manager, sync_service, port }
45    }
46
47    /// 使用现有的 SyncService 创建服务器
48    pub fn with_sync_service(
49        yrs_manager: Arc<YrsManager>,
50        sync_service: Arc<SyncService>,
51        port: u16,
52    ) -> Self {
53        Self { yrs_manager, sync_service, port }
54    }
55
56    /// 自定义错误处理器
57    pub async fn handle_rejection(
58        err: Rejection
59    ) -> Result<impl Reply, std::convert::Infallible> {
60        if let Some(room_error) = err.find::<RoomNotFoundError>() {
61            let error_response = json!({
62                "error": "ROOM_NOT_FOUND",
63                "message": format!("房间 '{}' 不存在", room_error.room_id()),
64                "room_id": room_error.room_id(),
65                "code": 404
66            });
67
68            let reply = warp::reply::with_status(
69                warp::reply::json(&error_response),
70                warp::http::StatusCode::NOT_FOUND,
71            );
72
73            return Ok(reply.into_response());
74        }
75
76        // 处理其他错误
77        if err.is_not_found() {
78            let error_response = json!({
79                "error": "NOT_FOUND",
80                "message": "请求的资源不存在",
81                "code": 404
82            });
83
84            let reply = warp::reply::with_status(
85                warp::reply::json(&error_response),
86                warp::http::StatusCode::NOT_FOUND,
87            );
88
89            return Ok(reply.into_response());
90        }
91
92        // 默认错误处理
93        let error_response = json!({
94            "error": "INTERNAL_SERVER_ERROR",
95            "message": "服务器内部错误",
96            "code": 500
97        });
98
99        let reply = warp::reply::with_status(
100            warp::reply::json(&error_response),
101            warp::http::StatusCode::INTERNAL_SERVER_ERROR,
102        );
103
104        Ok(reply.into_response())
105    }
106
107    /// 房间下线 - 优雅关闭房间
108    /// 1. 通知所有客户端房间即将关闭
109    /// 2. 等待客户端完成当前操作
110    /// 3. 保存数据(可选)
111    /// 4. 清理资源
112    pub async fn offline_room(
113        &self,
114        room_id: &str,
115        save_data: bool,
116    ) -> crate::Result<bool> {
117        tracing::info!("🔄 开始下线房间: {}", room_id);
118
119        // 1. 检查房间状态
120        let room_status = self.sync_service.get_room_status(room_id).await;
121        match room_status {
122            RoomStatus::NotExists => {
123                tracing::warn!("❌ 尝试下线不存在的房间: {}", room_id);
124                return Ok(false);
125            },
126            RoomStatus::Offline => {
127                tracing::info!("ℹ️ 房间 {} 已经下线", room_id);
128                return Ok(true);
129            },
130            _ => {},
131        }
132
133        // 2. 获取房间信息
134        if let Some(room_info) = self.sync_service.get_room_info(room_id).await
135        {
136            tracing::info!(
137                "📊 房间信息 - 节点数: {}, 客户端数: {}",
138                room_info.node_count,
139                room_info.client_count
140            );
141        }
142
143        // 3. 执行下线操作
144        match self.sync_service.offline_room(room_id, save_data).await {
145            Ok(snapshot) => {
146                if let Some(_snapshot) = snapshot {
147                    tracing::info!("💾 房间 {} 数据已保存", room_id);
148                }
149                tracing::info!("✅ 房间 {} 成功下线", room_id);
150                Ok(true)
151            },
152            Err(e) => {
153                tracing::error!("❌ 房间 {} 下线失败: {}", room_id, e);
154                Err(e)
155            },
156        }
157    }
158
159    /// 强制房间下线 - 紧急情况使用
160    pub async fn force_offline_room(
161        &self,
162        room_id: &str,
163    ) -> crate::Result<bool> {
164        tracing::warn!("⚠️ 强制下线房间: {}", room_id);
165
166        match self.sync_service.force_offline_room(room_id).await {
167            Ok(success) => {
168                if success {
169                    tracing::info!("✅ 房间 {} 强制下线成功", room_id);
170                } else {
171                    tracing::error!("❌ 房间 {} 强制下线失败", room_id);
172                }
173                Ok(success)
174            },
175            Err(e) => {
176                tracing::error!(
177                    "❌ 强制下线房间 {} 时发生错误: {}",
178                    room_id,
179                    e
180                );
181                Err(e)
182            },
183        }
184    }
185
186    /// 批量下线房间
187    pub async fn offline_rooms(
188        &self,
189        room_ids: &[String],
190        save_data: bool,
191    ) -> crate::Result<Vec<(String, bool)>> {
192        tracing::info!("🔄 批量下线 {} 个房间", room_ids.len());
193
194        let mut results = Vec::new();
195
196        for room_id in room_ids {
197            match self.offline_room(room_id, save_data).await {
198                Ok(success) => results.push((room_id.clone(), success)),
199                Err(e) => {
200                    tracing::error!("❌ 下线房间 {} 失败: {}", room_id, e);
201                    results.push((room_id.clone(), false));
202                },
203            }
204        }
205
206        let successful_count =
207            results.iter().filter(|(_, success)| *success).count();
208        tracing::info!(
209            "📊 批量下线完成: {}/{} 个房间成功下线",
210            successful_count,
211            room_ids.len()
212        );
213
214        Ok(results)
215    }
216
217    /// 获取所有活跃房间列表
218    pub fn get_active_rooms(&self) -> Vec<String> {
219        self.sync_service.get_active_rooms()
220    }
221
222    /// 获取房间统计信息
223    pub async fn get_rooms_stats(&self) -> Vec<RoomInfo> {
224        self.sync_service.get_rooms_stats().await
225    }
226
227    /// 根据条件下线房间
228    /// 例如:下线空闲时间超过指定时间的房间、下线没有客户端的房间等
229    pub async fn offline_rooms_by_condition<F>(
230        &self,
231        condition: F,
232        save_data: bool,
233    ) -> crate::Result<Vec<String>>
234    where
235        F: Fn(&RoomInfo) -> bool,
236    {
237        let room_stats = self.get_rooms_stats().await;
238        let rooms_to_offline: Vec<String> = room_stats
239            .into_iter()
240            .filter(|info| condition(info))
241            .map(|info| info.room_id)
242            .collect();
243
244        if rooms_to_offline.is_empty() {
245            tracing::info!("🔍 没有房间满足下线条件");
246            return Ok(vec![]);
247        }
248
249        tracing::info!("🎯 找到 {} 个房间满足下线条件", rooms_to_offline.len());
250
251        let results = self.offline_rooms(&rooms_to_offline, save_data).await?;
252        let successful_rooms: Vec<String> = results
253            .into_iter()
254            .filter_map(
255                |(room_id, success)| if success { Some(room_id) } else { None },
256            )
257            .collect();
258
259        Ok(successful_rooms)
260    }
261
262    /// 下线空房间(没有客户端连接的房间)
263    pub async fn offline_empty_rooms(
264        &self,
265        save_data: bool,
266    ) -> crate::Result<Vec<String>> {
267        tracing::info!("🔍 搜索并下线空房间");
268
269        self.offline_rooms_by_condition(
270            |room_info| room_info.client_count == 0,
271            save_data,
272        )
273        .await
274    }
275
276    /// 下线长时间未活动的房间
277    pub async fn offline_inactive_rooms(
278        &self,
279        inactive_duration: std::time::Duration,
280        save_data: bool,
281    ) -> crate::Result<Vec<String>> {
282        let now = std::time::SystemTime::now();
283        tracing::info!(
284            "🔍 搜索并下线超过 {:?} 未活动的房间",
285            inactive_duration
286        );
287
288        self.offline_rooms_by_condition(
289            |room_info| {
290                now.duration_since(room_info.last_activity).unwrap_or_default()
291                    > inactive_duration
292            },
293            save_data,
294        )
295        .await
296    }
297
298    /// 服务器完全关闭 - 下线所有房间
299    pub async fn shutdown(
300        &self,
301        save_all_data: bool,
302    ) -> crate::Result<()> {
303        tracing::info!("🔴 开始服务器关闭流程");
304
305        let all_rooms = self.get_active_rooms();
306        if all_rooms.is_empty() {
307            tracing::info!("ℹ️ 没有活跃房间需要关闭");
308            return Ok(());
309        }
310
311        tracing::info!("📊 准备关闭 {} 个房间", all_rooms.len());
312
313        // 批量下线所有房间
314        let results = self.offline_rooms(&all_rooms, save_all_data).await?;
315        let successful_count =
316            results.iter().filter(|(_, success)| *success).count();
317
318        tracing::info!(
319            "✅ 服务器关闭完成: {}/{} 个房间成功下线",
320            successful_count,
321            all_rooms.len()
322        );
323
324        if successful_count != all_rooms.len() {
325            tracing::warn!("⚠️ 部分房间下线失败,可能需要手动清理");
326        }
327
328        Ok(())
329    }
330
331    /// 启动 WebSocket 服务器
332    pub async fn start(self) {
333        let server = self.clone(); // 克隆 self 以移动到过滤器
334
335        // WebSocket 路由(带错误处理)
336        let ws_route = warp::path("collaboration")
337            .and(warp::path::param::<String>()) // Expect a room_id in the path, e.g., /collaboration/my-room-name
338            .and(warp::ws())
339            .and(warp::addr::remote()) // 这里添加
340            .and(warp::any().map(move || server.clone()))
341            .and_then(Self::ws_handler);
342
343        // HTTP 房间检查路由
344        let server_for_http = self.clone();
345        let room_check_route = warp::path("collaboration")
346            .and(warp::path("room-check"))
347            .and(warp::path::param::<String>()) // room_id
348            .and(warp::get())
349            .and(warp::any().map(move || server_for_http.clone()))
350            .and_then(Self::room_check_handler);
351
352        // 健康检查路由
353        let server_for_health = self.clone();
354        let health_route = warp::path("health")
355            .and(warp::get())
356            .and(warp::any().map(move || server_for_health.clone()))
357            .and_then(Self::health_check_handler);
358
359        // 房间状态路由
360        let server_for_status = self.clone();
361        let room_status_route = warp::path("collaboration")
362            .and(warp::path("rooms"))
363            .and(warp::path::param::<String>()) // room_id
364            .and(warp::path("status"))
365            .and(warp::get())
366            .and(warp::any().map(move || server_for_status.clone()))
367            .and_then(Self::room_status_handler);
368
369        // 合并所有路由并添加全局错误处理
370        let routes = ws_route
371            .or(room_check_route)
372            .or(health_route)
373            .or(room_status_route)
374            .recover(Self::handle_rejection) // 移到这里,对所有路由应用错误处理
375            .with(
376                warp::cors()
377                    .allow_any_origin()
378                    .allow_headers(vec!["content-type"])
379                    .allow_methods(vec![
380                        "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS",
381                    ]),
382            );
383
384        let addr = ([0, 0, 0, 0], self.port);
385        tracing::info!(
386            "🌐 协作服务器启动于 http://{}:{}",
387            addr.0.iter().map(|&o| o.to_string()).collect::<Vec<_>>().join("."),
388            addr.1
389        );
390        tracing::info!(
391            "📡 WebSocket: ws://{}:{}/collaboration/{{room_id}}",
392            addr.0.iter().map(|&o| o.to_string()).collect::<Vec<_>>().join("."),
393            addr.1
394        );
395        tracing::info!(
396            "🔍 房间检查: http://{}:{}/collaboration/room-check/{{room_id}}",
397            addr.0.iter().map(|&o| o.to_string()).collect::<Vec<_>>().join("."),
398            addr.1
399        );
400        tracing::info!(
401            "💚 健康检查: http://{}:{}/health",
402            addr.0.iter().map(|&o| o.to_string()).collect::<Vec<_>>().join("."),
403            addr.1
404        );
405        tracing::info!(
406            "📊 房间状态: http://{}:{}/collaboration/rooms/{{room_id}}/status",
407            addr.0.iter().map(|&o| o.to_string()).collect::<Vec<_>>().join("."),
408            addr.1
409        );
410
411        warp::serve(routes).run(addr).await;
412    }
413
414    /// WebSocket connection handler with room initialization.
415    async fn ws_handler(
416        room_id: String,
417        ws: Ws,
418        remote_addr: Option<std::net::SocketAddr>,
419        server: CollaborationServer,
420    ) -> Result<impl Reply, Rejection> {
421        let yrs_manager = server.yrs_manager.clone();
422        // 获取已存在的 awareness(不创建新的)
423        let awareness_ref = yrs_manager.get_or_create_awareness(&room_id);
424        Ok(ws.on_upgrade(move |socket| async move {
425            tracing::info!("✅ 客户端成功连接到现有房间: {}", room_id);
426            let client_addr = remote_addr
427                .map(|addr| addr.to_string())
428                .unwrap_or_else(|| "unknown".to_string());
429            // The buffer capacity can be adjusted as needed. 128 is a reasonable default.
430            let bcast = Arc::new(BroadcastGroup::new(awareness_ref, 128).await);
431            Self::peer(socket, bcast, room_id.clone(), client_addr).await;
432        }))
433    }
434
435    /// 处理单个客户端连接(基于官方示例)
436    async fn peer(
437        ws: WebSocket,
438        bcast: Arc<BroadcastGroup>,
439        room_id: String,
440        client_addr: String,
441    ) {
442        let (sink, stream) = ws.split();
443        let sink = Arc::new(Mutex::new(WarpSink::from(sink)));
444        let stream = WarpStream::from(stream);
445        // 增加客户端连接的详细日志
446        tracing::info!(
447            "🔗 新客户端连接到房间: {} (地址: {})",
448            room_id,
449            client_addr
450        );
451
452        let sub = bcast.subscribe(sink, stream);
453
454        match sub.completed().await {
455            Ok(_) => {
456                tracing::info!(
457                    "✅ 客户端正常断开连接 - 房间: {} (地址: {})",
458                    room_id,
459                    client_addr
460                );
461            },
462            Err(e) => {
463                // 根据错误类型提供更详细的错误信息
464                let error_msg = format!("{e}");
465
466                if error_msg.contains("failed to deserialize message") {
467                    tracing::warn!(
468                        "⚠️ 客户端发送了无效数据包 - 房间: {}, 错误: {}",
469                        room_id,
470                        error_msg
471                    );
472                    tracing::debug!(
473                        "💡 这通常是由网络中断或客户端异常关闭导致的,属于正常现象"
474                    );
475                } else if error_msg.contains("unexpected end of buffer") {
476                    tracing::warn!(
477                        "⚠️ 数据包不完整 - 房间: {}, 可能是网络传输中断",
478                        room_id
479                    );
480                } else if error_msg.contains("connection closed")
481                    || error_msg.contains("broken pipe")
482                {
483                    tracing::info!(
484                        "🔌 客户端连接意外断开 - 房间: {} ({})",
485                        room_id,
486                        error_msg
487                    );
488                } else {
489                    tracing::error!(
490                        "❌ 客户端连接异常 - 房间: {}, 错误: {}",
491                        room_id,
492                        error_msg
493                    );
494                }
495            },
496        }
497    }
498
499    /// 获取 SyncService 的引用,用于外部操作
500    pub fn sync_service(&self) -> &Arc<SyncService> {
501        &self.sync_service
502    }
503
504    /// HTTP 房间检查处理器
505    async fn room_check_handler(
506        room_id: String,
507        server: CollaborationServer,
508    ) -> Result<impl Reply, Rejection> {
509        tracing::debug!("🔍 检查房间是否存在: {}", room_id);
510
511        let exists = server.yrs_manager.room_exists(&room_id);
512
513        if exists {
514            let room_info = server.sync_service.get_room_info(&room_id).await;
515
516            let response = json!({
517                "exists": true,
518                "room_id": room_id,
519                "status": "available",
520                "info": room_info
521            });
522
523            tracing::debug!("✅ 房间 {} 存在", room_id);
524            Ok(warp::reply::with_status(
525                warp::reply::json(&response),
526                warp::http::StatusCode::OK,
527            ))
528        } else {
529            let response = json!({
530                "exists": false,
531                "room_id": room_id,
532                "status": "not_found",
533                "message": format!("房间 '{}' 不存在", room_id)
534            });
535
536            tracing::debug!("❌ 房间 {} 不存在", room_id);
537            Ok(warp::reply::with_status(
538                warp::reply::json(&response),
539                warp::http::StatusCode::NOT_FOUND,
540            ))
541        }
542    }
543
544    /// 健康检查处理器
545    async fn health_check_handler(
546        server: CollaborationServer
547    ) -> Result<impl Reply, Rejection> {
548        let room_stats = server.sync_service.get_rooms_stats().await;
549        let active_rooms = server.sync_service.get_active_rooms();
550
551        let response = json!({
552            "status": "healthy",
553            "timestamp": std::time::SystemTime::now()
554                .duration_since(std::time::UNIX_EPOCH)
555                .unwrap_or_default()
556                .as_secs(),
557            "service": "ModuForge Collaboration Server",
558            "version": env!("CARGO_PKG_VERSION"),
559            "statistics": {
560                "active_rooms": active_rooms.len(),
561                "total_rooms": room_stats.len(),
562                "rooms": active_rooms
563            }
564        });
565
566        Ok(warp::reply::with_status(
567            warp::reply::json(&response),
568            warp::http::StatusCode::OK,
569        ))
570    }
571
572    /// 房间状态处理器
573    async fn room_status_handler(
574        room_id: String,
575        server: CollaborationServer,
576    ) -> Result<impl Reply, Rejection> {
577        tracing::debug!("📊 获取房间状态: {}", room_id);
578
579        if let Some(room_info) =
580            server.sync_service.get_room_info(&room_id).await
581        {
582            let response = json!({
583                "room_id": room_id,
584                "status": room_info.status,
585                "node_count": room_info.node_count,
586                "client_count": room_info.client_count,
587                "last_activity": room_info.last_activity
588                    .duration_since(std::time::UNIX_EPOCH)
589                    .unwrap_or_default()
590                    .as_secs(),
591                "available": true
592            });
593
594            Ok(warp::reply::with_status(
595                warp::reply::json(&response),
596                warp::http::StatusCode::OK,
597            ))
598        } else {
599            let response = json!({
600                "room_id": room_id,
601                "status": "not_found",
602                "available": false,
603                "message": format!("房间 '{}' 不存在", room_id)
604            });
605
606            Ok(warp::reply::with_status(
607                warp::reply::json(&response),
608                warp::http::StatusCode::NOT_FOUND,
609            ))
610        }
611    }
612}