Network抓包实战:从原理到前端调试技巧全解析

司马瑞珺 移动 阅读 1,583
赞 11 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

做移动端开发这几年,抓包调试网络请求几乎是家常便饭。我一开始也用过各种花里胡哨的工具,但后来发现,最稳的还是用浏览器自带的 DevTools + 真机代理组合。尤其是现在 Safari 的 Web Inspector 对 iOS 调试支持越来越好了,配合 Charles 或 Proxyman,基本能覆盖 90% 的场景。

Network抓包实战:从原理到前端调试技巧全解析

我一般这样处理:先在手机上设置 HTTP 代理指向电脑(比如 192.168.x.x:8888),然后在 Charles 里开启 SSL Proxying,把目标域名加进去(比如 https://jztheme.com)。关键一步是:必须在手机上安装 Charles 的根证书,并在「设置 > 通用 > 关于本机 > 证书信任设置」里手动开启信任。这一步我踩过好几次坑——不信任证书的话,HTTPS 请求直接失败,还看不出原因,折腾半天才发现是证书问题。

如果你用的是 Android 7+,系统默认不信任用户安装的 CA 证书,这时候要么降级到 Android 6 测试,要么在 App 的 network_security_config.xml 里显式允许用户证书。不过大多数时候,我们只是调试 H5 页面,所以真机 Safari + Mac 的组合更省事。

这几种错误写法,别再踩坑了

很多人一上来就直接用 fetch 或 axios 发请求,然后在 DevTools 里看 Network 面板,以为这就完事了。但移动端经常遇到「请求发出去了,但 Network 面板啥也没有」的情况——这大概率是因为页面被 Service Worker 缓存了,或者请求被拦截后没走真实网络。

错误写法一:只依赖浏览器 Network 面板,不检查缓存头。比如:

fetch('/api/user')
  .then(res => res.json())
  .then(data => console.log(data));

这种写法在开发环境没问题,但上线后如果服务器返回了 Cache-Control: max-age=3600,下次请求就直接从 memory cache 读了,Network 面板里看不到真实请求。我建议强制禁用缓存,至少在调试阶段:

fetch('/api/user', {
  cache: 'no-cache',
  headers: {
    'Cache-Control': 'no-cache',
    'Pragma': 'no-cache'
  }
})
.then(res => res.json())
.then(data => console.log(data));

错误写法二:在抓包工具里看到 200,就以为接口正常。其实很多后端为了“友好”,即使出错也返回 200,然后在 body 里塞个 { code: 500, msg: 'error' }。这时候你得点开 Preview 或 Response 看具体内容,不能光看状态码。我曾经因为这个,误判了一个支付接口是成功的,差点酿成事故。

错误写法三:忽略 CORS 预检(OPTIONS)请求。有些同学在抓包时只关注主请求,但 OPTIONS 请求失败也会导致整个调用挂掉。特别是在请求头里加了自定义字段(比如 X-Token)时,浏览器会先发 OPTIONS。如果后端没正确处理 OPTIONS,主请求根本不会发出去。这时候 Network 面板里能看到一个灰色的 OPTIONS 请求,状态是 (canceled) 或 (failed),但很多人直接忽略了。

实际项目中的坑

去年做个项目,H5 嵌在 App WebView 里,线上用户反馈数据加载不出来。本地调试一切正常,抓包也看不出问题。折腾了半天,最后发现是 App 的 WebView 没有传递 User-Agent,而后端做了 UA 判断,非移动端 UA 直接返回空数据。这种问题在 DevTools 里根本模拟不出来,必须用真机 + 抓包才能发现。

还有一次,接口返回的数据明明是 JSON,但 Network 面板里显示为 “plain text”,导致 res.json() 报错。查了半天,原来是后端少写了 Content-Type: application/json 响应头。虽然浏览器能自动解析,但严格模式下 fetch 会报错。这种细节,只有仔细看 Headers 才能发现。

另外提醒一点:移动端网络不稳定,经常出现请求超时、DNS 解析失败等问题。抓包时如果看到请求长时间 pending,别急着说是代码问题,先看看是不是网络抖动。我一般会同时开两个设备对比,或者用 curl 在终端复现,排除环境干扰。

对了,iOS 14+ 默认开启了 Intelligent Tracking Prevention(ITP),会限制第三方 Cookie 和 localStorage 的持久化。如果你的请求依赖 cookie 认证,可能会在某些情况下突然失效。这时候抓包能看到请求发出去了,但响应是 401。解决方案要么用 token 放 header,要么确保域名是一致的(避免跨域)。

核心代码就这几行

为了方便调试,我通常会在项目里加一个全局的请求日志中间件。比如用 axios 的话:

// request interceptor
axios.interceptors.request.use(config => {
  console.log('[REQUEST]', config.method?.toUpperCase(), config.url, config.params || config.data);
  // 开发环境可加时间戳防缓存
  if (process.env.NODE_ENV === 'development') {
    config.params = {
      ...config.params,
      _t: Date.now()
    };
  }
  return config;
}, error => Promise.reject(error));

// response interceptor
axios.interceptors.response.use(response => {
  console.log('[RESPONSE]', response.config.url, response.status, response.data);
  return response;
}, error => {
  if (error.response) {
    console.error('[ERROR]', error.response.config.url, error.response.status, error.response.data);
  } else {
    console.error('[NETWORK ERROR]', error.message);
  }
  return Promise.reject(error);
});

这样即使在没有抓包工具的情况下,也能通过 console 快速定位问题。当然,线上记得关掉,不然用户打开控制台能看到所有请求细节,有安全风险。

如果是原生 fetch,也可以简单封装一下:

function debugFetch(url, options = {}) {
  const startTime = Date.now();
  console.log([FETCH START] ${url});
  
  return fetch(url, {
    ...options,
    cache: 'no-cache'
  })
  .then(response => {
    const duration = Date.now() - startTime;
    console.log([FETCH END] ${url} (${response.status}) in ${duration}ms);
    return response;
  })
  .catch(error => {
    console.error([FETCH ERROR] ${url}, error);
    throw error;
  });
}

踩坑提醒:这三点一定注意

  • 证书信任必须手动开启:iOS 和 Android 安装抓包工具证书后,默认是不信任的,必须去系统设置里手动开启,否则 HTTPS 请求直接失败。
  • 不要只看状态码:200 不代表成功,要看响应体内容;(canceled) 可能是 CORS 或证书问题,不是代码 bug。
  • 真机和模拟器行为不一致:有些网络策略(比如 DNS 缓存、HTTP/2 支持)在模拟器和真机上表现不同,关键路径一定要用真机验证。

以上是我个人对 Network 抓包的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,比如结合 Performance API 分析请求耗时、用 Puppeteer 自动化抓包等,后续会继续分享这类博客。希望这些踩坑经验能帮你少走弯路。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论