✨ feat: 完成aide decide设计文档
This commit is contained in:
472
aide-program/docs/commands/decide/server.md
Normal file
472
aide-program/docs/commands/decide/server.md
Normal file
@@ -0,0 +1,472 @@
|
||||
# aide decide HTTP 服务设计
|
||||
|
||||
## 一、概述
|
||||
|
||||
aide decide 使用内置 HTTP 服务器提供 Web 界面,供用户确认待定项。本文档定义服务器的设计规格。
|
||||
|
||||
### 1.1 技术选型
|
||||
|
||||
- 使用 Python 标准库 `http.server`
|
||||
- 无需额外依赖
|
||||
- 单线程阻塞模式(简化实现,满足单用户场景)
|
||||
|
||||
### 1.2 服务职责
|
||||
|
||||
| 职责 | 说明 |
|
||||
|------|------|
|
||||
| 静态资源服务 | 提供 HTML/CSS/JS 文件 |
|
||||
| API 服务 | 提供数据获取和提交接口 |
|
||||
| 生命周期管理 | 启动、等待、关闭 |
|
||||
|
||||
## 二、服务生命周期
|
||||
|
||||
### 2.1 状态机
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
[*] --> 初始化 : 调用 start()
|
||||
初始化 --> 端口探测 : 配置加载完成
|
||||
端口探测 --> 启动失败 : 无可用端口
|
||||
端口探测 --> 运行中 : 找到可用端口
|
||||
启动失败 --> [*]
|
||||
运行中 --> 关闭中 : 收到提交/超时/中断
|
||||
关闭中 --> 已关闭
|
||||
已关闭 --> [*]
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 2.2 启动流程
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:加载配置;
|
||||
note right: decide.port, decide.timeout
|
||||
|
||||
:确定起始端口;
|
||||
if (配置了固定端口?) then (是)
|
||||
:使用配置端口;
|
||||
else (否)
|
||||
:使用默认端口 3721;
|
||||
endif
|
||||
|
||||
:端口探测循环;
|
||||
repeat
|
||||
:尝试绑定端口;
|
||||
if (绑定成功?) then (是)
|
||||
:记录实际端口;
|
||||
break
|
||||
else (否)
|
||||
:端口 += 1;
|
||||
endif
|
||||
repeat while (尝试次数 < 10?)
|
||||
|
||||
if (找到可用端口?) then (是)
|
||||
else (否)
|
||||
:返回错误;
|
||||
stop
|
||||
endif
|
||||
|
||||
:创建 HTTP 服务器;
|
||||
|
||||
:注册请求处理器;
|
||||
|
||||
:输出访问链接;
|
||||
|
||||
:进入服务循环;
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 2.3 关闭流程
|
||||
|
||||
服务在以下情况下关闭:
|
||||
|
||||
| 触发条件 | 处理方式 |
|
||||
|----------|----------|
|
||||
| 用户提交决策 | 正常关闭,返回成功 |
|
||||
| 超时(若配置) | 正常关闭,返回警告 |
|
||||
| 键盘中断(Ctrl+C) | 正常关闭,返回中断 |
|
||||
| 异常错误 | 异常关闭,返回错误 |
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:收到关闭信号;
|
||||
|
||||
:停止接受新请求;
|
||||
|
||||
:等待当前请求完成;
|
||||
note right: 最多等待 5 秒
|
||||
|
||||
:关闭 socket;
|
||||
|
||||
:清理资源;
|
||||
|
||||
:返回关闭原因;
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 三、端口配置
|
||||
|
||||
### 3.1 端口探测策略
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `decide.port` | 3721 | 起始端口 |
|
||||
| 最大尝试次数 | 10 | 从起始端口开始尝试 |
|
||||
|
||||
**探测逻辑**:
|
||||
|
||||
1. 从 `decide.port` 开始
|
||||
2. 尝试绑定端口
|
||||
3. 若失败,尝试下一个端口
|
||||
4. 最多尝试 10 次
|
||||
5. 全部失败则返回错误
|
||||
|
||||
### 3.2 端口占用检测
|
||||
|
||||
```
|
||||
check_port_available(port: int) -> bool:
|
||||
"""
|
||||
检查端口是否可用
|
||||
|
||||
1. 创建 socket
|
||||
2. 尝试绑定到 127.0.0.1:port
|
||||
3. 成功则端口可用,关闭 socket 返回 True
|
||||
4. 失败则端口被占用,返回 False
|
||||
"""
|
||||
```
|
||||
|
||||
## 四、API 端点设计
|
||||
|
||||
### 4.1 端点一览
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/` | 返回主页面(index.html) |
|
||||
| GET | `/style.css` | 返回样式文件 |
|
||||
| GET | `/app.js` | 返回脚本文件 |
|
||||
| GET | `/api/items` | 获取待定项数据 |
|
||||
| POST | `/api/submit` | 提交决策结果 |
|
||||
|
||||
### 4.2 GET /api/items
|
||||
|
||||
**请求**:无参数
|
||||
|
||||
**响应**:
|
||||
|
||||
成功(200):
|
||||
```json
|
||||
{
|
||||
"task": "实现用户认证模块",
|
||||
"source": "task-now.md",
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "认证方式选择",
|
||||
"context": "任务描述中未明确指定认证方式",
|
||||
"options": [
|
||||
{"value": "jwt", "label": "JWT Token 认证", "score": 85, "pros": [...], "cons": [...]},
|
||||
{"value": "session", "label": "Session 认证", "score": 70, "pros": [...], "cons": [...]}
|
||||
],
|
||||
"recommend": "jwt"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
失败(500):
|
||||
```json
|
||||
{
|
||||
"error": "无法读取待定项数据",
|
||||
"detail": "文件不存在或格式错误"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 POST /api/submit
|
||||
|
||||
**请求**:
|
||||
|
||||
Content-Type: `application/json`
|
||||
|
||||
```json
|
||||
{
|
||||
"decisions": [
|
||||
{"id": 1, "chosen": "jwt"},
|
||||
{"id": 2, "chosen": "bcrypt", "note": "团队更熟悉 bcrypt"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
成功(200):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "决策已保存"
|
||||
}
|
||||
```
|
||||
|
||||
校验失败(400):
|
||||
```json
|
||||
{
|
||||
"error": "决策数据无效",
|
||||
"detail": "缺少待定项 2 的决策"
|
||||
}
|
||||
```
|
||||
|
||||
服务器错误(500):
|
||||
```json
|
||||
{
|
||||
"error": "保存失败",
|
||||
"detail": "无法写入文件"
|
||||
}
|
||||
```
|
||||
|
||||
**提交后行为**:
|
||||
|
||||
1. 验证决策数据完整性(所有待定项都有决策)
|
||||
2. 保存决策结果到历史记录
|
||||
3. 设置关闭标志
|
||||
4. 返回成功响应
|
||||
5. 服务器在响应发送后关闭
|
||||
|
||||
### 4.4 静态资源服务
|
||||
|
||||
| 路径 | 文件 | Content-Type |
|
||||
|------|------|--------------|
|
||||
| `/` | `web/index.html` | `text/html; charset=utf-8` |
|
||||
| `/style.css` | `web/style.css` | `text/css; charset=utf-8` |
|
||||
| `/app.js` | `web/app.js` | `application/javascript; charset=utf-8` |
|
||||
|
||||
**资源加载方式**:
|
||||
|
||||
- 方案A:从文件系统读取(开发时便于修改)
|
||||
- 方案B:嵌入 Python 代码(部署时无需额外文件)
|
||||
|
||||
建议:使用方案A,资源文件放在 `aide/decide/web/` 目录下。
|
||||
|
||||
## 五、请求处理
|
||||
|
||||
### 5.1 请求处理流程
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
:接收 HTTP 请求;
|
||||
|
||||
:解析请求路径和方法;
|
||||
|
||||
switch (路径)
|
||||
case (/)
|
||||
:返回 index.html;
|
||||
case (/style.css)
|
||||
:返回 style.css;
|
||||
case (/app.js)
|
||||
:返回 app.js;
|
||||
case (/api/items)
|
||||
if (方法 == GET?) then (是)
|
||||
:读取 pending.json;
|
||||
:返回 JSON 数据;
|
||||
else (否)
|
||||
:返回 405 Method Not Allowed;
|
||||
endif
|
||||
case (/api/submit)
|
||||
if (方法 == POST?) then (是)
|
||||
:解析请求体;
|
||||
:验证决策数据;
|
||||
:保存决策结果;
|
||||
:设置关闭标志;
|
||||
:返回成功响应;
|
||||
else (否)
|
||||
:返回 405 Method Not Allowed;
|
||||
endif
|
||||
case (其他)
|
||||
:返回 404 Not Found;
|
||||
endswitch
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 5.2 CORS 处理
|
||||
|
||||
由于前端和后端在同一服务器,通常不需要 CORS。但为了开发调试方便,建议添加以下响应头:
|
||||
|
||||
```
|
||||
Access-Control-Allow-Origin: *
|
||||
Access-Control-Allow-Methods: GET, POST, OPTIONS
|
||||
Access-Control-Allow-Headers: Content-Type
|
||||
```
|
||||
|
||||
### 5.3 错误处理
|
||||
|
||||
| HTTP 状态码 | 场景 |
|
||||
|-------------|------|
|
||||
| 200 | 请求成功 |
|
||||
| 400 | 请求数据无效 |
|
||||
| 404 | 路径不存在 |
|
||||
| 405 | 方法不允许 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 六、超时处理
|
||||
|
||||
### 6.1 配置项
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `decide.timeout` | 0 | 超时时间(秒),0 表示无超时 |
|
||||
|
||||
### 6.2 超时实现
|
||||
|
||||
```
|
||||
@startuml
|
||||
skinparam defaultFontName "PingFang SC"
|
||||
|
||||
start
|
||||
|
||||
fork
|
||||
:服务循环;
|
||||
:处理请求;
|
||||
fork again
|
||||
:超时计时器;
|
||||
:等待 timeout 秒;
|
||||
:设置超时标志;
|
||||
end fork
|
||||
|
||||
if (超时?) then (是)
|
||||
:关闭服务;
|
||||
:输出警告;
|
||||
else (否)
|
||||
:正常关闭;
|
||||
endif
|
||||
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
**注意**:由于使用单线程模型,超时检测需要在请求处理间隙进行,或使用 `select` 实现非阻塞等待。
|
||||
|
||||
## 七、方法签名原型
|
||||
|
||||
```
|
||||
class DecideServer:
|
||||
"""HTTP 服务器"""
|
||||
|
||||
root: Path # 项目根目录
|
||||
port: int # 实际使用的端口
|
||||
timeout: int # 超时时间(秒)
|
||||
pending_path: Path # pending.json 路径
|
||||
web_dir: Path # 静态资源目录
|
||||
should_close: bool # 关闭标志
|
||||
close_reason: str # 关闭原因
|
||||
|
||||
def __init__(self, root: Path) -> None:
|
||||
"""初始化服务器"""
|
||||
|
||||
def start(self) -> bool:
|
||||
"""
|
||||
启动服务器
|
||||
|
||||
1. 加载配置
|
||||
2. 探测可用端口
|
||||
3. 创建 HTTP 服务器
|
||||
4. 输出访问链接
|
||||
5. 进入服务循环
|
||||
6. 返回是否成功完成
|
||||
"""
|
||||
|
||||
def stop(self, reason: str) -> None:
|
||||
"""
|
||||
停止服务器
|
||||
|
||||
1. 设置关闭标志和原因
|
||||
2. 关闭 socket
|
||||
"""
|
||||
|
||||
def _find_available_port(self) -> int | None:
|
||||
"""
|
||||
探测可用端口
|
||||
|
||||
从配置端口开始,最多尝试 10 次
|
||||
返回可用端口或 None
|
||||
"""
|
||||
|
||||
def _serve_forever(self) -> None:
|
||||
"""
|
||||
服务循环
|
||||
|
||||
处理请求直到 should_close 为 True
|
||||
"""
|
||||
|
||||
|
||||
class DecideHandler:
|
||||
"""请求处理器"""
|
||||
|
||||
server: DecideServer # 服务器引用
|
||||
|
||||
def handle_request(self, method: str, path: str, body: bytes) -> Response:
|
||||
"""
|
||||
处理请求
|
||||
|
||||
根据 method 和 path 分发到具体处理函数
|
||||
"""
|
||||
|
||||
def handle_index(self) -> Response:
|
||||
"""返回主页面"""
|
||||
|
||||
def handle_static(self, filename: str) -> Response:
|
||||
"""返回静态资源"""
|
||||
|
||||
def handle_get_items(self) -> Response:
|
||||
"""处理 GET /api/items"""
|
||||
|
||||
def handle_submit(self, body: bytes) -> Response:
|
||||
"""处理 POST /api/submit"""
|
||||
|
||||
|
||||
Response = tuple[int, dict[str, str], bytes]
|
||||
# (状态码, 响应头, 响应体)
|
||||
```
|
||||
|
||||
## 八、安全考虑
|
||||
|
||||
### 8.1 绑定地址
|
||||
|
||||
- 默认绑定到 `127.0.0.1`(仅本地访问)
|
||||
- 不绑定到 `0.0.0.0`(避免外部访问)
|
||||
|
||||
### 8.2 输入验证
|
||||
|
||||
- 验证 JSON 格式
|
||||
- 验证决策数据完整性
|
||||
- 限制请求体大小(建议 1MB)
|
||||
|
||||
### 8.3 路径遍历防护
|
||||
|
||||
- 静态资源只从 `web/` 目录提供
|
||||
- 不允许 `..` 路径
|
||||
|
||||
## 九、相关文档
|
||||
|
||||
- [decide 详细设计入口](README.md)
|
||||
- [CLI 规格](cli.md)
|
||||
- [Web 前端设计](web.md)
|
||||
- [数据存储设计](storage.md)
|
||||
Reference in New Issue
Block a user