mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-28 16:41:52 +08:00
feat: 添加 RustDesk 协议支持和项目文档
- 新增 RustDesk 模块,支持与 RustDesk 客户端连接 - 实现会合服务器协议和 P2P 连接 - 支持 NaCl 加密和密钥交换 - 添加视频帧和 HID 事件适配器 - 添加 Protobuf 协议定义 (message.proto, rendezvous.proto) - 新增完整项目文档 - 各功能模块文档 (video, hid, msd, otg, webrtc 等) - hwcodec 和 RustDesk 协议技术报告 - 系统架构和技术栈文档 - 更新 Web 前端 RustDesk 配置界面和 API
This commit is contained in:
428
docs/modules/web.md
Normal file
428
docs/modules/web.md
Normal file
@@ -0,0 +1,428 @@
|
||||
# Web 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
Web 模块提供 HTTP API 和静态文件服务。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- REST API
|
||||
- WebSocket
|
||||
- 静态文件服务
|
||||
- 认证中间件
|
||||
- CORS 支持
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/web/
|
||||
├── mod.rs # 模块导出
|
||||
├── routes.rs # 路由定义 (9KB)
|
||||
├── ws.rs # WebSocket (8KB)
|
||||
├── audio_ws.rs # 音频 WebSocket (8KB)
|
||||
├── static_files.rs # 静态文件 (6KB)
|
||||
└── handlers/ # API 处理器
|
||||
├── mod.rs
|
||||
└── config/
|
||||
├── mod.rs
|
||||
├── apply.rs
|
||||
├── types.rs
|
||||
└── rustdesk.rs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 路由结构
|
||||
|
||||
### 2.1 公共路由 (无认证)
|
||||
|
||||
| 路由 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/health` | GET | 健康检查 |
|
||||
| `/auth/login` | POST | 登录 |
|
||||
| `/setup` | GET | 获取设置状态 |
|
||||
| `/setup/init` | POST | 初始化设置 |
|
||||
|
||||
### 2.2 用户路由 (需认证)
|
||||
|
||||
| 路由 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/info` | GET | 系统信息 |
|
||||
| `/devices` | GET | 设备列表 |
|
||||
| `/stream/*` | * | 流控制 |
|
||||
| `/webrtc/*` | * | WebRTC 信令 |
|
||||
| `/hid/*` | * | HID 控制 |
|
||||
| `/audio/*` | * | 音频控制 |
|
||||
| `/ws` | WS | 事件 WebSocket |
|
||||
| `/ws/audio` | WS | 音频 WebSocket |
|
||||
|
||||
### 2.3 管理员路由 (需 Admin)
|
||||
|
||||
| 路由 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/config/*` | * | 配置管理 |
|
||||
| `/msd/*` | * | MSD 操作 |
|
||||
| `/atx/*` | * | ATX 控制 |
|
||||
| `/extensions/*` | * | 扩展管理 |
|
||||
| `/rustdesk/*` | * | RustDesk |
|
||||
| `/users/*` | * | 用户管理 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 路由定义
|
||||
|
||||
```rust
|
||||
pub fn create_router(state: Arc<AppState>) -> Router {
|
||||
Router::new()
|
||||
// 公共路由
|
||||
.route("/health", get(handlers::health))
|
||||
.route("/auth/login", post(handlers::login))
|
||||
.route("/setup", get(handlers::setup_status))
|
||||
.route("/setup/init", post(handlers::setup_init))
|
||||
|
||||
// 用户路由
|
||||
.nest("/api", user_routes())
|
||||
|
||||
// 管理员路由
|
||||
.nest("/api/admin", admin_routes())
|
||||
|
||||
// 静态文件
|
||||
.fallback(static_files::serve)
|
||||
|
||||
// 中间件
|
||||
.layer(CorsLayer::permissive())
|
||||
.layer(CompressionLayer::new())
|
||||
.layer(TraceLayer::new_for_http())
|
||||
|
||||
// 状态
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
fn user_routes() -> Router {
|
||||
Router::new()
|
||||
.route("/info", get(handlers::system_info))
|
||||
.route("/devices", get(handlers::list_devices))
|
||||
|
||||
// 流控制
|
||||
.route("/stream/status", get(handlers::stream_status))
|
||||
.route("/stream/start", post(handlers::stream_start))
|
||||
.route("/stream/stop", post(handlers::stream_stop))
|
||||
.route("/stream/mjpeg", get(handlers::mjpeg_stream))
|
||||
|
||||
// WebRTC
|
||||
.route("/webrtc/session", post(handlers::webrtc_create_session))
|
||||
.route("/webrtc/offer", post(handlers::webrtc_offer))
|
||||
.route("/webrtc/ice", post(handlers::webrtc_ice))
|
||||
.route("/webrtc/close", post(handlers::webrtc_close))
|
||||
|
||||
// HID
|
||||
.route("/hid/status", get(handlers::hid_status))
|
||||
.route("/hid/reset", post(handlers::hid_reset))
|
||||
|
||||
// WebSocket
|
||||
.route("/ws", get(handlers::ws_handler))
|
||||
.route("/ws/audio", get(handlers::audio_ws_handler))
|
||||
|
||||
// 认证中间件
|
||||
.layer(middleware::from_fn(auth_middleware))
|
||||
}
|
||||
|
||||
fn admin_routes() -> Router {
|
||||
Router::new()
|
||||
// 配置
|
||||
.route("/config", get(handlers::config::get_config))
|
||||
.route("/config", patch(handlers::config::update_config))
|
||||
|
||||
// MSD
|
||||
.route("/msd/status", get(handlers::msd_status))
|
||||
.route("/msd/connect", post(handlers::msd_connect))
|
||||
|
||||
// ATX
|
||||
.route("/atx/status", get(handlers::atx_status))
|
||||
.route("/atx/power/short", post(handlers::atx_power_short))
|
||||
|
||||
// 认证中间件
|
||||
.layer(middleware::from_fn(auth_middleware))
|
||||
.layer(middleware::from_fn(admin_middleware))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 静态文件服务
|
||||
|
||||
```rust
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "web/dist"]
|
||||
#[include = "*.html"]
|
||||
#[include = "*.js"]
|
||||
#[include = "*.css"]
|
||||
#[include = "assets/*"]
|
||||
struct Assets;
|
||||
|
||||
pub async fn serve(uri: Uri) -> impl IntoResponse {
|
||||
let path = uri.path().trim_start_matches('/');
|
||||
|
||||
// 尝试获取文件
|
||||
if let Some(content) = Assets::get(path) {
|
||||
let mime = mime_guess::from_path(path)
|
||||
.first_or_octet_stream();
|
||||
|
||||
return (
|
||||
[(header::CONTENT_TYPE, mime.as_ref())],
|
||||
content.data.into_owned(),
|
||||
).into_response();
|
||||
}
|
||||
|
||||
// SPA 回退到 index.html
|
||||
if let Some(content) = Assets::get("index.html") {
|
||||
return (
|
||||
[(header::CONTENT_TYPE, "text/html")],
|
||||
content.data.into_owned(),
|
||||
).into_response();
|
||||
}
|
||||
|
||||
StatusCode::NOT_FOUND.into_response()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. WebSocket 处理
|
||||
|
||||
### 5.1 事件 WebSocket (ws.rs)
|
||||
|
||||
```rust
|
||||
pub async fn ws_handler(
|
||||
ws: WebSocketUpgrade,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(|socket| handle_ws(socket, state))
|
||||
}
|
||||
|
||||
async fn handle_ws(mut socket: WebSocket, state: Arc<AppState>) {
|
||||
// 发送初始设备信息
|
||||
let device_info = state.get_device_info().await;
|
||||
let json = serde_json::to_string(&device_info).unwrap();
|
||||
let _ = socket.send(Message::Text(json)).await;
|
||||
|
||||
// 订阅事件
|
||||
let mut rx = state.events.subscribe();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// 发送事件
|
||||
result = rx.recv() => {
|
||||
if let Ok(event) = result {
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
if socket.send(Message::Text(json)).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 接收消息 (心跳/关闭)
|
||||
msg = socket.recv() => {
|
||||
match msg {
|
||||
Some(Ok(Message::Ping(data))) => {
|
||||
let _ = socket.send(Message::Pong(data)).await;
|
||||
}
|
||||
Some(Ok(Message::Close(_))) | None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 音频 WebSocket (audio_ws.rs)
|
||||
|
||||
```rust
|
||||
pub async fn audio_ws_handler(
|
||||
ws: WebSocketUpgrade,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(|socket| handle_audio_ws(socket, state))
|
||||
}
|
||||
|
||||
async fn handle_audio_ws(mut socket: WebSocket, state: Arc<AppState>) {
|
||||
// 订阅音频帧
|
||||
let mut rx = state.audio.subscribe();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// 发送音频帧
|
||||
result = rx.recv() => {
|
||||
if let Ok(frame) = result {
|
||||
if socket.send(Message::Binary(frame.data.to_vec())).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理关闭
|
||||
msg = socket.recv() => {
|
||||
match msg {
|
||||
Some(Ok(Message::Close(_))) | None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. MJPEG 流
|
||||
|
||||
```rust
|
||||
pub async fn mjpeg_stream(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
let boundary = "frame";
|
||||
|
||||
// 订阅视频帧
|
||||
let rx = state.stream_manager.subscribe_mjpeg();
|
||||
|
||||
// 创建流
|
||||
let stream = async_stream::stream! {
|
||||
let mut rx = rx;
|
||||
while let Ok(frame) = rx.recv().await {
|
||||
let header = format!(
|
||||
"--{}\r\nContent-Type: image/jpeg\r\nContent-Length: {}\r\n\r\n",
|
||||
boundary,
|
||||
frame.data.len()
|
||||
);
|
||||
yield Ok::<_, std::io::Error>(Bytes::from(header));
|
||||
yield Ok(frame.data.clone());
|
||||
yield Ok(Bytes::from("\r\n"));
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
format!("multipart/x-mixed-replace; boundary={}", boundary),
|
||||
)],
|
||||
Body::from_stream(stream),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误处理
|
||||
|
||||
```rust
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, message) = match self {
|
||||
AppError::AuthError => (StatusCode::UNAUTHORIZED, "Authentication failed"),
|
||||
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"),
|
||||
AppError::Forbidden => (StatusCode::FORBIDDEN, "Forbidden"),
|
||||
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.as_str()),
|
||||
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.as_str()),
|
||||
AppError::Internal(err) => {
|
||||
tracing::error!("Internal error: {:?}", err);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
|
||||
}
|
||||
// ...
|
||||
};
|
||||
|
||||
(status, Json(json!({ "error": message }))).into_response()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 请求提取器
|
||||
|
||||
```rust
|
||||
// 从 Cookie 获取会话
|
||||
pub struct AuthUser(pub Session);
|
||||
|
||||
#[async_trait]
|
||||
impl<S> FromRequestParts<S> for AuthUser
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = AppError;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let cookies = Cookies::from_request_parts(parts, state).await?;
|
||||
let token = cookies
|
||||
.get("session_id")
|
||||
.map(|c| c.value().to_string())
|
||||
.ok_or(AppError::Unauthorized)?;
|
||||
|
||||
let state = parts.extensions.get::<Arc<AppState>>().unwrap();
|
||||
let session = state.sessions
|
||||
.get_session(&token)
|
||||
.ok_or(AppError::Unauthorized)?;
|
||||
|
||||
Ok(AuthUser(session))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 中间件
|
||||
|
||||
### 9.1 认证中间件
|
||||
|
||||
```rust
|
||||
pub async fn auth_middleware(
|
||||
State(state): State<Arc<AppState>>,
|
||||
cookies: Cookies,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
let token = cookies
|
||||
.get("session_id")
|
||||
.map(|c| c.value().to_string());
|
||||
|
||||
if let Some(session) = token.and_then(|t| state.sessions.get_session(&t)) {
|
||||
request.extensions_mut().insert(session);
|
||||
next.run(request).await
|
||||
} else {
|
||||
StatusCode::UNAUTHORIZED.into_response()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Admin 中间件
|
||||
|
||||
```rust
|
||||
pub async fn admin_middleware(
|
||||
Extension(session): Extension<Session>,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
if session.role == UserRole::Admin {
|
||||
next.run(request).await
|
||||
} else {
|
||||
StatusCode::FORBIDDEN.into_response()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. HTTPS 支持
|
||||
|
||||
```rust
|
||||
// 使用 axum-server 提供 TLS
|
||||
let tls_config = RustlsConfig::from_pem_file(cert_path, key_path).await?;
|
||||
|
||||
axum_server::bind_rustls(addr, tls_config)
|
||||
.serve(app.into_make_service())
|
||||
.await?;
|
||||
|
||||
// 或自动生成自签名证书
|
||||
let (cert, key) = generate_self_signed_cert()?;
|
||||
let tls_config = RustlsConfig::from_pem(cert, key).await?;
|
||||
```
|
||||
Reference in New Issue
Block a user