- Rust axum signaling server with WebSocket support - Lit + TypeScript frontend with Vite - Redis session storage with TTL - WebRTC transport and crypto client stubs - Phase1 architecture plan in plans/ - Deploy directory structure prepared
393 lines
7.7 KiB
Markdown
393 lines
7.7 KiB
Markdown
# FileDrop - 安全无痕文件传输 Web App
|
||
|
||
## 产品定位
|
||
|
||
免费、无注册、无历史记录的安全文件传输工具。
|
||
|
||
- 优先局域网,兼顾公网
|
||
- WebRTC 直连优先,TURN 兜底
|
||
- 浏览器端端到端加密
|
||
- 传完即销毁,不留痕迹
|
||
|
||
---
|
||
|
||
## 第一期范围
|
||
|
||
### 要做
|
||
|
||
- 扫码/短码配对
|
||
- WebRTC DataChannel 传输
|
||
- 浏览器端 AES-GCM 分块加密
|
||
- TURN 回退
|
||
- 会话超时自动清理
|
||
- 小文件传输(<100MB)
|
||
|
||
### 不做
|
||
|
||
- 用户注册/登录
|
||
- 历史记录
|
||
- 大文件/断点续传
|
||
- 付费/套餐
|
||
- Postgres
|
||
- WASM
|
||
- 对象存储中转
|
||
|
||
---
|
||
|
||
## 技术栈
|
||
|
||
| 层 | 技术 |
|
||
|---|---|
|
||
| 前端 | Lit + TypeScript + Vite |
|
||
| 后端 | Rust + axum + tokio |
|
||
| 临时状态 | Redis |
|
||
| NAT 穿透 | coturn |
|
||
| 反向代理 | Caddy |
|
||
| 部署 | Debian 12 + systemd |
|
||
|
||
---
|
||
|
||
## 系统架构
|
||
|
||
```
|
||
Browser A Browser B
|
||
| |
|
||
|---- HTTPS / WSS -------------|
|
||
| |
|
||
Rust Signaling Server
|
||
|
|
||
Redis
|
||
|
|
||
coturn
|
||
```
|
||
|
||
### 职责划分
|
||
|
||
**浏览器**
|
||
- 选文件
|
||
- 生成会话密钥
|
||
- 二维码/短码配对
|
||
- WebRTC 建连
|
||
- 文件分块加密
|
||
- DataChannel 发送/接收
|
||
|
||
**Rust 服务端**
|
||
- 创建/加入会话
|
||
- WebSocket 信令转发
|
||
- 临时会话状态
|
||
- 过期清理
|
||
- 下发 ICE 配置
|
||
|
||
**Redis**
|
||
- 会话临时状态
|
||
- 在线状态
|
||
- 一次性加入令牌
|
||
- TTL 自动过期
|
||
|
||
**coturn**
|
||
- STUN/TURN 服务
|
||
|
||
---
|
||
|
||
## 核心流程
|
||
|
||
### 1. 创建会话
|
||
|
||
1. 发送端选择文件
|
||
2. 浏览器本地生成 `session_secret`
|
||
3. 请求 `POST /api/sessions`
|
||
4. 服务端返回 `room_id`、`join_token`、`ws_url`、`ice_servers`
|
||
5. 前端生成分享信息:
|
||
- 短码:`room_id`
|
||
- 链接:`/join/{room_id}#k={session_secret}&t={join_token}`
|
||
|
||
> `#k=` 在 URL fragment 中,不会发给服务端
|
||
|
||
### 2. 加入会话
|
||
|
||
1. 接收端扫码进入
|
||
2. 前端从 fragment 取出 `session_secret` 和 `join_token`
|
||
3. 请求 `POST /api/sessions/{room_id}/join`
|
||
4. 服务端校验会话状态和 token
|
||
5. 成功后建立 WebSocket
|
||
|
||
### 3. 信令协商
|
||
|
||
1. 双方连上 `WS /ws`
|
||
2. 发送端创建 offer
|
||
3. 接收端返回 answer
|
||
4. 双方交换 ICE candidate
|
||
5. 建立 `RTCDataChannel`
|
||
|
||
### 4. 文件传输
|
||
|
||
1. 发送端发送 `file_manifest`
|
||
2. 文件切块(64KB ~ 256KB)
|
||
3. 每块独立 AES-GCM 加密
|
||
4. 通过 DataChannel 发送
|
||
5. 接收端逐块解密并缓存
|
||
6. 完成后生成下载文件
|
||
7. 双方发送完成确认并关闭会话
|
||
|
||
### 5. 会话销毁
|
||
|
||
触发条件:
|
||
- 传输完成
|
||
- 用户主动取消
|
||
- 超时(15 分钟)
|
||
- WebSocket/RTC 长时间断开
|
||
|
||
---
|
||
|
||
## 协议设计
|
||
|
||
### HTTP API
|
||
|
||
#### `POST /api/sessions`
|
||
|
||
创建会话
|
||
|
||
**请求**
|
||
```json
|
||
{
|
||
"file_count": 2,
|
||
"total_size": 1834201
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
```json
|
||
{
|
||
"room_id": "8F4K2P",
|
||
"join_token": "opaque-token",
|
||
"ws_url": "wss://app.example.com/ws",
|
||
"expires_at": "2026-04-09T12:00:00Z",
|
||
"ice_servers": [
|
||
{ "urls": ["stun:turn.example.com:3478"] },
|
||
{
|
||
"urls": ["turn:turn.example.com:3478?transport=udp"],
|
||
"username": "u",
|
||
"credential": "p"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### `POST /api/sessions/{room_id}/join`
|
||
|
||
加入会话
|
||
|
||
**请求**
|
||
```json
|
||
{
|
||
"join_token": "opaque-token"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
```json
|
||
{
|
||
"ws_url": "wss://app.example.com/ws",
|
||
"expires_at": "2026-04-09T12:00:00Z",
|
||
"ice_servers": [...]
|
||
}
|
||
```
|
||
|
||
#### `GET /health`
|
||
|
||
健康检查
|
||
|
||
### WebSocket 消息
|
||
|
||
统一格式:
|
||
```json
|
||
{
|
||
"type": "offer|answer|ice|ready|cancel|error|ping|pong",
|
||
"room_id": "8F4K2P",
|
||
"role": "sender|receiver",
|
||
"payload": {}
|
||
}
|
||
```
|
||
|
||
### DataChannel 消息
|
||
|
||
控制消息(JSON):
|
||
- `file_manifest` - 文件清单
|
||
- `chunk_ack` - 分块确认
|
||
- `transfer_complete` - 传输完成
|
||
|
||
数据面:`ArrayBuffer`(加密分块)
|
||
|
||
---
|
||
|
||
## 安全设计
|
||
|
||
1. 会话密钥只在浏览器生成和持有
|
||
2. 文件名和文件内容都加密
|
||
3. 服务端只看到 room_id、连接状态、粗粒度元信息
|
||
4. 不保存文件内容
|
||
5. 不保存传输历史
|
||
6. Redis 全部使用 TTL
|
||
7. 所有页面和 WS 强制 HTTPS/WSS
|
||
8. TURN 使用临时凭证
|
||
9. 前端在完成或取消后清空密钥和缓存
|
||
|
||
**加密方案**
|
||
- 会话密钥:32 字节随机
|
||
- 密钥派生:HKDF-SHA-256
|
||
- 分块加密:AES-GCM
|
||
- 摘要校验:SHA-256
|
||
|
||
---
|
||
|
||
## 前端页面
|
||
|
||
| 路由 | 说明 |
|
||
|---|---|
|
||
| `/` | 首页:发送/接收入口 |
|
||
| `/send` | 选文件、创建会话、展示二维码 |
|
||
| `/join/:roomId` | 扫码进入、确认接收、下载 |
|
||
| `/expired` | 会话失效提示 |
|
||
|
||
---
|
||
|
||
## Redis 设计
|
||
|
||
### Key 结构
|
||
|
||
- `session:{room_id}` - 会话元数据
|
||
- `presence:{room_id}:sender` - 发送端在线状态
|
||
- `presence:{room_id}:receiver` - 接收端在线状态
|
||
- `join_token:{room_id}` - 一次性加入令牌
|
||
- `ws:{connection_id}` - WebSocket 连接映射
|
||
|
||
### TTL
|
||
|
||
- 会话:15 分钟
|
||
- 已完成会话:立即删除或保留 1 分钟缓冲
|
||
- WebSocket presence:30-60 秒心跳续期
|
||
|
||
---
|
||
|
||
## 开发里程碑
|
||
|
||
### P0 原型
|
||
- 创建/加入会话
|
||
- WS 信令
|
||
- DataChannel 建立
|
||
- 文本消息互通
|
||
|
||
### P1 小文件传输
|
||
- 文件 manifest
|
||
- 分块发送
|
||
- 接收后下载
|
||
- 进度显示
|
||
|
||
### P2 安全闭环
|
||
- 会话密钥生成
|
||
- 文件名/内容加密
|
||
- 会话销毁
|
||
- Redis TTL 清理
|
||
|
||
### P3 稳定性
|
||
- TURN 兜底验证
|
||
- 异常断开处理
|
||
- 超时与取消
|
||
- Chrome / Safari / iOS 测试
|
||
|
||
### P4 上线
|
||
- Debian 部署
|
||
- HTTPS/WSS
|
||
- 基础限流
|
||
- 基础日志与监控
|
||
|
||
---
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
filedrop/
|
||
├── Cargo.toml
|
||
├── plans/
|
||
│ └── phase1-architecture.md
|
||
├── server/
|
||
│ ├── src/
|
||
│ │ ├── main.rs
|
||
│ │ ├── config.rs
|
||
│ │ ├── api/
|
||
│ │ │ ├── mod.rs
|
||
│ │ │ ├── sessions.rs
|
||
│ │ │ └── health.rs
|
||
│ │ ├── signaling/
|
||
│ │ │ ├── mod.rs
|
||
│ │ │ ├── ws_handler.rs
|
||
│ │ │ └── room.rs
|
||
│ │ ├── session/
|
||
│ │ │ ├── mod.rs
|
||
│ │ │ ├── service.rs
|
||
│ │ │ └── store.rs
|
||
│ │ └── ice/
|
||
│ │ ├── mod.rs
|
||
│ │ └── config.rs
|
||
│ └── Cargo.toml
|
||
├── web/
|
||
│ ├── package.json
|
||
│ ├── tsconfig.json
|
||
│ ├── vite.config.ts
|
||
│ ├── index.html
|
||
│ └── src/
|
||
│ ├── main.ts
|
||
│ ├── api/
|
||
│ │ ├── session-api.ts
|
||
│ │ └── signaling.ts
|
||
│ ├── webrtc/
|
||
│ │ ├── transport.ts
|
||
│ │ └── ice.ts
|
||
│ ├── crypto/
|
||
│ │ └── crypto-client.ts
|
||
│ ├── transfer/
|
||
│ │ ├── file-transfer.ts
|
||
│ │ └── state.ts
|
||
│ └── ui/
|
||
│ ├── pages/
|
||
│ │ ├── home-page.ts
|
||
│ │ ├── send-page.ts
|
||
│ │ ├── join-page.ts
|
||
│ │ └── expired-page.ts
|
||
│ └── components/
|
||
│ ├── qr-display.ts
|
||
│ ├── file-picker.ts
|
||
│ ├── progress-bar.ts
|
||
│ └── status-indicator.ts
|
||
├── deploy/
|
||
│ ├── docker-compose.yml
|
||
│ ├── Caddyfile
|
||
│ ├── coturn.conf
|
||
│ └── filedrop.service
|
||
└── README.md
|
||
```
|
||
|
||
---
|
||
|
||
## 部署拓扑
|
||
|
||
最小部署:
|
||
- Caddy/Nginx
|
||
- axum app
|
||
- Redis
|
||
- coturn
|
||
|
||
域名建议:
|
||
- `app.example.com`:Web App + API + WS
|
||
- `turn.example.com`:TURN 服务
|
||
|
||
---
|
||
|
||
## 风险与注意事项
|
||
|
||
1. Safari 对 WebRTC/DataChannel、后台标签页较敏感
|
||
2. TURN 成本需监控,大文件应尽快切应用层密文中转
|
||
3. 浏览器内存占用,大文件必须按块处理
|
||
4. 断点续传复杂度不适合首版
|
||
5. 端到端加密需覆盖文件名和文件列表
|