有时候,Bug 并不体现在程序错误上,而是行为偏差。在一次常规功能测试中,我们发现移动端某个提交请求被触发了两次,虽然后端做了幂等处理,但频繁请求仍可能带来性能问题、错误日志膨胀、以及潜在副作用。
这类问题常被归类为“无影响的冗余请求”,但我们决定 彻查触发路径与请求内容差异,确保系统行为在各种网络和设备条件下都能一致。
本文记录了我们如何通过多个抓包工具协作,从客户端真实行为开始,逐步确认问题成因并设计验证手段。
目标接口为内容收藏操作接口。日志中部分用户在点击“收藏”按钮后,同一秒钟内发起了两次 POST 请求,唯一标识相同,仅参数顺序略有不同。重复请求并未导致业务错误,但触发了重复打点和错误日志记录。
后端日志已明确标出“某些用户双次请求”,但客户端代码中绑定事件的逻辑没有重复。我们决定从“请求层”而不是“代码逻辑”入手,验证真实触发过程。
我们测试时使用三种终端:
分别构建抓包环境:
| 工具 | 使用场景 | 理由 | | ----------- | ---------------------------------------- | --------------------------- | | Charles | 抓桌面端和H5请求 | 快速配置代理,界面清晰 | | Sniffmaster | 抓取iOS端点击收藏后的完整HTTPS数据 | 无需越狱,解密HTTPS请求结构 | | mitmproxy | 添加拦截脚本,记录请求时间间隔与字段序列 | 便于分析参数排序逻辑 | | Wireshark | 捕获低层网络重传/断线重连可能 | 辅助验证 TCP 层触发行为 |
我们进行了5次不同网络环境下的收藏请求测试,记录结果如下:
使用 Sniffmaster 抓到的第一个请求结构完整,携带认证信息;第二个请求字段顺序变化、缺少特定 header,推测由缓存逻辑触发。
我们进一步将这些请求导出,使用脚本对字段逐个比对:
```python
def diff_keys(req1, req2): for k in set(req1) | set(req2): if req1.get(k) != req2.get(k): print(f"{k}: {req1.get(k)} != {req2.get(k)}") ```
结果明确指出第二次请求字段精简,可能为“后补请求”或“失败重发”。
为了确认是否为 App SDK 中的重试逻辑触发,我们借助 mitmproxy 添加延迟响应模拟:
python
def response(flow):
if "/collect" in flow.request.url:
import time
time.sleep(1.5) # 模拟服务端超时响应
添加该脚本后,App 端再次触发重复请求,且第二次请求体与日志完全一致。验证 App 在响应超过特定阈值后,自动重发该请求,说明问题并非“事件被绑定多次”,而是 SDK 逻辑层的超时重试机制未做去重处理。
我们最终定位问题源于:
解决方案:
单一工具并不能支撑从“重现现象”到“还原过程”到“构造条件”再到“验证结果”的完整链条。我们每个阶段只用了它最擅长的工具:
这种“任务拆解式”调试方式,让问题不是“抓到了”,而是“理解了”。
如果你也经常遇到“行为不一致但日志正常”的问题,不妨试着建立一套多工具协作的抓包分析流程。不是为了用工具炫技,而是为了让每一个网络行为都被还原、每一次异常都有解释。
真正高效的调试流程,不靠“哪个工具最全”,而靠“流程拆得够细、每步工具用得刚好”。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70046796/viewspace-3088234/ ,如需转载,请注明出处,否则将追究法律责任。