Files

495 lines
9.8 KiB
Markdown
Raw Permalink Normal View History

# 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 | 起始端口 |
| `decide.bind` | `"127.0.0.1"` | 监听地址,设为 `"0.0.0.0"` 可允许外部访问 |
| `decide.url` | `""` | 自定义访问地址,为空时自动生成 `http://localhost:{port}` |
| 最大尝试次数 | 10 | 从起始端口开始尝试 |
### 3.2 配置示例
```toml
[decide]
port = 3721
bind = "0.0.0.0" # 监听所有网络接口
url = "http://example.dev.net:3721" # 自定义访问地址
```
### 3.3 端口探测策略
**探测逻辑**
1.`decide.port` 开始
2. 尝试绑定到 `decide.bind:port`
3. 若失败,尝试下一个端口
4. 最多尝试 10 次
5. 全部失败则返回错误
### 3.4 访问地址生成
```
if decide.url 不为空:
access_url = decide.url
else:
access_url = f"http://localhost:{actual_port}"
```
### 3.5 端口占用检测
```
check_port_available(port: int) -> bool:
"""
检查端口是否可用
1. 创建 socket
2. 尝试绑定到 {bind}:{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)