浏览器显示「打不开」,可能是名字没有解析出来,也可能是连接已经建立、服务器却回答「没有这个资源」。S18 的 DNS 帮我们找到地址,S17 的 TCP 帮我们建立可靠的字节传输;本课继续追问:送过去的字节表达什么请求,收回来的字节又代表什么结果?沿用 S13 的分层探测方法,我们把「可达」进一步拆成「可以连接」「可以安全通信」「HTTP 回答符合预期」。
本课以 TCP 上的 HTTP/1.1 为主。文中的 app.example 和 198.51.100.20 是教学用域名与文档地址,命令输出是同一组示例环境的记录,不能把它们当作公网测试服务。实际操作时应换成有权测试的域名、地址和路径。命令中的时间与状态都是用来说明证据如何衔接的具体样本,不代表所有服务都会给出同样结果。
1 HTTP 在协议栈里回答什么问题
TCP 关心字节能否按序到达,HTTP 关心这些字节在应用层表达什么。客户端可以请求读取某个页面,也可以提交表单;服务端用状态、字段和可选的正文回答。即使 TCP 完整传输了 900 字节,这 900 字节也可能是一张错误页。要判断服务是否可用,必须读懂 HTTP 层的意思。
在本课的模型中,1 次 HTTP 交互由请求和响应组成。客户端先说明要做什么、访问什么目标,并附上必要信息;服务端再说明处理结果,按需返回内容。请求可以没有正文,响应也不一定带正文。因此,不能靠「返回了多少字节」判断是否成功,也不能因为页面看起来像报错,就直接认定服务器没有收到请求。
URL 把若干信息放在同一处。例如 https://app.example:443/api/items?id=17 中,https 指定使用 HTTPS,app.example 指定主机名,443 是端口,/api/items 是路径,id=17 是查询参数。路径和查询参数参与表达访问目标;它们不要求服务器磁盘上存在同名文件,应用可以用程序生成响应。URL 末尾若有 #details 这样的片段,浏览器通常在本地使用它,不把片段作为 HTTP 请求目标发送。
把这个例子拆开看:DNS 为主机名返回地址,TCP 连接目标地址的 443 端口,TLS 为 HTTPS 建立安全通信条件,随后才传递 HTTP 请求。若 DNS 返回了错误地址,HTTP 的方法写得再正确也无济于事;若 TCP 连接超时,还没有可供分析的 HTTP 状态。反过来,已经收到 404 就说明本次交互至少到达了能够返回 HTTP 响应的某个组件,可以继续查它是否是预期组件。

例如,读取商品 17 的请求得到 200 和 640 字节 JSON,可以作为接口验收的候选证据;同样的连接得到 404 和 640 字节 HTML,则不能因为大小相同就判为通过。我们要同时记录目标、响应状态和内容含义。HTTP 补充了 DNS、TCP 之后的观察维度,也要求保留此前的分层证据,避免每遇到错误页都重新从「网络断了」开始猜。
还要区分连接与报文的边界。HTTP/1.1 可以在同一条 TCP 连接上先后完成多个请求,收到一次响应不必断开连接。TCP 提供连续字节流,某次读取拿到的字节可能只有半行字段,也可能包含字段和部分正文;不能把一次读取或一个 TCP 包当成完整 HTTP 响应。客户端需要按 HTTP 规则识别字段结束位置,再判断正文是否收齐。
例如响应声明 Content-Length: 640,客户端收到状态 200 后只读到 300 个正文字节,连接便被关闭。这时状态行虽然完整,正文传输却未完成,不能将截断的 JSON 当作成功结果。若响应采用 Transfer-Encoding: chunked,则按分块长度读取,直到结束块及其后的报文结束边界,不能靠短暂没有数据判断结束。相反,204 响应按协议没有正文,不能因为内容为空就判为丢包。排查时应同时确认状态、正文边界和实际接收结果;这也解释了为何日志已有 200,客户端仍可能报告传输失败。
2 请求与响应:方法、路径、状态行
HTTP/1.1 请求的开头是请求行。下面的例子表示:在指定站点上读取编号为 17 的商品。
GET /api/items/17 HTTP/1.1
Host: app.example
Accept: application/json
请求行的 3 个部分分别是方法 GET、请求目标 /api/items/17 和版本 HTTP/1.1。普通直连场景中,请求目标主要是路径及可选查询参数,主机名由 Host 字段表达。后面每行字段补充请求条件,字段结束后用空行分隔正文。这个 GET 示例没有正文;空行依然有意义,它告诉接收方字段已经结束。
GET 的基本直觉是读取资源,POST 的基本直觉是向资源提交数据。比如读取订单 17 可用 GET /orders/17,提交新订单可用 POST /orders,具体接口契约由服务定义。不要仅凭路径中出现 create 就判断方法,也不要以为 POST 的正文天然保密;明文 HTTP 中,方法、字段和正文都不因使用 POST 而自动获得加密保护。
POST /orders HTTP/1.1
Host: app.example
Content-Type: application/json
Content-Length: 11
{"item":17}
这里正文是 11 个 ASCII 字节,Content-Length: 11 描述字节长度,不能把字符数普遍当作字节数。示例 JSON 的值 17 是商品编号,不是状态码;看协议时要把数据所在位置与含义对应起来。真实客户端通常负责生成正确的长度和报文边界,排查时无需手工拼装这些内容,但应理解字段和正文为何分开。
响应以状态行开头,例如:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 23
{"id":17,"stock":"yes"}
状态行包含版本、3 位状态码和便于人阅读的原因短语。判断协议结果主要看 200,不能只匹配英文 OK。这个响应正文有 23 个 ASCII 字节,表示商品存在且有库存;它并不自动证明下单、扣款等后续动作也成功。若验收目标只是「商品查询可用」,则还应检查字段是否包含目标编号 17,而不是接受任意一个 200 页面。
幂等性的谨慎理解是:按协议语义,重复执行相同请求的预期服务端效果与执行 1 次相同;GET 应具有这种性质,POST 则不能据此假定可安全重复提交。它不承诺每次返回的正文完全相同,也不意味着请求没有日志记录。对于支付或创建订单,超时后是否重试应服从接口约定,不能因为没看到响应就推断服务端没有执行。
3 关键字段:Host、Content-Type 与为何重要
同一个 IP 地址可以承载多个站点。假设 app.example 和 docs.example 都解析到 198.51.100.20,并使用 80 端口。TCP 只看到连接的地址与端口,无法从这 2 个值分辨用户想看应用还是文档;HTTP/1.1 的 Host 把站点身份带进请求,使服务器能够选择相应的虚拟主机。对 HTTP/1.1 请求来说,Host 是必需字段,非默认端口也应包含在对应的主机信息中。
GET / HTTP/1.1
Host: app.example
这个请求可能得到应用首页;将 Host 改为 docs.example,在同一个地址上就可能得到文档首页。直接请求 http://198.51.100.20/ 时,客户端通常发送 IP 形式的 Host,服务器可能把它交给默认站点。默认站点返回 404,并不能推翻「应用域名首页返回 200」这一结果,因为 2 次请求选择的站点不同。

HTTP 的 Host 与 HTTPS 的证书校验还要区分:前者影响 HTTP 请求交给哪个站点,后者在发送该请求之前检查 TLS 连接身份。HTTPS 中还常用 TLS 的服务器名称指示来选择站点证书,只修改 HTTP Host 不一定能得到预期的 TLS 行为。后面使用 --resolve 时,我们会保留 URL 中的主机名,同时固定连接地址,避免把这些条件混在一起改变。
Content-Type 描述当前报文正文的媒体类型。响应中的 application/json 表示正文按 JSON 解释,text/html 表示 HTML;请求中的该字段则告诉服务端如何理解客户端提交的正文。它不直接描述 URL 后缀,也不是「这次请求一定成功」的保证。一个名为 /api/items/17 的接口,可能在异常时返回 HTML 错误页。
例如客户端预期 200 和 JSON,实际收到 200、Content-Type: text/html 以及登录页,这说明仅检查状态的探针可能误判。另一个请求把 JSON 正文标为 text/plain,服务端可能拒绝解析,返回 400 或 415,具体取决于接口约定。这时应检查提交格式与字段是否一致,而不是先改 DNS。请求中的 Accept: application/json 表达希望收到的类型,与声明已发送正文类型的 Content-Type 作用不同。
排查时至少将「主机名、路径、方法、正文类型」作为同一条请求的条件保存。只给同事发一个 IP,或者只截取 200,不足以复现上述差别。若 2 次响应不同,先比较请求条件是否真的相同,再讨论服务是否不稳定。
4 状态码家族:2xx、3xx、4xx、5xx 怎么读
先读首位,再定位具体代码,比背诵状态码清单更能帮助排查。2xx 表示请求在 HTTP 语义上被成功处理;3xx 指示重定向等进一步处理;4xx 表示请求条件等客户端一侧的问题;5xx 表示服务端处理失败。这些家族缩小了下一步范围,但不能直接替代业务验收或责任归属判断。
200 OK 常见于成功读取资源。假设查询商品 17 返回 200,而 JSON 中的 id 是 18,HTTP 成功仍不满足本次验收。又如某接口约定在 200 的正文中提供业务结果码,调用方就必须继续读取该字段。状态码回答协议层结果,业务契约决定还需要验证哪些内容,不能把两者压缩成单一绿灯。
301 Moved Permanently 表示资源的永久重定向,通常通过 Location 字段给出新目标。例如 /old 返回 Location: /new,客户端若跟随,就再请求 /new。首个响应是 301,随后可能是 200,这已经是 2 次请求。若工具只显示最后的页面,会隐藏中间的跳转;排查地址变化时应保留每个状态和 Location,而非只看最后一行。
404 Not Found 表示响应方没有找到对应资源,或者不愿公开它是否存在。它常见于路径写错、路由未部署、选中了默认站点等情形。收到 404 的同时,DNS、TCP 和 HTTP 响应都可能完全正常。因此「404 是客户端错误家族」不等于「用户应该独自负责」,发布遗漏或网关路径改写错误同样可能制造 404。
502 Bad Gateway 通常来自扮演网关或代理的组件,表示它没有从上游获得有效响应。假设客户端连接网关的 443 端口成功,网关连接应用的 8080 端口却失败,就可能向客户端返回 502。客户端到网关和网关到应用是 2 段独立交互;前段可连接不能证明后段正常,502 也不能只凭代码确定是哪个上游或哪种故障。

用 4 个数练习分类:200 先核对正文;301 先核对 Location;404 先核对 Host、路径和方法;502 先确认响应网关及其上游证据。首位帮我们选择检查方向,后 2 位和字段、正文、日志一起帮助收敛。1xx 是信息性响应,本课不展开;读到它时应继续寻找本次交互的最终响应。
5 HTTPS 边界:TCP 已连接之后还可能失败在哪里
http:// 和 https:// 的差别涉及连接建立后的通信方式。本课的 HTTPS 路径是先建立 TCP,再进行 TLS 握手,随后通过受保护的连接交换 HTTP 报文。TCP 显示 Connected,仅说明该连接已建立;如果 TLS 失败,客户端通常还没有发送正常的应用 HTTP 请求,也就没有本次请求的 HTTP 状态可读。
假设访问 https://app.example/api,TCP 在 30 毫秒建立,随后证书校验报告名称不匹配,整个命令在 45 毫秒失败。这 45 毫秒不是「接口处理了 45 毫秒后报错」,其中根本没有进入我们预期的 HTTP 请求阶段。正确记录应是「TCP 成功,TLS 名称校验失败,未取得 HTTP 响应」,下一步检查 URL 主机名、证书覆盖的名称及入口站点配置。

证书名称不匹配、证书时间有效性问题、未知或不受信任的签发机构,都是 TLS 阶段可能出现的证据。名称错误可发生在把域名 URL 改成 IP URL 后;时间问题可能来自证书过期,也可能来自客户端时钟明显错误;未知 CA 则要检查证书链及客户端信任配置。每种情况都要保存具体报错,不能用「HTTPS 有问题」替代故障描述。
例如同一证书有效期到 10 月 31 日,客户端时钟却设成 11 月 10 日,就可能认为它已过期。这个例子说明判断依赖本地时间,但并不意味着所有过期报错都该通过改时钟解决。应比较可信时间、证书有效期和实际报错;若证书确实过期,就应更新证书。这里关注的是阶段与证据,无需展开完整的公钥基础设施课程。
curl 的 -k 会跳过证书校验,因而可能让原先失败的请求继续到 HTTP。即使此时拿到 200,也只能说明在放宽校验的条件下应用回答了请求,不能作为 HTTPS 身份验证通过的验收证据。正式验收应使用正常校验,并记录证书名称是否匹配。排查命令改变了什么条件,必须与它得出的结论一起说明。
把明文 HTTP 发到预期 TLS 的入口,或者把 HTTPS 发到只接受明文的端口,也可能产生协议错误或连接被关闭,不能把它们统称为 404。先核对 scheme 与端口的服务约定,再读后续输出。HTTP/2 以及更后的版本也存在,其中 HTTP/3 的传输方式不同;本课用 --http1.1 固定观察范围,避免把本课顺序当成所有版本的唯一实现。
6 看一次交互:用 curl 读阶段与正文
curl 的 -v 会输出连接和协议过程。示例先固定 HTTP/1.1,正文另存到文件,使终端中的阶段记录更容易阅读。> 标出发出的 HTTP 行,< 标出收到的 HTTP 行,* 通常是 curl 的过程说明;这些标记是工具的展示方式,不属于线上报文正文。
curl --http1.1 -v -o /tmp/s19-body.json https://app.example/api
echo $?
* Trying 198.51.100.20:443...
* Connected to app.example (198.51.100.20) port 443
* SSL connection using TLSv1.3
* SSL certificate verify ok.
> GET /api HTTP/1.1
> Host: app.example
< HTTP/1.1 404 Not Found
< Content-Type: application/json
0
这里省略了握手细节。Trying 表示正在尝试某个地址,不等于已经连上;Connected 是 TCP 已连接的证据;TLS 版本和证书校验成功说明该阶段通过;最后的状态行才是 HTTP 回答。输出格式会随 curl 版本和 TLS 后端变化,应识别阶段含义,不要把某一句英文当作所有系统必有的固定文本。
末尾的 0 来自紧随 curl 的 echo $?,表示这个 curl 命令按默认规则完成了传输。curl 默认不会把 404 或 502 自动当作命令执行失败,因此「退出码 0」和「HTTP 200」不是同一个判断。退出码应立刻保存或显示,因为后续执行的其他命令会覆盖 shell 中的 $?。
curl --http1.1 --fail -v -o /tmp/s19-fail-body.json https://app.example/api
echo $?
< HTTP/1.1 404 Not Found
curl: (22) The requested URL returned error: 404
22
对于这里的 404,--fail 让 curl 返回 22;本课后面的 502 也有相同对照。这个选项通常还会抑制 HTTP 错误响应正文,若正文包含诊断信息,应在默认请求中单独留存。22 不能区分 404 和 502,脚本仍须保存状态码,不能把退出码本身当成业务结果码。
若怀疑 DNS 指到了旧入口,可以在保持 URL 名称的同时固定地址:
curl --http1.1 -v --resolve app.example:443:198.51.100.20 https://app.example/health
--resolve 为指定主机与端口提供本次命令使用的地址,连接去往 198.51.100.20,URL 名称、HTTP Host 和 TLS 使用的服务器名称仍围绕 app.example。这比把 URL 改成 IP 更适合隔离入口地址差异。它绕过了本次目标的正常解析选择,所以该请求成功不能反过来证明 DNS 配置正确;应把普通访问与固定地址访问的结果配对记录。

若要比较耗时,可以使用 -w 输出累计时间。例如观测到 DNS 完成于 0.010 秒、TCP 完成于 0.040 秒、TLS 完成于 0.090 秒、首字节到达于 0.210 秒、总计 0.230 秒。在这次无复用、无跳转的简单请求中,TCP 阶段约为 30 毫秒,TLS 增量约为 50 毫秒,TLS 完成到首字节约为 120 毫秒。最后这段仍包括请求传送和等待等因素,不能直接命名为应用 CPU 时间。
7 常见坑:把传输成功当业务成功、乱猜状态码、忽略 Host
第 1 个坑是看见 Connected 就宣布服务正常。一次探测可能依次得到 TCP 成功、TLS 成功和 HTTP 502,这 3 条记录并不矛盾。它们分别回答不同问题。验收报告应写出「哪个 URL、什么方法、预期什么状态、实际什么状态」,而不是只贴上连接成功截图。若目标要求返回商品 17,再多加一项正文断言,才能防止登录页或维护页以 200 混过检查。
第 2 个坑是把代码当成根因。404 提示资源选择方向,却不自动说明磁盘文件不存在;502 提示网关与上游方向,却不证明应用进程一定已停止。假设 /api 得到 404,/health 得到 200,可以缩小到路径、方法或路由差异,但还不能排除 2 条路径被分发给不同服务。要把「已证实的现象」与「待验证的原因」写成不同句子。
第 3 个坑是忽略 Host。运维人员测 IP 首页得到默认站点的 404,用户用域名却能访问,双方可能都测对了自己的请求,只是请求并不相同。比较结果时应列出完整 URL、Host、实际连接地址及端口。对于 HTTPS 还要保留 TLS 名称,避免为了测试 Host 而引入新的证书不匹配问题。
第 4 个坑是用浏览器表现替代原始响应。浏览器可能自动跟随 301,最终显示 200,也可能展示先前缓存的页面;缓存或 CDN 还可能让相同 URL 在不同时间、不同入口得到不同响应。若问题是跳转,先不跟随跳转观察 Location;若问题只在部分用户出现,就比较实际入口与响应字段,不要在证据缺失时断言「服务器随机报错」。
第 5 个坑是只报告「慢」。假设 2 次请求都耗时 2 秒,前者卡在 TCP 连接,后者 40 毫秒已连上、随后等待首字节,两者的检查方向显然不同。报告应写清哪个阶段耗时,以及是否发生了重试或跳转。跟随 3 次跳转的总时间不能直接与无跳转请求的应用等待时间比较。
还有一种误判发生在健康检查范围上。/health 返回 200 可能只表示入口进程能回答,不代表订单、数据库和支付链路都正常。假设健康路径由网关直接返回,而 /api/orders 需要访问上游,那么健康 200 与订单 502 可以同时出现。健康路径证明了什么必须由服务约定,不能把轻量探针结果扩大成全部业务保证。
8 案例:同个 URL「打不开」的3种 HTTP 侧原因
现在把用户入口固定为 https://app.example/work。以下是 3 次独立故障中的证据包,不是同一时刻连续出现的状态。它们都可能被用户描述成「页面打不开」,也都能在 TCP 和 TLS 成功后发生。我们的目标是从响应中选择首个检查动作,而不是从这句口头描述直接选负责人。
情形 A 是重定向配置形成环路。先请求入口,得到以下响应:
HTTP/1.1 301 Moved Permanently
Location: https://app.example/login
继续请求 /login,又得到:
HTTP/1.1 301 Moved Permanently
Location: https://app.example/work
2 个地址互相指向,浏览器可能最终报告跳转过多。用 curl --http1.1 -v -L --max-redirs 5 https://app.example/work 能保留每跳状态与 Location;在本例中达到重定向上限时,curl 返回退出码 47。这个 47 是客户端因跳转上限停止的结果,不能当成 HTTP 状态码。首个检查应是比较入口与登录跳转规则,定位谁把已经跳转过的请求送回原址。
情形 B 是身份或权限条件不满足。未带所需凭据时,入口可能回答:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="app"
Content-Type: application/json
401 提示需要满足身份验证条件;已有身份却不允许访问时,常见响应是 HTTP/1.1 403 Forbidden,但实际选择仍以服务契约为准。证据包应记录测试账号的角色、凭据是否过期、请求时间和状态,避免在共享输出中暴露令牌。首个检查是本次请求是否具有约定的身份条件,再核对该身份的权限;反复改 DNS 不会为请求增加权限。
情形 C 是入口网关无法取得有效的上游响应:
HTTP/1.1 502 Bad Gateway
Content-Type: text/html
X-Request-ID: s19-work-052
此时保留请求标识 s19-work-052 与时间,交给网关侧查询对应路由、上游地址和连接结果。若日志显示网关访问 10.0.2.17:8080 时连接被拒绝,就有了继续检查该实例监听与进程的依据;若只有 502 状态,便不能擅自补出这条结论。响应中的服务器名称字段可以提供线索,但不应被当成不可伪造的组件身份证明。
3 个证据包的差别很具体:A 有重复 Location 链,B 有身份或权限相关响应,C 有网关失败及可关联的请求标识。相同的用户入口不保证相同的处理路径;重定向规则、登录状态和上游健康都能改变结果。报告「同一 URL 失败」时,要再补上时间、请求条件和首个异常响应,才能让后续排查从可验证的位置开始。
9 排查案例:从 DNS/TCP 正常走到 HTTP 404 与 502
值班人员收到报告:「应用 API 不可用,可能网络断了。」本例预期 GET /api 返回 200 和接口描述,GET /health 返回 200 和 ok。我们先验证前序阶段,再比较同一域名的路径。下列片段来自控制了请求条件的教学环境,每条命令后立即读取退出码;实际记录还应写上采样时间,以免把不同时刻的状态误当成并存事实。
dig +short app.example A
198.51.100.20
这证明本次 DNS 查询返回了该地址,尚不能单凭这个输出认定地址正确。与方案中的预期入口 198.51.100.20 对照一致后,继续测试端口:
nc -vz 198.51.100.20 443
echo $?
Connection to 198.51.100.20 443 port [tcp/https] succeeded!
0
现在有了 DNS 返回预期地址、该地址 443 端口可建立 TCP 的证据。它们还没有证明证书正确,也没有证明 /api 存在。若域名有多个地址,应逐个核对相关入口;本例为便于比较,只配置 1 个 A 地址,随后 curl 的 Connected 行也确认连接到了它。
curl --http1.1 -v https://app.example/api
echo $?
* Trying 198.51.100.20:443...
* Connected to app.example (198.51.100.20) port 443
* SSL certificate verify ok.
> GET /api HTTP/1.1
> Host: app.example
< HTTP/1.1 404 Not Found
< Content-Type: application/json
{"error":"route_not_found"}
0
这次请求通过了 TCP 和 TLS,收到明确的 HTTP 404,curl 默认退出码为 0。排查重点转向「这个站点上的 GET /api 是否应存在」。先按相同 scheme、域名和端口请求约定健康路径:
curl --http1.1 -v https://app.example/health
echo $?
* Connected to app.example (198.51.100.20) port 443
* SSL certificate verify ok.
> GET /health HTTP/1.1
> Host: app.example
< HTTP/1.1 200 OK
< Content-Type: text/plain
ok
0
两次只改变路径,健康请求能返回预期内容,因此「所有 HTTP 请求都到不了入口」与现有证据不符。下一步核对路由清单、接口版本和发布记录:是否实际部署的是 /api/v1,是否网关误删了 /api 前缀,是否该路径只支持其他方法。不要为了追求成功而任意换方法;应与接口约定核对后,再实施有目的的比较。
若还怀疑虚拟主机或入口选择,可以用 --resolve app.example:443:198.51.100.20 重复上述请求,并保留 Host: app.example。如果固定地址后 /api 仍为 404,就继续查询该入口的站点路由。若换到另一个已知入口结果改变,才进一步比较入口配置。直接访问 IP 得到默认页,会同时改变名称条件,不适合作为仅隔离地址的对照。
接着测试另一条约定路径 /api/orders。本例中它已配置到订单上游,但该上游当前不可用:
curl --http1.1 -v https://app.example/api/orders
echo $?
* Connected to app.example (198.51.100.20) port 443
* SSL certificate verify ok.
> GET /api/orders HTTP/1.1
> Host: app.example
< HTTP/1.1 502 Bad Gateway
< X-Request-ID: s19-orders-081
< Content-Type: text/html
<html><body>Bad Gateway</body></html>
0
502 与刚才的 404 都证明有 HTTP 组件回答了本次请求,却提示不同方向。404 先查目标选择和路由是否存在;502 先查已接收请求的网关如何访问上游。本例继续关联 s19-orders-081 的网关日志,发现它连接订单实例 8080 端口被拒绝,才将故障收敛到对应实例的监听与服务状态。若没有这条日志,结论应停在「需要核查网关到上游」,不能跳到重启应用。

最后让命令退出码与验收脚本的意图对齐:
curl --http1.1 --fail -v https://app.example/api
echo $?
curl --http1.1 --fail -v https://app.example/api/orders
echo $?
< HTTP/1.1 404 Not Found
curl: (22) The requested URL returned error: 404
22
< HTTP/1.1 502 Bad Gateway
curl: (22) The requested URL returned error: 502
22
默认请求中 404 与 502 都返回退出码 0,加 --fail 后这里都返回 22;健康请求在两种模式下均为 200、退出码 0。若验收明确要求 200,还应直接比较状态是否为 200,因为 --fail 不能替代精确的状态断言,例如未跟随的 301 通常不会因此变成失败。
这次交接可写为:「预期入口解析一致,443 TCP 可连接,TLS 校验通过;GET /api 返回 404,GET /health 返回 200 与 ok,GET /api/orders 返回 502。请应用路由负责人核对 /api 发布情况,网关负责人按请求标识核对订单上游。」这种表述让每条结论都有对应证据,也明确哪些事项仍需查证。
放进方案里
HTTP 服务方案应把「可访问」改写成可重复的验收条件。先写完整 URL、方法、端口和预期状态,例如 GET https://app.example/api/items/17 应返回 200,正文的 id 应为 17,Content-Type 应为 JSON。若允许重定向,写清允许的目标与最大跳数,不能让测试人员自行猜测 301 是否算通过。受保护接口还应说明测试身份和预期权限,不在方案中保存真实凭据。
把入口条件写在同一处:域名应解析到哪个入口,HTTP Host 应是什么,TLS 证书应覆盖哪个名称。需要固定地址测试时,提供保留域名的 --resolve 示例,并说明该结果只验证指定入口。若共有 2 个入口,分别列出地址与验收结果,避免只测中其中 1 个便宣布全部可用。正常证书校验是验收条件,跳过校验的结果应单独标注。
单列健康路径及其证明范围,例如 /health 返回 200 和 ok 只证明入口能够响应;订单功能另测 /api/orders。记录状态、正文关键断言、请求时间、实际连接地址及 curl 退出码,默认模式和失败判定规则都要明确。对于慢请求,保存 DNS、连接、TLS 与首字节阶段时间,让后续人员知道先查哪段。
责任分工应落到证据入口:404 由谁核对站点路由与发布,401/403 由谁核对身份权限,301 环路由谁检查跳转规则,502 由谁先查网关日志、再联系对应上游负责人。502 交接至少带 URL、时间与请求标识,不要求用户凭状态码判断具体实例。另写连接超时与首字节超时的上限秒数,以及超时时应保留的 curl -v 片段,避免把传输层等待误记成 HTTP 业务失败。
验收时请第 2 位同事独立执行:面对「Connected 后失败」「健康 200 但接口 404」「订单 502」这 3 种症状,能否各自指出首个 HTTP 侧检查并找到负责人。能完成这项映射,方案才具备交接价值。

10 小结:走向解决方案(P1)
现在可以带着证据叙述浏览器访问的过程:DNS 为主机名找地址,TCP 建立连接,HTTPS 场景中 TLS 校验并建立安全通信条件,HTTP 再表达方法、目标与处理结果。每个阶段都能成功或失败,后续阶段的结论不能只靠前序阶段推测。
自测可以沿用本课记录:为什么 Connected 后还会没有 HTTP 状态?为什么 404 可以伴随退出码 0?为什么同一 IP 下修改 Host 会得到不同站点?为什么健康 200 不能排除订单 502?回答时各指出对应的输出位置和下一步检查,比背诵代码名称更能说明你掌握了分层判断。
阶段 1 的收束是能够把现象、证据和结论连起来。下一阶段进入云与非功能需求时,还会讨论可用性、性能和责任边界;本课留下的 URL 验收条件、阶段耗时和故障交接记录,将成为那些讨论可操作的起点。
下一课转向 P1 什么是解决方案。
把本课的 URL 分层验收、阶段耗时与故障交接记录带走:下一课把这些证据写进「解决方案」的问题定义与验收口径,并检查云与非功能需求讨论是否仍能量化到可观察的 HTTP 结果。
作业:附上本课 1 组 curl 阶段耗时与 3 类症状→首个检查映射,作为 P1 练习的输入。