9.2 KiB
9.2 KiB
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 | 从起始端口开始尝试 |
探测逻辑:
- 从
decide.port开始 - 尝试绑定端口
- 若失败,尝试下一个端口
- 最多尝试 10 次
- 全部失败则返回错误
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):
{
"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):
{
"error": "无法读取待定项数据",
"detail": "文件不存在或格式错误"
}
4.3 POST /api/submit
请求:
Content-Type: application/json
{
"decisions": [
{"id": 1, "chosen": "jwt"},
{"id": 2, "chosen": "bcrypt", "note": "团队更熟悉 bcrypt"}
]
}
响应:
成功(200):
{
"success": true,
"message": "决策已保存"
}
校验失败(400):
{
"error": "决策数据无效",
"detail": "缺少待定项 2 的决策"
}
服务器错误(500):
{
"error": "保存失败",
"detail": "无法写入文件"
}
提交后行为:
- 验证决策数据完整性(所有待定项都有决策)
- 保存决策结果到历史记录
- 设置关闭标志
- 返回成功响应
- 服务器在响应发送后关闭
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/目录提供 - 不允许
..路径