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