HAR 工具
HAR 文件是浏览器在网络上所见一切的 JSON 记录:它的结构长什么样、七个计时阶段各自量的是什么,以及在把它交给别人之前必须先脱敏掉哪些东西。
2 工具
HAR 是通信记录,不是抓包
HAR(HTTP Archive)文件是一份 JSON 文档,描述浏览器在网络上观察到了什么。所有
有价值的内容都在 log.entries[] 里:每个元素对应一组请求/响应,携带方法、URL、
头、cookie、大小、逐阶段的计时拆分,以及——有时候——正文。格式是 HAR 1.2,一份
从未被批准的 W3C 草案,但 Chrome、Firefox、Safari、Charles 和 Fiddler 输出的方言
彼此足够接近,文件可以在它们之间互相搬运。
真正能省下几个小时的思维模型是:这份文件是浏览器自己的网络栈事后写出来的,它保存的 是那个栈的汇总,而不是线路上发生的事。没有 TLS 握手记录,没有 HTTP/2 帧,没有 DNS 报文,只有网络栈选择上报的那些时长。DevTools 打开之前发出的请求不在里面;除非开了 Preserve log,上一次导航也不在;命中缓存或 service worker 的请求,会以从未接触 网络的条目形式出现。同一个 bug 的两次抓取经常互相矛盾。
文件的结构
log
├─ version "1.2" · creator{name,version} · browser
├─ pages[] id, title, startedDateTime, pageTimings{onContentLoad,onLoad}
└─ entries[] pageref, startedDateTime, time, connection, serverIPAddress
├─ request method, url, httpVersion, headers[], cookies[],
│ queryString[], postData{mimeType,text,params}
├─ response status, statusText, redirectURL, headers[], cookies[],
│ content{size,compression,mimeType,text,encoding}
├─ cache beforeRequest, afterRequest
└─ timings blocked, dns, connect, ssl, send, wait, receive
有两个细节最坑人。request.headers[] 和 request.cookies[] 是同一批 cookie 的
两种独立表示,所以只遍历头的清洗会把整罐 cookie 原样留下。以 _ 开头的键
(_initiator、_priority、_resourceType、_webSocketMessages)是厂商扩展:
在 Chrome 里有用,别处没有。而且几乎每个数值字段都可能是 -1——那是不可获得,
不是零——包括 bodySize、pageTimings 和每一个计时阶段。
七个计时阶段
单位是毫秒,按发生顺序排列。
| 阶段 | 量的是什么 | 数值偏大通常意味着 | 何时为 -1 |
|---|---|---|---|
blocked | 请求离开客户端之前的排队 | HTTP/1.1 每源约 6 条的上限;代理协商 | 从未排队 |
dns | 主机名解析 | 冷解析器、很长的 CNAME 链 | 连接被复用 |
connect | TCP 握手——包含 ssl | 源站遥远、RTT 高、无 keep-alive | 连接被复用 |
ssl | TLS 协商(计入 connect 内部) | 证书链庞大、没有会话恢复 | 明文 HTTP,或连接复用 |
send | 把请求字节推出去 | 上传体积很大 | — |
wait | 最后一个请求字节之后的 TTFB | 后端处理时间加一个 RTT | — |
receive | 从线路上读取正文 | 未压缩或体积过大的载荷 | — |
entry.time 是总耗时,实践中等于
blocked + dns + connect + send + wait + receive。由于 ssl 嵌套在 connect
里面,把七列全部相加就会把握手算两遍——这是 HAR 分析中最常见的算术错误。
重建瀑布图
两个坐标轴都能从 JSON 里恢复出来:水平位置是 entry.startedDateTime 减去所属
log.pages[].startedDateTime;宽度是 entry.time,再按上面那些阶段切开。
entry.connection 则显示哪些请求复用了同一个 socket。
接下来要读的是形状,不是数字。阶梯形——每个请求都在上一个结束时才开始——是依赖链,
HTML → JS → API → 图片,任何服务端调优都治不了它,只能把资源发现提前。一片密集的
方块、并且越往下 blocked 段越长,那是客户端在排队;这也解释了为什么 HTTP/2 常常
能在没有任何一个响应变快的情况下把瀑布图压平。
选对工具
先把抓取结果粘进 HAR 文件查看器——它也在
SRE 工具精选 里。它把 log.entries[] 摊平成
Method / Status / Type / Size / Time / URL,附一行
N requests · X KB · Y ms total 汇总,并把 Status 单元格对 4xx/5xx 标红、
对 3xx 标橙:这是找出离群项最快的路径。只有元数据,从不含正文。
查看器给的是每条的总时间,不是阶段拆分。要看阶段,就用
JSONPath 查找器 直接查原始文件——$..timings 取出全部
拆分,或者用 $.log.entries[?(@.time>1000)].request.url 只取慢的那些。如果导出的
文件根本解析不了(下载被截断、粘贴时被折行),把它扔进
JSON 格式化,能拿到解析器报出的精确错误位置。
Status 那一列,HTTP 状态码参考 把一个裸数字变成名称、分类和
一行释义,可按码值或关键词过滤——分辨 400 与 422 最快的办法。它止步于含义,不
涉及重定向行为:它的 301 那一行只说资源已永久移动。读一条重定向链时,这部分要从
协议本身补上——301 和 302 允许客户端把 POST 改写成 GET 并丢掉正文,而 307
和 308 不允许。完整对照是 HTTP 工具集 里的重定向矩阵,那里也
覆盖了响应头这一侧。手上没有抓取、只有一个 URL 时,
HTTP 头信息检查工具 会从 api.sitekits.dev 在服务端取回它,
返回状态、重定向跳数和全部响应头——正文从不获取,URL 从不存储。用
JWT 令牌解码器 解开 401 里的 bearer token,看看 exp 是不是
已经过了;再用 REST API 测试器 重放一遍,它从你的浏览器
直连目标,因此 CORS 的表现跟在你自己的应用里一样。
落坑清单
一份原始 HAR 就等于一份凭证
会话 cookie、Authorization: Bearer …、x-api-key、签名 URL、登录请求体,以及
你这个会话能读到的每一个响应,全都在里面。
HAR 文件清洗器 完全在你的浏览器里运行——什么都不上传——它把
敏感头、名字匹配
token|key|secret|password|passwd|pwd|auth|session|sig|signature 的参数,以及
整个 postData.text / content.text 的值替换成 [REDACTED],并报告一共命中了
多少处。即便浏览器给了你一个“已脱敏”的导出选项,也照样跑一遍——那些选项到底剥掉
什么,随版本而变。
模式脱敏不构成证明
头名是拿一份固定清单比对的,参数名是拿正则比对的,所以任何不按常规命名的东西都会
存活下来:藏在 URL 路径段里的密钥(/v1/reset/9f3c…)、结构化的 cookies[]
数组、serverIPAddress(你源站的真实 IP)、内部主机名。扫一遍输出,把任何可疑
URL 粘进 URL 解析器 看解码后的查询串,并在文件离开你的机器之前
grep 一遍 authorization 和 set-cookie。同一套条件反射也适用于
隐私工具 和 安全工具精选。
content.size 和 bodySize 量的是两回事
content.size 是解码后的正文长度;bodySize 是实际收到的字节数,省下了多少记在
content.compression 里。命中缓存的响应报告 bodySize: 0,所以用 content.size
汇总出来的 KB 数,会高估一个启用了压缩的站点的网络成本。