## Purpose 定义 HTTP 类型拨测目标:通过 `type: http` 配置 HTTP/HTTPS 探测,支持请求方法、headers、body、SSL 校验、重定向和响应体大小限制,捕获 HTTP 状态码、响应头、响应体和执行耗时,按 expect 规则校验并生成 matched 判定。 ## Requirements ### Requirement: HTTP target 配置 系统 SHALL 支持 `type: http` 的 target 配置,通过 `http.url` 描述目标 HTTP 地址,并通过可选字段控制请求方法、请求体、headers、SSL 校验、重定向和响应体大小限制。 #### Scenario: 解析最简 HTTP target - **WHEN** YAML 中 target 配置 `type: http` 和 `http.url: "https://example.com"` - **THEN** 系统 SHALL 将其解析为 HTTP checker,并填充 `method=GET`、`maxBodyBytes=100MB`、`ignoreSSL=false`、`maxRedirects=0`、headers、body、interval、timeout、group 和 expect 配置 #### Scenario: HTTP target 缺少 url - **WHEN** YAML 中 target 配置 `type: http` 但缺少 `http.url` - **THEN** 系统 SHALL 以配置错误退出,并提示该 target 缺少 http.url 字段 #### Scenario: HTTP target 覆盖请求方法 - **WHEN** YAML 中某个 HTTP target 显式配置 `http.method: POST` - **THEN** 该 target SHALL 使用自身 method 字段的值,而不是内置默认值 GET #### Scenario: HTTP target 覆盖响应体大小限制 - **WHEN** YAML 中某个 HTTP target 显式配置 `http.maxBodyBytes` - **THEN** 该 target SHALL 使用自身 maxBodyBytes 字段的值,而不是内置默认值 100MB #### Scenario: HTTP target 覆盖 headers - **WHEN** YAML 中某个 HTTP target 显式配置 `http.headers` - **THEN** 该 target SHALL 使用自身 headers 字段的值 #### Scenario: HTTP target 配置 ignoreSSL - **WHEN** YAML 中 HTTP target 设置 `http.ignoreSSL: true` - **THEN** 系统 SHALL 解析该字段并在执行时跳过 SSL 证书校验 #### Scenario: HTTP target 配置 maxRedirects - **WHEN** YAML 中 HTTP target 设置 `http.maxRedirects: 5` - **THEN** 系统 SHALL 解析该字段并在执行时允许最多跟随 5 次重定向 #### Scenario: HTTP 序列化展示摘要 - **WHEN** 系统同步 HTTP target 到 targets 表 - **THEN** `target` 展示摘要 SHALL 为 HTTP URL,`config` JSON SHALL 包含 resolved 后的 url、method、headers、body、ignoreSSL、maxBodyBytes 和 maxRedirects ### Requirement: HTTP checker 执行 系统 SHALL 按 HTTP target 配置发起 HTTP 请求,使用引擎注入的 `ctx.signal` 响应超时。系统 SHALL 支持手动重定向跟随(`redirect: "manual"`),在 `maxRedirects` 限制内跟随 301/302/303/307/308 重定向。系统 SHALL 记录完整执行耗时和 HTTP observation,并在网络错误、超时、响应体超限或字符编码不支持时产生结构化失败信息。 #### Scenario: HTTP 请求成功 - **WHEN** HTTP target 指向可正常响应的 HTTP 服务,且未配置 expect 或 `expect.status` 为 `[200]` - **THEN** 系统 SHALL 记录 `matched=true`、`durationMs` 和包含 statusCode、headers、bodyPreview、contentType、contentLength 的 observation #### Scenario: 使用 ctx.signal 响应超时 - **WHEN** 引擎注入的 `ctx.signal` 在 HTTP 请求过程中 abort - **THEN** 系统 SHALL 记录 `matched=false`,observation SHALL 为 null,failure 的 kind 为 `error`,phase 为 `request`,message 包含超时信息 #### Scenario: 网络错误 - **WHEN** HTTP 请求因网络错误(DNS 解析失败、连接被拒绝等)失败 - **THEN** 系统 SHALL 记录 `matched=false`,observation SHALL 为 null,failure 的 kind 为 `error`,phase 为 `request` #### Scenario: 重定向跟随 - **WHEN** HTTP target 配置 `http.maxRedirects: 3` 且服务端返回 301/302/303/307/308 重定向 - **THEN** 系统 SHALL 在 maxRedirects 限制内自动跟随重定向,并返回最终响应供 expect 校验 #### Scenario: 重定向次数耗尽 - **WHEN** HTTP target 配置 `http.maxRedirects: 1` 且服务端返回超过 1 次重定向 - **THEN** 系统 SHALL 将最后一次重定向响应作为最终结果返回,不继续跟随 #### Scenario: POST 重定向为 GET - **WHEN** HTTP target 使用 POST 方法且收到 301 或 302 重定向,或收到 303 重定向 - **THEN** 系统 SHALL 将后续请求方法改为 GET,移除 Content-Type 和 Content-Length 请求头,并移除 body #### Scenario: 跨域重定向移除敏感头 - **WHEN** 重定向到不同 origin 的目标 - **THEN** 系统 SHALL 移除 Authorization 和 Cookie 请求头 #### Scenario: 响应体读取与大小限制 - **WHEN** HTTP target 配置了 body expect 且响应体未超过 `maxBodyBytes` - **THEN** 系统 SHALL 读取完整响应体,按 Content-Type 字符编码解码,并用于 body expect 校验 #### Scenario: 响应体超过大小限制 - **WHEN** HTTP target 配置了 body expect 且响应体超过 `maxBodyBytes` - **THEN** 系统 SHALL 停止读取,记录 `matched=false`,failure 的 kind 为 `error`,phase 为 `body`,message 包含响应体超过大小限制的信息 #### Scenario: 不支持的字符编码 - **WHEN** HTTP 响应的 Content-Type 指定了不支持的 charset - **THEN** 系统 SHALL 记录 `matched=false`,failure 的 kind 为 `error`,phase 为 `body`,message 包含不支持的字符编码信息 #### Scenario: 无 body expect 时不读取响应体 - **WHEN** HTTP target 未配置 `expect.body` 或 body expect 为空数组 - **THEN** 系统 SHALL NOT 读取响应体内容,bodyPreview SHALL 为 null #### Scenario: GET/HEAD 请求不发送 body - **WHEN** HTTP target 配置 `http.method: GET` 或 `http.method: HEAD` - **THEN** 系统 SHALL NOT 发送请求体,即使配置了 `http.body` ### Requirement: HTTP expect 校验 系统 SHALL 支持 HTTP 专属 expect,包括 `status`、`headers`、`body` 和 `durationMs`,并按 status、headers、early-timeout(仅当配置了 body expect 时)、body、durationMs 的阶段顺序快速失败。`status` SHALL 保持状态码数组语义,支持精确数字(100-599)和范围模式(`1xx` 到 `5xx`),未配置时在 Resolved expect 中默认 `[200]`。`headers` SHALL 使用共享 `RawKeyedExpectations` 输入并在运行期使用 `KeyedExpectations`,header key 大小写不敏感。`body` MUST 使用共享 `RawContentExpectations` 数组输入并在运行期使用 `ContentExpectations`,支持直接 ValueMatcher 以及 json/css/xpath 提取器。`durationMs` SHALL 使用共享 `RawValueExpectation` 输入并在运行期使用 `ValueExpectation` 校验完整执行耗时。 #### Scenario: 默认 status 成功语义 - **WHEN** HTTP target 未显式配置 `expect.status` - **THEN** 系统 SHALL 在 Resolved HTTP expect 中使用默认 `status: [200]` 进行校验 #### Scenario: status 精确值匹配 - **WHEN** HTTP target 配置 `expect.status: [200, 201]` 且响应状态码为 201 - **THEN** 系统 SHALL 判定 status 阶段通过,继续后续 expect 阶段 #### Scenario: status 范围模式匹配 - **WHEN** HTTP target 配置 `expect.status: ["2xx"]` 且响应状态码为 204 - **THEN** 系统 SHALL 判定 status 阶段通过 #### Scenario: status 不匹配快速失败 - **WHEN** HTTP target 配置 `expect.status: [200]` 且响应状态码为 500 - **THEN** 系统 SHALL 立即返回 `matched=false`,failure 的 phase 为 `status` #### Scenario: headers 校验通过 - **WHEN** HTTP target 配置 `expect.headers: {Content-Type: {contains: "application/json"}}` 且响应包含对应 header - **THEN** 系统 SHALL 判定 headers 阶段通过,header key 匹配大小写不敏感 #### Scenario: headers 校验失败 - **WHEN** HTTP target 配置 `expect.headers: {X-Custom: {exists: true}}` 且响应不包含该 header - **THEN** 系统 SHALL 返回 `matched=false`,failure 的 phase 为 `headers` #### Scenario: body ContentExpectations 校验通过 - **WHEN** HTTP target 配置 `expect.body: [{contains: "ok"}, {json: {path: "$.status", equals: "success"}}]` 且响应体满足全部条件 - **THEN** 系统 SHALL 判定 body 阶段通过 #### Scenario: body ContentExpectations 快速失败 - **WHEN** HTTP target 配置两条 body expectation 且第一条失败 - **THEN** 系统 SHALL 返回第一条失败 expectation 的 failure,phase 为 `body`,并 MUST NOT 执行第二条 expectation #### Scenario: body 非 JSON 响应触发 JSONPath 失败 - **WHEN** HTTP target 配置 json body expectation 但响应体不是合法 JSON - **THEN** 系统 SHALL 记录 `matched=false`,failure 的 kind 为 `error`,phase 为 `body` #### Scenario: early-timeout 快速失败 - **WHEN** HTTP target 配置了 body expect 和 `expect.durationMs: {lte: 500}`,且请求状态码和 headers 通过后已耗时超过 500ms - **THEN** 系统 SHALL 在读取响应体之前判定 durationMs 超限,不读取响应体,直接返回 `matched=false`,failure 的 phase 为 `duration` #### Scenario: durationMs 校验 - **WHEN** HTTP target 配置 `expect.durationMs: {lte: 1000}` 且完整执行耗时超过 1000ms - **THEN** 系统 SHALL 返回 `matched=false`,failure 的 phase 为 `duration` ### Requirement: HTTP checker 启动期配置校验 系统 SHALL 在启动期对 HTTP checker 的配置契约和语义执行严格校验。HTTP target 的 `http` 分组 SHALL 只允许 `url`、`method`、`headers`、`body`、`ignoreSSL`、`maxBodyBytes`、`maxRedirects` 字段。HTTP expect SHALL 只允许 `status`、`headers`、`body`、`durationMs` 字段。未知字段、非法类型、非法 URL 协议、非法 status 值、非法 maxBodyBytes、非法 ContentExpectations 和不可编译正则 MUST 导致启动期配置错误。语义校验 MUST NOT 修改 Raw HTTP expect 输入。 #### Scenario: http.url 为空字符串 - **WHEN** YAML 中 HTTP target 配置 `http.url: ""` - **THEN** 系统 SHALL 以配置错误退出,提示缺少 http.url 字段 #### Scenario: http.url 协议非法 - **WHEN** YAML 中 HTTP target 的 `http.url` 不以 `http://` 或 `https://` 开头 - **THEN** 系统 SHALL 以配置错误退出,提示 URL 格式不合法 #### Scenario: http.url 格式非法 - **WHEN** YAML 中 HTTP target 的 `http.url` 不是合法 URL - **THEN** 系统 SHALL 以配置错误退出,提示 URL 格式不合法 #### Scenario: http.maxBodyBytes 格式非法 - **WHEN** YAML 中 HTTP target 的 `http.maxBodyBytes` 不是合法 size 值 - **THEN** 系统 SHALL 以配置错误退出,提示 maxBodyBytes 格式错误 #### Scenario: http 分组未知字段失败 - **WHEN** YAML 中 HTTP target 的 `http` 分组包含未知字段 - **THEN** 系统 SHALL 以配置错误退出,提示 http 分组包含未知字段 #### Scenario: expect.status 数字非法 - **WHEN** YAML 中 HTTP target 的 `expect.status` 包含非整数或不在 100-599 范围内的数字 - **THEN** 系统 SHALL 以配置错误退出,提示 status 数字不合法 #### Scenario: expect.status 模式非法 - **WHEN** YAML 中 HTTP target 的 `expect.status` 包含不符合 `1xx` 到 `5xx` 格式的字符串 - **THEN** 系统 SHALL 以配置错误退出,提示 status 模式不合法 #### Scenario: expect.body 必须为数组 - **WHEN** YAML 中 HTTP target 的 `expect.body` 已配置但不是数组 - **THEN** 系统 SHALL 以配置错误退出,提示 expect.body 必须为数组 #### Scenario: HTTP expect 未知字段失败 - **WHEN** YAML 中 HTTP target 的 expect 包含 `connected`、`exitCode`、`maxDurationMs` 或其他非 HTTP expect 字段 - **THEN** 系统 SHALL 以配置错误退出,提示 expect 包含未知字段 ### Requirement: HTTP observation 与 detail HTTP checker SHALL 在 observation 中记录 statusCode(number)、headers(Record,截断至前 20 个)、bodyPreview(string | null,截断至前 1024 字符)、contentType(string | null)、contentLength(number | null)。API detail SHALL 由 `buildDetail` 从 observation 动态构造,格式为 `HTTP {statusCode}`。网络错误或超时导致无法收集 observation 时,observation SHALL 为 null,detail SHALL 为 null。 #### Scenario: 正常响应 observation - **WHEN** HTTP 请求成功返回 - **THEN** observation SHALL 包含 statusCode、截断后的 headers、截断后的 bodyPreview、contentType 和 contentLength #### Scenario: 未读取响应体时 bodyPreview 为 null - **WHEN** HTTP target 未配置 body expect - **THEN** observation.bodyPreview SHALL 为 null #### Scenario: 请求失败 observation 为 null - **WHEN** HTTP 请求因网络错误或超时失败 - **THEN** observation SHALL 为 null,detail SHALL 为 null #### Scenario: detail 格式 - **WHEN** API 序列化 HTTP CheckResult - **THEN** detail SHALL 为 `HTTP {statusCode}` 格式(如 `HTTP 200`)