- frontend-architecture-refactor: 拆分 hooks/组件、类型筛选器动态化 - http-checker-quality-hardening: ReDoS 防护、failure 格式修正、测试补全
94 lines
5.6 KiB
Markdown
94 lines
5.6 KiB
Markdown
## Context
|
||
|
||
HTTP checker 是 DiAL 拨测系统的核心 runner 之一,负责对 HTTP 目标执行请求并校验响应。经审查发现以下质量问题:
|
||
|
||
1. **actual 值截断格式不符合 spec**:spec 要求 failure 中的 actual 摘要需截断并附带字符计数,但当前 `truncateActual` 函数只加省略号无计数,导致用户无法判断原始响应体规模。
|
||
2. **ReDoS 风险**:用户配置的 regex body 规则和 match operator 直接对大响应体执行 `new RegExp().test()`,恶意或不当正则可能导致 CPU 阻塞。
|
||
3. **JSON 重复解析**:多条 json body 规则各自独立调用 `JSON.parse(body)`,对大 JSON 响应体造成不必要的重复开销。
|
||
4. **CSS 规则分支冗余**:`checkCssRule` 中"无 operator 时检查元素存在"和"exists: true"是重复逻辑。
|
||
5. **重定向测试不足**:303、307/308、相对路径 Location 等分支缺少测试覆盖。
|
||
|
||
当前代码结构:
|
||
- `src/server/checker/expect/failure.ts` — failure 构造函数
|
||
- `src/server/checker/runner/http/body.ts` — body 规则检查
|
||
- `src/server/checker/runner/http/execute.ts` — HTTP 执行主流程
|
||
- `src/server/checker/expect/operator.ts` — operator 匹配逻辑
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
- 实现 failure actual 值截断,满足 spec 要求
|
||
- 消除 regex 相关的 ReDoS 风险
|
||
- 优化多条 JSON 规则的解析性能
|
||
- 精简冗余代码分支
|
||
- 补全重定向和集成测试覆盖
|
||
|
||
**Non-Goals:**
|
||
- 不改变 CheckResult / CheckFailure 的类型结构(截断在构造时完成,对外接口不变)
|
||
- 不引入新依赖
|
||
- 不改变 HTTP checker 的功能行为(纯内部质量改进)
|
||
- 不添加 response timing 分段记录(暂缓)
|
||
- 不添加重试机制(拨测场景下重试会掩盖网络问题信号)
|
||
|
||
## Decisions
|
||
|
||
### Decision 1: actual 截断在 mismatchFailure 构造点统一实施
|
||
|
||
**选择**:在 `expect/failure.ts` 的 `mismatchFailure` 函数内部对 actual 参数截断,阈值 200 字符。
|
||
|
||
**替代方案**:
|
||
- 在存储层(store.ts insertCheckResult)截断 — 但这样 API 实时返回的 failure 仍然很大
|
||
- 在每个调用点手动截断 — 分散且容易遗漏
|
||
|
||
**理由**:构造点截断是最集中的拦截位置,所有 mismatch failure 都经过此函数,一处修改全局生效。expected 值不截断(来自用户配置,通常很短)。
|
||
|
||
**截断格式**:`<前 200 字符>…(共 N 字符)` — 保留前缀便于诊断,附带总长度便于判断规模(省略号为单字符 U+2026)。
|
||
|
||
### Decision 2: ReDoS 防护使用正则复杂度静态检测
|
||
|
||
**选择**:在启动期 validate 阶段对 regex body 规则和 match operator 进行静态复杂度检测,拒绝含有嵌套量词等危险模式的正则。运行期不做额外防护。
|
||
|
||
**替代方案**:
|
||
- 运行期用 AbortSignal + setTimeout 强制中断 — Bun 的 RegExp 执行不可中断,无法实现
|
||
- 使用 safe-regex 库 — 引入新依赖,违反项目规范
|
||
- 限制正则执行的输入长度 — 会影响正常大响应体的匹配
|
||
|
||
**理由**:自行实现轻量级检测函数,检查常见 ReDoS 模式(嵌套量词 `(a+)+`、重叠交替 `(a|a)*`)。在 validate 阶段拒绝危险正则,比运行期防护更可靠——配置错误应该在启动时暴露。
|
||
|
||
**检测规则**:
|
||
- 嵌套量词:量词内包含量词(如 `(a+)+`、`(a*)*`、`(a+)*`)
|
||
- 重叠字符类交替后跟量词:`(x|x)+` 模式
|
||
|
||
### Decision 3: JSON parse 结果缓存在 checkBodyExpect 层
|
||
|
||
**选择**:在 `checkBodyExpect` 函数中,首次遇到 json 规则时执行 `JSON.parse`,将结果缓存并传递给后续 json 规则复用。
|
||
|
||
**实现方式**:修改 `checkSingleBodyRule` 签名,接受可选的 `parsedJson` 参数;在 `checkBodyExpect` 循环中维护一个 `let parsedJson: { ok: boolean; value?: unknown; error?: string }` 状态。
|
||
|
||
**理由**:改动最小,不改变外部接口,只在内部传递缓存。对于非 json 规则(contains、regex、css、xpath)无影响。
|
||
|
||
### Decision 4: CSS 规则分支合并策略
|
||
|
||
**选择**:将 `checkCssRule` 重构为线性流程:
|
||
1. 解析 HTML
|
||
2. 处理 `exists: false`(元素不存在即通过)
|
||
3. 查找元素(不存在则失败)
|
||
4. 处理 `exists: true`(到这里已确认存在,直接通过)
|
||
5. 提取值(attr 或 text)
|
||
6. 无 operator 时检查值非 undefined 即通过
|
||
7. 有 operator 时执行匹配
|
||
|
||
**理由**:消除当前三层嵌套判断中的重复逻辑,使控制流线性化,更易理解和维护。
|
||
|
||
### Decision 5: execute.ts 提前 duration 检查保留但加注释
|
||
|
||
**选择**:保留第 56-74 行的提前 duration 检查逻辑(它是有效的性能优化——避免读取注定超时的 body),但重构为独立的 helper 函数使意图更明确。
|
||
|
||
**理由**:删除它会导致超时场景下仍然读取完整 body 后才报错,浪费网络带宽和时间。提取为 `checkEarlyTimeout` 函数名即可自解释。
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- **ReDoS 静态检测的误报**:过于严格的检测可能拒绝合法但看起来复杂的正则。→ 缓解:只检测最常见的嵌套量词模式,不做过度分析;提供清晰的错误信息指导用户修改。
|
||
- **actual 截断丢失诊断信息**:截断后用户无法看到完整 actual 值。→ 缓解:200 字符的前缀通常足够定位问题;如需完整响应体,用户应直接请求目标 URL 查看。
|
||
- **JSON parse 缓存的内存占用**:对于大 JSON 响应体,缓存的 parsed 对象会在整个 body rules 检查期间驻留内存。→ 缓解:这是短暂的(单次检查周期内),且原本每条规则都会各自 parse 一份,缓存反而减少了峰值内存。
|