ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

iOS网络调试:请求发出、系统代理与绕过代理排查指南

iOS网络调试:请求发出、系统代理与绕过代理排查指南 做iOS开发和网络调试的人都知道最头疼的问题不是接口报错而是请求压根没动静。代码里调了接口界面上没有任何反应抓包工具里也看不到一条记录。这时候你脑子里会蹦出三个问题请求到底发出去了没有它有没有按系统的代理设置走还是说app内部直接绕过了代理自己把数据拉回来了我在实际排查中这三件事几乎每次都要挨个验证一遍它们互相纠缠、线索混淆稍不留神就会把方向带偏。这篇内容就围绕iPhone网络调试把这三件事掰开揉碎讲清楚适合做iOS开发、客户端测试、前后端联调以及被“抓不到包”折磨过的读者参考。1. 先理清排查思路请求从代码到服务器到底经过哪几道门1.1 这次调试要回答的三个问题本质上是三件事标题里三个关键词请求是否发出、是否经过系统代理、app绕过代理。这三件事在iOS上并不是一一对应的很多人排查不通是因为把它们当成了一条线。请求发出了不等于走了系统代理——这是第一个概念。走了系统代理不等于你在Charles里能看到——这是第二个概念。app如果自己绕过了代理那问题就更隐蔽因为它在应用层甚至系统HTTP层根本不留痕迹。所以先建立这个认知一次iPhone上的网络请求从代码产生到真正到达服务器要经过应用层、系统网络栈、底层传输、服务器响应四个环节。应用层就是你的业务代码比如URLSession、NSURLConnection、第三方SDK系统网络栈负责根据URL、DNS、代理设置来建立连接底层传输就是TCP和TLS的握手、发包服务器响应则原路返回。绝大多数人习惯直接在业务代码里看有没有回调或者在Charles里看记录但这两者覆盖的范围完全不同。我经常遇到回调正常、但数据其实是本地缓存的情况这种不抓包根本无法定性。1.2 系统代理在iPhone网络里到底是什么角色iOS的“系统代理”通俗讲就是你在一台iPhone的“设置 → 无线局域网 → 已连接Wi-Fi的i图标 → HTTP代理”里配置的那串服务器地址和端口。它本质上是HTTP代理也就是说iOS系统网络栈会把发往80、443端口的HTTP和HTTPS流量先发到代理服务器再由代理服务器去请求目标地址最后把响应原样带回来。Charles、Fiddler这类工具的工作原理就是把自己伪装成一个HTTP代理从而看到所有经过它的明文请求。但有个关键点必须记住这个系统代理只对“使用系统网络栈”的请求生效。如果你的app用的是Network.framework、BSD socket或者自己封装了原始TCP连接走不走代理完全取决于代码怎么写。这也是很多人困惑的来源——我把代理配好了Charles却一片空白好像手机断网了一样。其实不是断网是流量根本没按你设想的路线走。顺带说一个调试时容易忽略的细节DNS解析和keep-alive连接并不一定走HTTP代理。比如你用URLSession请求一个域名DNS解析还是在本地进行只有真正建立HTTP连接时代理服务器才会知道目标URL。这也是为什么某些抓包工具抓不到DNS请求、只能看到TCP层请求的原因。1.3 app绕过代理的三种典型实现以及背后的合理业务动机所谓“绕过代理”在iOS开发里有几种比较典型的实现。第一种在URLSessionConfiguration里主动把connectionProxyDictionary设置为空字典或者设置为“禁用代理”。这是最直白的方式等于告诉系统这个session的请求不要走代理直连目标。第二种使用Network.frameworkNWConnection、NWConnectionGroup或者更底层的BSD socket。这类API根本不看系统HTTP代理配置因为它不关心HTTP协议直接就是TCP/UDP层面的连接。凡是涉及实时音视频、游戏、IoT设备的app基本都会走这条路。第三种用内置私有APN设置或者第三方HTTP库直连IP。这里要解释一下“直连IP”当URL里的主机名变成了IP而不是域名部分代理逻辑会直接放行因为代理规则经常按域名匹配。遇到这种情况Charles同样看不到。从开发者的角度看app绕过代理并不总是坏事甚至有充分的业务理由超低延迟的直播流、局域网内设备通信、连接池复用、网络状态自动切换等等。但副作用就是——开发调试时要抓包传统代理抓包工具就没用了。我接手过一个项目某个SDK为了降延迟自己维护了一套连接池用原始socket连接服务器所有业务请求都走这个池子。结果现场要定位线上问题Charles什么都看不到我和同事折腾了大半天才发现端倪。所以排查前务必先问一句这个app的网络栈到底由谁负责是系统的URLSession还是某个SDK自建了通道。2. 关键细节拆解怎么确认请求发出去、走了代理、还是被绕过2.1 验证“请求是否发出”的三板斧第一板斧代码断点。在发起请求前和请求后各打一个断点确认执行路径有没有真的走到网络调用。但要注意断点只能证明代码执行到了这一行不能证明数据包真的离开了手机。比如请求可能被缓存层拦截、被ATS拦截、被URLProtocol拦截这些都不会体现在断点上。第二板斧统一日志。在URLSession的delegate回调或者completion handler里打日志输出请求URL、HTTP状态码、耗时以及响应体大小。这个操作成本低、信息全是所有排查手法里的首选。我建议用os_log而不是print这样在Xcode控制台里可以按子系统过滤导真机日志也方便。第三板斧底层抓包。用Wireshark或tcpdump抓Wi-Fi网卡看有没有TCP SYN包发出、有没有三次握手完成、有没有HTTP请求明文仅限非HTTPS。这是最确凿的证据但要注意HTTPS下看到的是TLS加密后的报文只能判断连接层面看不了内容。实际处理过的Case里我更推荐先做统一日志。因为它在应用层直接给你“发起→响应”的完整时间线等信息确认了再决定要不要上Wireshark——可以省掉很多来回折腾的时间。2.2 判断请求是否走系统代理三个位置的证据第一个证据是Charles或Fiddler的记录。如果你开启了系统代理同时app走的又是URLSession理论上所有HTTP和HTTPS请求都会出现在代理工具里。此时看不到多半是没走代理。但别忘了做SSL Proxying否则HTTPS请求只能看到一个CONNECT隧道看不到具体路径和内容。第二个证据是Wireshark抓到的协议交互。你配置系统代理后iOS会直接跟代理服务器建立TCP连接Wireshark里会看到目标IP不再是原来的服务器IP而是代理服务器的IP。如果底层抓包看到的还是目标服务器IP说明代理要么没生效要么被绕过了。这个观察方式在很多联动调试场景下非常有用。第三个证据是代理服务器自身的访问日志。比如你本地跑了Squid直接看Squid的access.log能精确列出每个URL请求。这个方法适合在线上环境辅助排查比Wireshark更聚焦本地调试倒是很少用。2.3 识别“app绕过代理”的代码特征最快的方式就是直接看代码。在iOS工程里搜索这些关键词connectionProxyDictionary、proxySettings、kCFNetworkProxiesHTTPEnable、NWConnection、socket.connect。如果出现connectionProxyDictionary的赋值请注意它的内容置为{}代表禁用代理置为一组kCFNetworkProxies*键值对代表手动指定了代理绕开了系统配置。如果用了Network.framework基本可以断定不会走系统HTTP代理因为API层面就没有这个概念。此外还有一类第三方库要特别小心像socket.IO这类实时通信库默认就是直连使用时甚至不会告诉你要接代理逻辑。这类库出问题的时候特别容易让人误判成“Charles设置有问题”。还有一种更隐蔽的情况app在启动时读取了远程配置中心的代理开关动态决定是否绕过系统代理。这种动态配置在线上排查时非常坑因为你看到的代码逻辑可能是“正常走代理”但运行时却拿到了“关闭代理”的配置。2.4 工具选型Charles、Wireshark、tcpdump怎么分工抓HTTP/HTTPS请求Charles最省心特别是它能把请求和响应格式化成可读树形结构非常利于调试。但碰到WebSocket、TCP、UDP这类非HTTP流量Charles就无能为力了。Wireshark是看底层交互的万金油但明文字段需要自己过滤学习成本相对高。tcpdump适合命令行环境常用于服务器端或远程环境诊断。我日常的组合是先用Charles判断流量有没有走近路代理再用Wireshark看网络层的完整交互。绝大多数“请求消失了”的问题都能在这两步组合里找到答案。记住一个原则每一层工具只能看到它那一层的证据别指望一把工具解决所有问题。3. 实操记录一步步搭建调试环境抓出“看不见的请求”3.1 基础环境把iPhone的流量导向Charles先在自己的Mac上装好Charles打开Proxy → SSL Proxying配置允许SSL代理的域名。如果只是本地测试可以直接用通配符。然后用iPhone连到同一个局域网在Wi-Fi设置里把“HTTP代理”改成“手动”服务器填Mac的局域网IP端口默认8888。此时Charles会弹出一个“是否允许来自此设备的连接”点击允许。这里有个极其常见的错误手机和电脑连了同一个Wi-Fi但是开了AP隔离两者根本不通Charles自然是空的。之后打开iPhone上的任意app理论上Charles里就能看到请求记录。如果此时只有CONNECT记录没有解密后的内容说明SSL Proxying没配好或者证书没装。去chls.pro/ssl下载根证书安装后还要在“设置 → 通用 → 关于本机 → 证书信任设置”里手动打开信任开关。iOS 10.3之后这个步骤必须做很多人卡在这里。3.2 跑一次普通URLSession请求确认默认链路是通的我通常会在工程里放一段测试代码先把“代码 → URLSession → 系统代理 → Charles → 服务器”这条链路跑通。比如请求https://httpbin.org/getlet config URLSessionConfiguration.default let session URLSession(configuration: config) let url URL(string: https://httpbin.org/get)! let task session.dataTask(with: url) { data, response, error in print(resp:, response ?? error ?? no response) if let data data { print(data:, String(data: data, encoding: .utf8) ?? ) } } task.resume()运行时Charles里能看到一条GET https://httpbin.org/get的记录状态码200。这意味着默认链路是通的。如果这一步都不通后面排查任何高级问题都没有意义。很多团队的iOS联调环境其实很脆弱代理、证书、网络隔离轮流出问题先把这条基础链路稳住了后面的疑难杂症才有参照系。3.3 复现“app绕过代理”的典型场景为了让读者看清现象我在同一个工程里加了一个绕过代理的Sessionlet bypassConfig URLSessionConfiguration.ephemeral bypassConfig.connectionProxyDictionary [:] let bypassSession URLSession(configuration: bypassConfig) let bypassTask bypassSession.dataTask(with: url) { data, response, error in print(bypass resp:, response ?? error ?? no response) } bypassTask.resume()注意我把connectionProxyDictionary设置为一个空字典。运行后Charles里不会出现任何与该请求相关的记录但代码的callback正常数据也正常返回。也就是说——请求确实发出去了但它没走系统代理。为了确认它在网络层是真的发出了我打开Wireshark抓iPhone所在的Wi-Fi流量能看到手机直接向httpbin.org的IP发起了TCP连接目标地址完全绕开了Mac上Charles所在的IP。这就是“app绕过代理获取数据”在底层最直观的证据。很多读者会问我没有Wireshark怎么办其实你可以在Mac上跑sudo tcpdump -i en0 port 80 or port 443再把iPhone的流量引过来也可以看到类似的TCP连接过程只是没有Wireshark的图形界面方便。3.4 另一种“看起来没走系统代理”的情况自建代理参数除了直接禁用代理还有一种情况更容易踩坑app自己在代码里指定了一个代理但用的不是系统配置。比如你把代理服务器指向某台特定ipNSDictionary *proxySettings { (__bridge NSString *)kCFNetworkProxiesHTTPEnable: YES, (__bridge NSString *)kCFNetworkProxiesHTTPProxy: 192.168.1.100, (__bridge NSString *)kCFNetworkProxiesHTTPPort: 8888 }; NSMutableURLRequest *request [NSMutableURLRequest requestWithURL:url]; NSURLSessionConfiguration *config [NSURLSessionConfiguration defaultSessionConfiguration]; config.connectionProxyDictionary proxySettings; NSURLSession *session [NSURLSession sessionWithConfiguration:config];这段代码如果跑起来请求会走到192.168.1.100那个代理但你的Charles如果监听的是Mac自己的端口就什么也看不到。这个问题在团队联调时相当常见我遇到过好几次同事拿着自己电脑上的Charles排查结果app里的代理配置指向了另一个同事的电脑IP双方白白对了好久的日志。3.5 确认绕过代理之后还能怎么继续抓包一旦确认app绕过了代理调试并不等于结束。你有三条路可以走。首选方案把抓包点下沉到网络层。用Wireshark抓TCP甚至在iOS 14及以上系统里用sudo rvictl创建虚拟网卡然后直接抓iPhone的流量。配合TLS会话密钥注入可以解密HTTPS流量不过这套方案相对复杂适合深水区排查。次选方案临时改代码支持代理。在排查窗口期把connectionProxyDictionary清空或者把Network.framework临时换成URLSession等验证完再回滚。虽然是临时改动但能最快锁定问题范围。要注意改完代码需要重新构建签名真机调试会比较耽误时间。最稳妥的长期方案在app的调试配置里增加一个开关开发时可以选择“使用系统代理”还是“绕过系统代理”。不少大厂的网络库都内置了这个开关线上排查问题时价值极高。如果在项目初期就把这个开关设计进去后面能省下很多加班时间。4. 常见问题与避坑实录4.1 Charles一片空白第一步先查什么先确认手机和电脑在同一局域网再检查手机代理的IP和端口。如果都没问题请打开Charles的Proxy → Access Control Settings确认允许的连接来源范围包含手机IP。我见过最多的失误就是这个——手机和电脑明明都在同一个Wi-Fi里Charles却把连接请求直接拒了。其次是证书信任问题iOS更新到新版本后对本地证书的要求会更严格但Charles证书严格来说是自签名证书必须手动信任。如果你能看到CONNECT记录但看不到具体内容排查方向就锁定在SSL Proxying和证书信任上。记住在这一步钻牛角尖没有意义先把基础链路跑通再谈后面的内容解不加密。4.2 请求明明走了代理但Charles显示连接重置这种情况多半与目标服务器对TLS指纹的限制有关。Charles在解密HTTPS时会以中间人的身份和服务器建立一个新的TLS连接。部分服务端能够识别这种非标准握手直接断开。遇到这种问题一般没有太好的解决办法要么放弃解密用Wireshark看密文交互要么在服务端临时加入信任本地验证完再移除。另外一个容易被忽略的原因是HTTP代理本身的代理认证。如果你的代理需要用户名密码而iOS侧的代理设置里没有填连接同样会被代理服务器拒绝。Charles默认不启用认证但线上代理往往有认证要求这一点在排查公司统一代理时特别关键。4.3 真机、模拟器与代理的关系常常被混淆模拟器并不严格走你在“模拟器自己设置”里的代理。因为模拟器的网络栈和Mac共享它实际上会直接绕开你在iOS设置里配置的代理在宿主机层就被网络过滤了。而真机是完全独立的必须单独在Wi-Fi设置里配代理。很多人用真机调试却忘了这一步于是Charles只能看到模拟器的流量真机的流量一点影子都没有。还有一个iOS 15之后的新坑系统代理和“私有无线局域网地址”存在交互某些环境下代理会间歇性失效。如果遇到代理时好时坏可以先关闭Wi-Fi的随机MAC地址功能再试。4.4 我常用的排查路径速查表现象优先排查方向辅助工具Charles无记录callback正常app是否绕过代理代码检索connectionProxyDictionary、NWConnectionCharles只有CONNECT无内容SSL Proxying / 证书未配置chls.pro/ssl、证书信任设置Charles无记录callback失败网络层是否发出请求Wireshark、tcpdumpCharles有记录但返回异常代理服务器、DNS、目标服务器问题curl测试、服务器日志Wireshark看到SYN未握手网络权限、防火墙、AP隔离更换AP、关闭防火墙测试这张表是我在实际调试过程中慢慢总结出来的通常照着走一遍九成以上的问题都能定位。特别注意第二行和第五行它们都表现为“抓不到包”但一个出在应用层一个出在网络层排查路径完全不同。实际工作中我最深的体会是只要把“请求是否发出”和“是否走代理”这两件事分开验证大部分疑难杂症都会变成可见的常规问题。尤其是遇到Charles看不到的请求先别急着怀疑抓包工具把排查思路往前推一步——确认app的网络栈到底由谁负责再决定用哪把工具、看哪层证据。这个思路一度帮我少加了很多班。最后再分享一个建议如果你正在搭建一个新的iOS项目最好从第一天就把代理开关设计进网络库的调试配置中心用的时候一键切换“走系统代理”和“直连”这样既省去反复改代码的麻烦也能在线上问题出现时快速判断到底该查应用层还是网络层。
返回列表