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