观察 HTTP:工具与记录
说明终端、浏览器和网络捕获分别能证明什么。
本页沿 HTTP 的具体接口和通信行为展开。沿用归档中的消息与代码示例,归档所称的实测仅作为原作者历史记录;2026-10-05 整理未向外部站点发送实验请求,也未操作浏览器。命令输出随服务、网络、版本和代理条件变化。
本页说明怎样把一次通信的客户端、报文和网络证据联系起来。curl 片段是原作者在 2026-10-03 使用 macOS curl 8.7.1 留下的记录;服务、代理与软件版本变化后,状态和字节数可能不同。本轮保留记录并审查其解释,未复现实验。
一、三件工具,三层视野
为什么需要工具?因为 HTTP 报文平时看不见:浏览器替你发、替你收,你只看到渲染好的页面。想观察它,就得借助仪器。常用的有三件,各站一层高度:
- 浏览器 DevTools(开发者工具):浏览器的"行车记录仪",站在应用层记录每次 HTTP 请求——发了什么头、拿到什么状态码、花了多少时间。
- curl:命令行 HTTP 客户端。用它等于自己当客户端,不经浏览器亲手发报文;改一个头、换一个方法,立刻看到服务器的反应,最适合做实验。
- Wireshark:网络封包分析器(network packet analyzer),站在网卡视角逐个封包(packet)展示网上流动的数据,DNS、TCP、TLS、HTTP 各层全都看得见。官方类比很贴切:就像电工用电压表检查电线,是"检查网线里发生了什么的测量仪器"。
下面先说明浏览器和 curl 的观察入口,再说明抓包的范围。
二、浏览器里的放大镜:DevTools Network 面板
先开面板,再访问页面
按 F12(macOS 上可能需要 fn+F12,或改用 Cmd+Option+I,也可右键页面选"检查")打开 DevTools,切到 Network(网络)面板,刷新页面,就能看到一串请求鱼贯而入。
新手最容易踩的坑:Network 面板通常在 DevTools 开启并启用录制时采集请求,之前发生的不会补录,顺序一定是"先开面板、再访问页面"。页面加载或跳转会清空列表,想保留就勾选 Preserve log(保留日志),请求会保留到你取消勾选。
请求列表:每一列回答一个问题
- Name / Type / Time:文件名、资源类型(文档、脚本、图片……)、总耗时;
- Status:状态码(如 200、404),失败显示 (failed),跨域(CORS)问题标注 CORS error;
- Size:响应头加响应体的传输大小;
- Waterfall(瀑布图):把每个请求的时间分解画成一根横条。
表头右键可增删列,比如回答"谁发起的"的 Initiator(发起者)列。瀑布图横向是时间轴,按总时长排序时浅色是等待、深色是下载字节。点列头是按该列排序;想按开始时间、响应时间、总时长、延迟(latency)等活动阶段排序,需右键表头在 Waterfall 子菜单中选择。悬停会弹出时间分解,Shift+悬停高亮发起者(绿)与依赖(红)。
过滤有两板斧:点上方类型按钮(CSS、JS、Img 等)按类型筛,Ctrl/Cmd+点击多选、点 All 取消;或在 Filter 框输入条件——png 只看文件名含 png 的资源,/.*\.[cj]s+$/ 用正则表达式(regular expression)过滤,-main.css 排除匹配项,domain:raw.githubusercontent.com 按域名筛。
点开一个请求:六个标签页
- Headers:最常用。General 汇总状态码与可读状态文本,往下是 Response Headers 与 Request Headers,点 view source 看接收顺序的原始头;
- Payload:查询字符串参数和表单数据;
- Preview:渲染或格式化后的预览,官方说明主要用于查看图片;
- Response:服务器返回的原始响应体(若传输时经 gzip/br 压缩,DevTools 会自动解压后显示);当内容被压缩成一行(minified)时,可点底部的 Format 按钮重新排版;
- Cookies 与 Timing:前者列请求携带的 Cookie,后者是网络活动分解(见下)。
注意 Preview 与 Response 不是一回事:前者是摆好盘的样子,后者是原料。
Timing:时间都花在哪了
Timing 把耗时按阶段分解,像一张快递进度表。最常见的是下面六个,归档实验记录里也可能多出 Stalled、Proxy negotiation 等行——那是更细的分段,不必疑惑:
- Queueing(排队):请求就绪但还在排队。常见原因:有更高优先级的请求在前、浏览器在分配磁盘缓存空间,以及 HTTP/1.x 下同一源(origin)最多 6 条 TCP 连接,超出必须等位置;
- DNS Lookup:把域名解析成 IP;
- Initial connection(初始连接):建立连接,含 TCP 握手与 SSL/TLS 协商;
- Request sent:发出请求,通常极短;
- Waiting (TTFB):等待响应首字节(Time to First Byte),含一次往返时延加服务器处理;
- Content Download:接收响应体。
观察前的两个开关
勾选 Disable cache(停用缓存) 后 DevTools 会禁用浏览器缓存——数"真实发出了多少请求"时勾上,观察缓存协商时取消。长按刷新按钮(需 DevTools 打开)可选 Empty Cache and Hard Reload(清空缓存并硬性重新加载),请求重新获取资源;Service Worker、代理和服务器响应仍需要分别观察。
要点回顾:
- 标签页各司其职:Headers 看头、Payload 看数据、Response 看原始正文、Preview 看渲染、Timing 看耗时。
- Chrome 常见 HTTP/1.x 场景使用约 6 条同源连接;这是浏览器实现策略,超出即排队。
三、自己当客户端:curl 最小入门
浏览器把细节都藏了起来;想亲手控制报文,就用 curl(macOS 与多数 Linux 自带)。
看完整对话:-v
curl URL 就是发一个 GET 请求,但默认只打印响应正文;加 -v(verbose)才能看到完整对话。输出有三种前缀,方向是"发出/收到"而非"输入/输出":> 是 curl 发出的头部(请求),< 是 curl 收到的头部(正常隐藏),* 是连接过程信息(DNS、TCP、TLS)。
curl -v https://example.com以下为 2026-10-03 归档实验输出节选:
* Trying 198.18.1.193:443...
* Connected to example.com (198.18.1.193) port 443
* ALPN: curl offers h2,http/1.1
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted h2
* using HTTP/2
> GET / HTTP/2
> Host: example.com
> User-Agent: curl/8.7.1
> Accept: */*
>
< HTTP/2 200
< date: Sat, 03 Oct 2026 19:28:09 GMT
< content-type: text/html; charset=utf-8
< server: cloudflare
< last-modified: Fri, 02 Oct 2026 16:11:02 GMT
< cf-cache-status: HIT
<
{ [577 bytes data]
* Connection #0 to host example.com left intact逐段读:* 行显示连上 443 端口、用 TLS 1.3 加密、经 ALPN(应用层协议协商)谈定 HTTP/2;> 是请求、< 是响应;[577 bytes data] 是 577 字节正文;末句记录 curl 在该客户端会话中保留连接,不能单凭它证明后续确实复用了连接。注意 198.18.1.193 是原调研代理工具分配的 fake-IP,不能据此推定源站的公网地址——代理工具常把域名映射到 198.18.0.0/15 这个保留网段的假地址上;cf-cache-status: HIT 表示命中 Cloudflare 边缘缓存,都因环境而异。
另外两个选项:-i(--include)在输出中包含响应头——是响应头,不是请求头,man page 明确建议"要查看请求头,考虑 -v"(curl 8.10.0 起有了新名 --show-headers);-I(--head)发 HEAD 请求,只要响应头不要正文,如 curl -I https://example.com。
改请求:-H、-d 与被误解的 -X
-H 添加自定义请求头。curl 本来就会自带一些请求头(如 Host、User-Agent,下称内部头),与内部头同名则覆盖,-H "Host:"(冒号后留空)则删除该头。httpbin.org 会把收到的请求头原样回显,正好用来验证(命令里的 -s 是静默模式,不显示进度条,免得它混进 JSON 输出):
curl -s https://httpbin.org/headers -H 'X-My-Header: hello-http'返回的 JSON 中出现 "X-My-Header": "hello-http",证明头确实加上了。
-d(--data)默认选择 POST,并发送所给数据、默认设置 Content-Type: application/x-www-form-urlencoded。它不会自动把任意输入进行 URL 编码;需要编码字段值时使用 --data-urlencode。已经是 JSON 的文本可以配合显式 JSON Content-Type 发送,--json 则组合常用 JSON 选项。@ 前缀表示读文件,多个 -d 的值通常以 & 连接;保留文件内的换行与字节时考虑 --data-binary。
-X(--request)常被滥用。man page 警告得很直白:它"只改变 HTTP 请求里实际使用的那个单词,不改变 curl 的行为"。想做 HEAD 请求,-X HEAD 并不够——curl 仍会像 GET 一样等正文——正确做法是 -I;跟随重定向时还可能因方法不调整产生意外副作用。官方结论:通常你不需要这个选项。
要点回顾:
-v同时看到请求、响应与连接过程;>发出、<收到、*连接信息。-i含的是响应头;-I只取响应头;-d默认 POST 并设置表单 Content-Type;URL 编码需单独选择。
四、逐行读懂一段真实报文
这组记录只需回顾 HTTP/1.1 的四部分:起始行、头字段、空行和可选正文。空行标记头部结束;curl 的 >、< 与数据提示是显示格式,未必是线上原始字节。完整消息结构与方法语义分别在本分册的对应章节解释。
请求报文
这是 curl -v http://httpbin.org/post -d 'hello=world' 的真实请求(2026-10-03 原调研记录;注意没写 -X POST,方法因 -d 自动变成 POST):
> POST /post HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Length: 11
> Content-Type: application/x-www-form-urlencoded
>
} [11 bytes data]第一行是请求行(request-line),三要素:方法(POST)、请求目标(/post)、版本(HTTP/1.1)。之后每行一个头字段(header field),"名字: 值"占一行;字段名不区分大小写:Content-Type 与 content-type 是同一个字段。Content-Length: 11 恰好是正文 hello=world 的字节数。单独一行 > 就是空行,其后是正文。
响应报文
同一次请求的响应开头:
< HTTP/1.1 200 OK
< Date: Sat, 03 Oct 2026 19:29:37 GMT
< Content-Type: application/json
< Content-Length: 428
< Connection: keep-alive第一行是状态行(status-line):版本、状态码(200)、状态文本(OK)。其后是响应头:Content-Type 说明正文是 428 字节的 JSON,Connection: keep-alive 表示连接保持复用。空行之后才是正文。
HTTP/2 有什么不同(进阶一小段)
example.com 实际用的是 HTTP/2,线上样子与上面完全不同:方法、路径被放进 :method、:path 这类伪头部(pseudo-header)(注意只有前导冒号,冒号后接字段值,如 :method: GET),再经 HPACK(HTTP/2 专用的头部压缩算法)压缩后以二进制帧传输,不再是空格分隔的明文行。curl 与 DevTools 显示的都是还原后的可读形式;学明文报文格式,用 HTTP/1.1(或 http:// 站点)看最直观。
五、缓存协商实战:亲眼看到一个 304
304 表示条件 GET/HEAD 的现有表示可以继续使用,不含响应内容。本例保留原记录的验证器与输出,用于说明如何从请求和响应建立证据。实际检查时应先读取同一资源、同一表示的 ETag 或 Last-Modified,再将该值放入对应条件头,并记录 URL、Vary、状态及是否存在可复用缓存。ETag 对应 If-None-Match,Last-Modified 对应 If-Modified-Since;两种条件都在时按标准优先处理前者。
原记录使用比 Last-Modified 晚的日期取得了以下 304。这个日期只能证明服务器按该条件判断无需传输;客户端只有确实保存了对应表示,才能据此复用内容。复查时应原样使用当前响应的验证器,避免把任意未来日期当成“已经拥有最新版”的证据。
curl -s -D - -o /dev/null \
-H 'If-Modified-Since: Sat, 03 Oct 2026 00:00:00 GMT' \
http://example.com返回(2026-10-03 原调研记录,节选;完整输出还有 Server、Age、Allow、cf-cache-status 等更多头,以你的实际结果为准):
HTTP/1.1 304 Not Modified
Date: Sat, 03 Oct 2026 19:30:22 GMT
Connection: keep-alive
Last-Modified: Fri, 02 Oct 2026 16:11:02 GMT
ETag: "6abfd796-241"
alt-svc: h3=":443"; ma=86400空行之后什么都没有——304 没有正文。同一天用 -H 'If-None-Match: "6abfd796-241"' 原调研记录,同样得到 304。
六、页面加载与刷新怎样记录
观察一次加载时,记录页面 URL、浏览器版本、缓存开关、Service Worker 状态、请求数量、Initiator 与 Timing。请求数随页面内容变化,HTML 里的引用也未必都会产生网络请求;缓存、预加载、脚本分支和阻止规则会影响实际列表。
比较刷新行为需要同时检查请求条件头、响应状态和缓存来源。普通刷新可能验证主文档,也可能复用仍新鲜的子资源;强制刷新通常要求重新获取,但不能承诺所有请求都得到源站 200。重定向、错误响应、Service Worker 和代理都可能改变结果。Cache-Control: max-age=0 要求缓存满足零年龄的新鲜条件,可以触发验证;no-cache 要求成功验证后才能复用已存响应。列表中的 304、from memory cache、from disk cache 和请求失败应分别计数。
七、更深处:Wireshark,以及一条安全边界
最后往更深一层看一眼。Wireshark 官网自述为"世界领先的网络协议分析器":免费开源(GPL v2 许可证),运行于 Windows、macOS、Linux 等大多数平台,能深入检查数百种协议;官方列出的用途之一正是学习网络协议的内部原理。
它与 DevTools 的差别在层次:DevTools 只展示浏览器视角的 HTTP 层;Wireshark 逐包展示所有层次,DNS 查询、TCP 握手、TLS 协商与证书、明文 HTTP 的每个字节都尽收眼底。
但有一条重要边界:对 HTTPS 流量,Wireshark 默认只能看到密文——部分握手与网络元数据可见,应用层内容是加密的;TLS 1.3 的证书等握手消息也在加密保护内;要解密必须提供密钥(通常需要会话密钥日志;服务器 RSA 私钥只适用于特定旧式 RSA 密钥交换,不能解密现代前向保密连接;Chrome/Firefox 可设 SSLKEYLOGFILE 环境变量导出会话密钥)。这从底层印证了:DevTools 能看到 HTTPS 的明文请求,是因为浏览器自己就知道明文;而在网线上,它是密文。想直接看明文报文,抓 http:// 站点或用 DevTools 最直接。
最后是安全与礼貌:抓包与调试工具只用于自己的网络与自己有授权的环境;curl 的 man page 也特别警告,-v 输出可能包含 Cookie、凭据等敏感信息,分享日志前务必先检查。
当你想亲眼看看 TLS 握手长什么样、TCP 怎样一来一回时,Wireshark 就在那里等你。
最后更新于