1. 为什么WebSocket抓包和普通HTTP抓包完全是两回事
很多人第一次用Fiddler抓WebSocket的时候都会懵:明明HTTP请求抓得好好的,一切到WebSocket就什么都看不到了。这不是Fiddler坏了,而是WebSocket这套协议从底层就跟HTTP走的不是一条路。
WebSocket在建立连接之前,确实会先发一个HTTP请求做"握手"——这个握手请求Fiddler是能抓到的,你能看到一个带Upgrade: websocket头的GET请求。但握手一旦完成,双方就切换到了全双工的TCP长连接模式,后续的数据帧不再走HTTP的请求-响应模型,而是以帧(Frame)为单位双向流动。Fiddler默认的抓包逻辑是围绕HTTP会话设计的,如果不做额外配置,它要么看不到这些帧,要么看到了也解析不出来。
这就是为什么你搜"fiddler设置抓包WebSocket协议"的时候,会发现大家的答案五花八门——有人让你改脚本,有人让你装插件,有人让你换工具。其实核心就两件事:让Fiddler能捕获到WebSocket流量,以及让Fiddler能把二进制帧解析成可读内容。这两件事的配置方式,取决于你用的是Fiddler Classic还是Fiddler Everywhere,也取决于目标流量是明文ws://还是加密的wss://。
这篇文章我会把这两种情况都拆开讲清楚,包括每一步为什么这么做、参数怎么填、抓不到的时候怎么一步步排查。如果你正在调试一个实时聊天、在线协作、行情推送或者游戏同步类的应用,这套流程能直接拿去用。
2. 动手之前先把Fiddler的解密开关配对
在碰WebSocket之前,有一个前置条件必须先搞定,否则后面全是白忙活:HTTPS解密。现在绝大多数生产环境的WebSocket都是wss://,也就是跑在TLS之上的。Fiddler如果解不开TLS,它连握手请求里的Upgrade头都看不到,更别提后面的帧了。
2.1 开启HTTPS解密的标准操作
打开Fiddler Classic,走Tools → Options → HTTPS,把Capture HTTPS CONNECTs和Decrypt HTTPS traffic两个勾都打上。下面那个下拉框里,如果你只关心特定域名,可以选...from all processes或者...from remote clients only,但调试阶段建议先全量抓,确认通了再收窄范围。
勾完之后Fiddler会提示你安装根证书,点Yes一路走完。这一步的本质是:Fiddler在中间充当了一个"可信中间人",它用自己的根证书给每个HTTPS站点动态签发一张证书,浏览器信任了Fiddler的根证书,自然就信任了这些动态证书,于是TLS在Fiddler这里被"拆开",明文就露出来了。
注意:如果你用的是Firefox,它有自己的证书库,不认系统证书。需要在Firefox的
设置 → 隐私与安全 → 证书 → 查看证书 → 证书颁发机构里手动导入Fiddler的根证书,否则Firefox的HTTPS流量在Fiddler里会显示成一堆乱码或者直接连不上。
2.2 验证解密是否真的生效
别急着去抓WebSocket,先随便打开一个HTTPS网站,在Fiddler里看对应的会话。如果Inspectors → Response里能看到正常的HTML或JSON,说明解密链路是通的。如果看到的是Tunnel to ...:443这种只有一条CONNECT记录的会话,那说明解密没生效,回去检查证书安装。
这一步看着简单,但我见过太多人跳过它,然后对着WebSocket抓不到的问题折腾半天,最后发现根因是HTTPS根本没解开。先验证基础能力,再叠加复杂场景,这是排查问题的基本纪律。
2.3 关于Fiddler Classic和Fiddler Everywhere的选择
Fiddler Classic是免费的Windows桌面工具,功能全、脚本能力强,WebSocket支持从较早版本就有了,但界面偏老。Fiddler Everywhere是跨平台的新一代产品,Mac和Linux都能用,WebSocket的展示界面更友好,但部分高级功能需要订阅。
如果你在Windows上做深度调试,Classic足够用;如果你在Mac上或者团队里系统混杂,Everywhere的体验更统一。两者的WebSocket配置思路是一致的,只是入口位置不同。下面我以Classic为主线讲,Everywhere的差异我会单独点出来。
3. 让Fiddler真正捕获到WebSocket会话
配置好HTTPS之后,理论上Fiddler已经能看到WebSocket的握手请求了。但"看到握手"和"抓到帧"之间还有一道坎,这道坎跟Fiddler的会话捕获机制有关。
3.1 握手请求长什么样
一个典型的WebSocket握手请求是这样的:
GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13服务端如果同意升级,会返回101 Switching Protocols。在Fiddler里,这个会话的图标和普通HTTP请求不太一样,响应码是101。你点开这个会话,在Inspectors里应该能看到WebSocket这个标签页——如果看不到这个标签页,说明Fiddler没有把它识别为WebSocket会话,问题多半出在握手请求没被完整捕获。
3.2 过滤器别把WebSocket挡在门外
Fiddler的Filters标签页是个双刃剑。很多人为了减少噪音,会设置只显示特定Host或者只显示特定Content-Type。问题在于,WebSocket握手请求的Content-Type往往是空的,如果你设置了Show only if Content-Type matches之类的规则,握手请求可能直接被过滤掉了。
排查方法很简单:把Filters里的所有勾先全部取消,回到全量捕获状态,重新触发一次WebSocket连接。如果这时候能看到了,再逐条把过滤规则加回来,定位是哪条规则误伤。
3.3 进程过滤的坑
Fiddler默认会捕获所有进程的流量,但有时候你会手动设置只捕获某个进程。如果你抓的是浏览器里的WebSocket,确认浏览器进程在捕获范围内。更隐蔽的一种情况是:某些应用使用了独立的网络库或者走了系统代理之外的通道,Fiddler压根没经过它的流量。这时候需要在应用的网络设置里确认它是否走了系统代理。
3.4 确认WebSocket标签页出现
当一切正常时,你点开那个101会话,会看到Inspectors区域多出一个WebSocket标签。切到这个标签,里面会列出所有收发的帧,包括Opcode(文本帧、二进制帧、ping、pong、close)、方向(发送/接收)、长度和内容。如果内容是可读文本,直接就能看;如果是二进制,就需要下一步的解析处理。
4. 二进制帧看不懂?用Fiddler脚本把内容翻译出来
WebSocket的文本帧(Opcode=1)Fiddler能直接显示成字符串,但二进制帧(Opcode=2)默认只给你看十六进制或者一堆乱码。很多实时应用——比如即时通讯的protobuf消息、游戏的状态同步包、音视频的信令——用的都是二进制帧。这时候就需要动Fiddler的脚本了。
4.1 Fiddler脚本的入口和基本结构
Fiddler Classic的脚本在Rules → Customize Rules,打开的是一个JScript文件(CustomRules.js)。这个文件里有一堆OnXxx开头的函数,Fiddler在特定时机调用它们。跟WebSocket相关的主要是OnWebSocketMessage。
默认情况下这个函数可能是空的或者只有一行日志。我们要做的,就是在里面加逻辑:判断帧的方向和Opcode,如果是二进制帧,就按目标协议的格式去解析,然后把解析结果写到一个能看见的地方。
4.2 一个可用的二进制帧解析骨架
下面这段脚本演示了怎么把二进制帧的内容转成十六进制字符串并打印出来,方便你进一步分析:
function OnWebSocketMessage(oMsg) { if (oMsg.FrameType === "2") { // 二进制帧 var payload = oMsg.PayloadData; var hex = ""; for (var i = 0; i < payload.Length; i++) { var b = payload[i] & 0xFF; hex += ("0" + b.toString(16)).slice(-2) + " "; } FiddlerObject.log("WS二进制帧 [" + oMsg.MsgID + "] 方向:" + (oMsg.IsOutbound ? "发送" : "接收") + " 内容: " + hex); } }这段代码的关键点在于:oMsg.FrameType判断帧类型,oMsg.PayloadData拿到原始字节,oMsg.IsOutbound区分方向。FiddlerObject.log会把内容输出到Fiddler左侧的Log标签页里。
4.3 针对具体协议做结构化解析
光看十六进制还不够,真正的价值在于把字节按协议格式拆开。假设你的应用用的是"4字节长度 + 2字节消息类型 + N字节消息体"这种自定义格式,脚本可以这样写:
function OnWebSocketMessage(oMsg) { if (oMsg.FrameType === "2") { var p = oMsg.PayloadData; if (p.Length < 6) return; var len = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; var msgType = (p[4] << 8) | p[5]; FiddlerObject.log("消息类型:" + msgType + " 声明长度:" + len + " 实际负载:" + (p.Length - 6)); } }这里有个细节要注意:JavaScript里位运算默认按32位有符号整数处理,如果长度字段超过2GB会出问题,但实际场景里不可能有这么大的单帧,所以可以放心用。另外p[i]取出来的是字节值,直接参与位运算没问题。
4.4 把解析结果落到文件里方便回看
FiddlerObject.log的输出在会话多了之后会刷得很快,不好回溯。更实用的做法是写到文件:
var wsLog = "C:\\ws_debug.log"; function OnWebSocketMessage(oMsg) { if (oMsg.FrameType === "2") { var fso = new ActiveXObject("Scripting.FileSystemObject"); var f = fso.OpenTextFile(wsLog, 8, true); f.WriteLine(new Date().toTimeString() + " " + (oMsg.IsOutbound ? "OUT" : "IN") + " " + oMsg.PayloadData.Length); f.Close(); } }注意:Fiddler脚本里用
ActiveXObject是Windows特有的,Mac上的Fiddler Everywhere不支持这种写法。如果你在Mac上,需要用Everywhere提供的脚本API,思路一样但语法不同。
4.5 脚本调试的常见坑
改完脚本一定要点Save Script,然后重启Fiddler或者重新触发连接,因为脚本是在Fiddler启动时加载的,热更新不一定生效。另外脚本里任何一处语法错误都会导致整个脚本失效,Fiddler会在启动时弹窗报错,别忽略那个弹窗。我习惯在改脚本前先备份一份CustomRules.js,改崩了直接还原。
5. 抓不到、抓不全、抓出来是乱码的排查链路
配置都做了,脚本也写了,但结果不理想的情况太常见了。这一节我把几类典型症状和对应的排查路径整理出来,你可以按图索骥。
5.1 症状一:完全看不到101会话
如果连握手请求都看不到,按这个顺序查:
- HTTPS解密是否生效——回到第2节验证,这是最高频的原因。
- 流量是否经过Fiddler——确认应用走了系统代理,或者Fiddler的捕获开关是打开的(左下角那个
Capturing状态)。 - 过滤器是否误伤——清空Filters重试。
- 目标是否用了非标准端口——有些WebSocket服务跑在非常规端口上,确认Fiddler没有端口过滤。
5.2 症状二:看到101但WebSocket标签页是空的
握手成功但帧列表为空,通常是这两种情况:
- 连接建立后很快就断了——有些应用握手完立刻发一个心跳然后断开重连,帧可能在Fiddler刷新前就没了。解决方法是让应用保持连接,或者用脚本在
OnWebSocketMessage里实时记录。 - 帧走了不同的TCP连接——极少数情况下,某些实现会在握手后重新建立连接,导致帧和握手不在同一个会话里。这时候需要在Fiddler里按Host过滤,看是否有多个相关会话。
5.3 症状三:文本帧显示正常但二进制帧是乱码
这就是第4节要解决的问题。Fiddler对二进制帧的默认展示能力有限,必须靠脚本解析。如果你不想写脚本,另一个思路是用Fiddler的Inspectors → HexView看原始字节,然后拿这些字节到外部工具里按协议解析。但这种方式效率低,适合一次性分析,不适合持续调试。
5.4 症状四:帧内容被截断
Fiddler对单个帧的展示有长度限制,超长帧会被截断。如果你调试的是大消息体(比如文件传输、大JSON),需要在脚本里把完整内容写到文件,而不是依赖界面展示。另外,WebSocket协议本身支持分片(Fragmentation),一个逻辑消息可能被拆成多个帧传输,Fiddler会分别展示这些帧,你需要按FIN标志位把它们拼起来才能得到完整消息。
5.5 一张排查对照表
| 症状 | 最可能的原因 | 优先动作 |
|---|---|---|
| 看不到101会话 | HTTPS未解密 | 检查证书安装 |
| 有101无帧 | 连接过快断开 | 脚本实时记录 |
| 二进制乱码 | 未做协议解析 | 写OnWebSocketMessage脚本 |
| 内容截断 | 帧过长或分片 | 写文件+按FIN拼接 |
| 部分帧丢失 | 过滤器或进程范围 | 清空Filters全量抓 |
6. 几个让调试效率翻倍的实战习惯
配置和排查讲完了,最后分享几个我在实际调试中养成的习惯,这些细节文档里不会写,但能实实在在省时间。
第一,先用手写脚本发一条测试消息。在正式调试应用之前,我会先用浏览器的控制台或者一个简单的HTML页面手动建一个WebSocket连接,发一条已知内容的消息,确认Fiddler能完整抓到。这样能把"工具配置问题"和"应用本身问题"隔离开,避免在应用代码里瞎找。
第二,给脚本加时间戳和会话ID。多连接并发的时候,光看内容根本分不清哪条帧属于哪个连接。在OnWebSocketMessage里把oMsg.MsgID或者连接的URL一起打出来,回溯的时候一目了然。
第三,二进制协议先抓一次全量再分析。不要一上来就猜协议格式,先把原始字节完整抓一段,用十六进制对比几次交互,规律自然就出来了。长度字段、类型字段、校验位这些,看多了就有感觉。
第四,注意ping/pong帧的干扰。WebSocket有心跳机制,Opcode为9和10的ping/pong帧会频繁出现,它们没有业务负载。在脚本里用oMsg.FrameType过滤掉,日志会干净很多。
第五,Fiddler Everywhere的WebSocket界面可以直接导出。如果你用Everywhere,帧列表支持导出成文件,比Classic手动复制方便。团队协作的时候,把导出文件发给同事,对方不用配环境就能看。
这套流程走下来,从握手捕获到二进制解析再到问题排查,基本覆盖了Fiddler抓WebSocket的完整链路。真正上手的时候,建议先拿一个公开的WebSocket测试服务练手,把配置和脚本跑通,再切到自己的目标应用上,这样出问题的时候你心里有底,知道是哪一环的事。