1. 工具和日志
工欲善其事,必先利其器。应用开发和问题排查的方法论完全不一样,应用开发强调的是从无到有的构建方法,而问题排查强调的是抽丝剥茧的分析之法。问题排查的基础是建立在对程序”预期行为“的理解和掌握上,任何偏离预期的行为都是解决问题的线索。程序的”行为“活动除了问题本身的症状表象,更多的内容则记录和体现在内部或外部日志中,从日志中观察异常行为,再作出合理推测是排查过程的基本要素。在离线包相关问题的排查中,内部日志主要是app控制台日志,H5页面控制台日志;外部日志主要包括:HTTP应用层网络包和TCP层网络包。根据症状正确地使用工具去捕获到合适的日志是问题分析的基石。这一章节主要介绍三个实用工具的具体使用步骤。
1.1. HTTP报文捕获工具
客户端会持续不断的和服务端通过HTTP协议进行交互,以获取各种数据和配置信息等。交互过程中产生的HTTP报文对问题排查往往非常有效。例如,当观察到存在请求报错时,可以通过分析请求报文和响应报文,查看请求的信息是否存在错误、服务器是否正常返回以及有没有根据实际情况下发等,初步判断问题所在。常用的HTTP抓包工具有两个:Fiddler和Charles。两者的基本原理几乎一样:在本地架设一个的HTTP Proxy,所有通过该Proxy的HTTP报文均可以被截获和解析。需要注意的是,对于HTTPS协议报文的解析需要提前在客户端配置好Charles/Fiddler的CA Root证书,保证“中间人”转发的信息被客户端信任,从而实现报文的解密。
注意:Charles/Fiddler只能对HTTPS报文本身进行解密展示。在是实践中,开发者可能会数据先做一次离线加密操作(如MGS的数据加密功能),再经过HTTPS进行通讯。这部分的原始内容是无法被Charles/Fiddler解密的,只能展示离线加密后的内容。
1.1.1 Charles (Mac OS 平台)
1.1.1.1. 安装与基本界面
从Charles官网((https://www.charlesproxy.com/)[https://www.charlesproxy.com/])下载Charles 4.5.5的dmg安装包。运行安装包并安装到系统中。
首次启动Charles时,会请求给予设置系统代理的权限,设置允许。启动后,当有HTTP请求经过Charles时,应该可以在Charles主界面中见到这些请求,如下图。
1.1.1.2. 客户端配置
- 在本机上运行的模拟器所产生的HTTP请求默认地会走系统Proxy,因此无需手动配置Proxy信息。
- 对于物理移动设备(iPhone/Android手机)则需要手动配置Proxy,保证其所产生的网络请求均经过Charles Proxy转发。
- 保证移动设备可以通过IP直接访问到安装Charles的Mac机器,建议移动设备和安装Charles的机器处于同一网络下。
- 查看Charles的Proxy配置,记住Proxy端口号:点击
Proxy
->Proxy Settings
,打开Proxy配置页面,如下图
默认端口号为8888
- 打开系统网络设置,查看本机IP地址:
- 配置移动端Proxy设置,已iOS设备为例,设置->无线网络->对应WIFI设置,添加Proxy(Charles机器)的IP地址和端口号。
- 移动端配置成功后,移动端首次请求到达Charles时会有如下提示,点击允许:
- 应该可以在Charles中见到移动设备产生的HTTP请求。例如,通过手机浏览器访问http://www.antfin.com/ ,可以在Charles中见到该请求,见下图:
- 打开系统网络设置,查看本机IP地址:
1.1.1.3. SSL Proxying设置
在默认情况下,Charles不会对HTTPS报文进行解密,如果需要分析HTTPS的报文内容,需要配置好SSL Proxying功能。主要有两部分:
- 设备端(模拟器和真机都要安装)安装并信任Charles Root CA;
- Charles上配置好需要解密的HTTPS站点。
1.1.1.3.1 设备端配置
- 在Charles开启的状态下,移动端通过浏览器(iOS请使用Safari)访问 chls.pro/ssl ,浏览器会自动下载charles-proxy-ssl-proxying-certificate.pem并提示安装
Charles Proxy Customer Root Certificate
。
Android会需要对证书命名,并安装在用户信任凭据(Customer Certificate)中。
- iOS 10以上系统,需要进入
设置
->通用
->关于本机
->证书信任设置
,对上一步安装的Charles证书启用完全信任。
- 需要注意的是,对于Android App,需要通过增加
配置网络安全选项
的方式来信任用户信任凭据。
a. 在Portal工程中增加一个Network Security Configuration XML资源放到res/xml/network_security_config.xml
;
XML的内容如下:
b. 更新AndroidManifest.xml
文件,配置使用上面的network security configuration:
android:networkSecurityConfig="@xml/network_security_config"
... >
...
信任User CA的样例代码项目:3-1-trust-user-CA.zip
- 如果本机(Mac)需要信任Charles证书,请通过
Help
->SSL Proxying
->Install Charles Root Certificate
3.1.1.3.2 Charles配置
在Charles菜单栏选择Proxy
-> SSL Proxy Settings...
,在SSL Proxying选项卡中可以添加需要进行HTTPS报文解密的域名和端口,并勾选Enable SSL Proxying
对于mPaaS公有云用户,需要增加的域名包括:
对于私有云用户,参考上述配置,加入自定义域名。
*.aliyun.com
*.alipay.com
*.aliyuncs.com
上述配置完成后,应该可以在Charles中看到HTTPS报文的内容,例如:
配置前:HTTPS报文处于乱码状态
配置完成后:HTTPS报文内容被解密
1.1.1.4 小结
在本节中,简单介绍了Charles的原理、安装、配置和HTTPS解密配置的内容。Charles在Mac端是相当重要的HTTP报文分析工具,后续的小节会针对问题案例做HTTP报文分析的详细解释。
参考文档:
- https://www.charlesproxy.com/documentation/welcome/
- https://zhuanlan.zhihu.com/p/26182135
- https://developers.google.com/admob/android/charles
1.2. TCP日志捕获工具
网络是移动应用生命线,网络层面的各种问题会给移动应用带来许多迷惑的行为和症状。Charles和Fiddler可以帮助捕获和分析HTTP层面的问题,如果问题发生在TCP/IP层面,则需要TCP报文的捕获和分析工具。Wireshark(支持Mac/Windows平台)、Network Monitor(Windows 平台)和tcpdump是常用的三种网络层抓包工具。比较常见的网络层问题包括SSL握手失败和TCP链接中断、重发等。
1.2.1. 网络包抓取基础
抓包不难,难的是怎么抓,怎么正确地抓包。不再像Charles/Fiddler那样可以通过“中间人”代理模式来捕获报文,TCP报文的抓取一般是非侵入试,一般是监听在网卡接口上,直接进行TCP报文的“镜像”捕获。
在一般场景下,可以抓包的点比较多,可以在客户端抓(A),可以在中间设备上抓(B),也可以在服务端上抓(C),见下图。
不同点上抓取的包都有各自的局限性(只能说明部分链路的网络情况,不能简单推广覆盖到全链路上),因此需要结合症状和其他日志合理选取抓包方式。在某些极端的案例中,我们需要在客户端、中间设备和服务端同时抓包,才能相互交叉比对验证问题的根源。下面几个小结会介绍几种抓包模式
1.2.2. 常见抓包工具基本使用
1.2.2.1 Wireshark
i. 下载和安装
在Wireshark官网下载安装包:https://www.wireshark.org/
安装并启动,主界面如下(Mac和Windows版本界面略有差别):
ii. 启动抓包
在Wireshark主界面上,一般可以看到本机的网络接口:
已本机为例,双击Wi-Fi: en0
接口开始抓取该网卡接口上的网络包:
iii. 停止抓包
点击菜单栏上的红色停止按钮(CMD+E),停止抓包。
点击保存按钮(CMD+S)保存捕获的网络包,以便离线分析。
1.2.3. 手机端抓包
如3.2.1.中图示,针对客户端的TCP抓包可以在两个地方进行:客户端出口和网络接入点入口。本节介绍在Point A处的抓包方式。
iOS平台
iOS客户端出口抓包需要把iOS移动设备通过usb连接到macbook上,并在Mac上建立的一个该设备网卡的虚拟映射。Wireshark通过该虚拟网卡捕获iOS移动设备上的网络包。
- 获取iPhone的UDID
将iOS移动设备通过USB接口连接Mac上,然后在终端上使用如下命令获取iOS设备的UDID(Serial Number):
$ system_profiler SPUSBDataType
或者通过Xcode
->Window
->Devices and Simulators
查看UDID(Identifiler)
- 创建虚拟网卡映射
$ rvictl -s < Your Device UUID >
Starting device < Your Device UUID > [SUCCEEDED] with interface rvi0
rvi0
即虚拟网卡名
- 启动抓包(已Wireshark为例)
打开Wireshark,本地接口列表界面中出现了rvi0
。图示如下:
双击rvi0
进入抓包界面,进入默认自动开始抓包。
- 停止抓包
问题复现后,要停止抓包,在菜单栏点击结束
(CMD+E)按钮停止抓包,点击保存
(CMD+S)按钮保存报文。
Android平台:
Android客户端出口抓包需要提前获取设备root权限,通过adb在设备上调用tcpdump
命令实现抓包。
- 下载TCPDump for Android
下载地址:https://www.androidtcpdump.com/ - 安装TCPDump
通过如下命令把TCPDump安装到设备上,并赋予执行权限:
adb push tcpdump /data/local/tcpdump
adb shell chmod 6755 /data/local/tcpdump
- 启动tcpdump开始抓包
cd /data/local
./tcpdump -i any -p -s 0 -w /sdcard/myCapture.pcap
tcpdump参数详细配置见:http://www.tcpdump.org/manpages/tcpdump.1.html
- 停止抓包,获取数据
问题复现后,需要停止抓包时,根据提示停止抓包(按下Ctrl+ C
)。通过如下命令将报文数据复制出来:
adb pull /sdcard/myCapture.pcap
1.2.3. 中间设备抓包
如3.2.1.中图示,针对客户端的TCP抓包可以在两个地方进行:客户端出口和网络接入点入口。本节介绍在Point B处的抓包方式。
- 抓包网络拓扑配置:
这种抓包模式的一种常见网络链接配置如下:
用一台Mac或PC作为抓包设备,该机器需要提前配置好双网卡,其中一块网卡需要有发射无线热点的能力。客户端通过WIFI连接到配置好的无线热点中,Mac或PC的另一块网卡接入正常网络。
- 启动抓包(以Wireshark为例)
打开Wireshark,找到手机接入的无线热点网卡(上图的网卡1)。双击该网卡标识开始抓包 - 停止抓包
问题复现后,要停止抓包,菜单栏点击结束
(CMD+E
)按钮停止抓包,再点击保存
(CMD+S
)按钮保存报文。
“中间设备”类型可能有很多种,本节介绍的只是其中一种。在实践中,可以用不同的工具在不同的设备上进行同类型的抓包工作。需要注意的是,尽量在第一跳接入设备上抓取,避免中间过程的干扰。
1.2.4. 服务端抓包
如3.2.1.中图示,某些问题需要在服务端启动抓包,本节以TCPDump为例。
- 安装tcpdump
在CentOS上安装:
yum install tcpdump
在Debian和Ubuntu上安装:
apt-get install tcpdump
其他情况的详细参见:https://www.tcpdump.org/
- 启动抓包
TCPDump本身可配置的参数较多,可以结合具体场景进行参数配置,参数详情见:https://www.tcpdump.org/#documentation
例如:
tcpdump -s 0 -w myCapture.pcap
- 停止抓包
问题复现后,按下Ctrl+C
停止抓包,将捕获的报文保存到合适的地方。
1.2.5. 小结
网络包的捕获首先需要结合场景和问题合理地规划抓包方式。因为场景比较丰富,可选择的工具也比较多,实践中需要根据实际的系统环境正确选择。本节提到三个工具均既可以作为抓包工具,也可以作为后期的报文分析工具。具体的分析过程不在此详述。
2. 问题诊断方法
“真相只有一个”。
故障和异常现象的排查工作实际上是一个探寻“真相”的过程,而所谓的“真相”可能埋藏在代码逻辑上、配置项里或外部环境中。面对问题,我们需要根据症状作出一个初步的判断,判断问题产生的可能原因。判断的依据可以来自经验,可以是根据现象的推理。如果现有的症状不足以帮助作出判断,则需要根据情况观察日志中的行为。从这些日志中体现出行为或错误信息上做进一步的判断。再然后就是小心求证这个所作出的判断,这个判断可能错误,可能正确,可能还不够深入。不断的重复这一步骤,直到发现真相,找到问题的解决方案。
2.1. H5容器与离线包的常见问题
根据离线包的技术特点和历史经验,与离线包相关的问题症状非常多,包括:
- 页面完全打不开;
- 页面打开白屏;
- 页面资源加载不正常,内容渲染不正确,页面功能不正常;
- 资源不是从本地获取,而是从线上获取;
- 离线包无法更新……等等等等
从类型上看,大致有三个大类:
- 加载失败
- 更新失败
- 离线失败(没有走本地资源)
从问题发生的位置上看,问题可能来自:
- 客户端
- 容器本身错误
- 前端代码错误
- 离线包验签错误
- 代码逻辑错误
- 组件bug 等等
- 服务端
- 离线包发布错误
- 离线包配置错误
- 后端bug 等等
- 网络层面
- 中间设备问题
- SSL握手问题
- TCP连接问题 等等
授人予鱼不如授人予渔。这里暂不详细解释各种问题和原因的细节,在实际的开发生产过程中,开发者遭遇的问题更是层出不穷、千奇百怪。回归本源,我们首先观察一下正常的情况下, 离线包的各种行为应该是什么样。
2.2. 从HTTP日志看H5容器和离线包的行为
2.2.1. 离线包的下载
mPaaS客户端框架对离线包的下载行为作出深度的封装,开发者往往并不直接控制离线包的下载行为。离线包的下载过程大致如下:
- 客户端向MDS服务端(cn-hangzhou-component-gw.cloud.alipay.com)发送请求,请求中提供了下载目标H5 App的ID;
- 服务端返回该离线包的相关信息(如果存在);
- 客户端根据返回信息中的
Package URL
并结合返回信息中的下载配置参数
,主动去下载离线包的amr文件。
1. 请求离线包信息:
2. 返回的离线包信息:
3. 客户端根据上一步拿到的离线包的Package URL去下载对于的amr文件:
离线包下载日志样本:1-offline-package-download-example.chls.zip
2.2.2.a. 离线包的更新
当开发者在离线包发布平台上发布一个新的离线包版本时,客户端拉取更新包的基本过程如下:
- 客户端向MDS服务端发送请求,请求中提供了需要更新的目标H5 App的ID和本地版本号;
- 服务端返回该离线包的相关更新信息(如果存在);
- 客户端根据返回信息中的
增量包地址
并结合返回信息中的下载配置参数
,主动去下载更新包的amr文件(如果没有增量包地址,则根据Package URL
下载全量包)。
1. 请求离线包信息:
2. 返回的离线包更新信息:
3. 客户端根据上一步拿到的增量包的URL去下载对于的amr文件:
离线包更新日志样本:2-offline-package-update-example.chls.zip
2.2.2.b. 更新全部离线包
iOS和安卓平台均提供了API实现一次请求所有离线包的更新信息,其基本过程如下:
- 客户端向MDS服务端发送请求,请求中提供了本地已安装的所有H5 App的ID和本地版本号,外加一个特殊的App ID:
nebula-*-all
; - 服务端返回所有符合条件的离线包信息(不在客户端版本范围内的,不返回);
- 客户端根据返回信息中的内容,主动去下载全量或增量的amr文件。
1. 请求离线包信息:
2. 返回的所有符合要求的离线包信息:
3. 客户端根据上一步拿到URL去下载所有的amr文件:
离线包全部更新日志样本:2-offline-package-update-all-example.chls.zip
2.2.3. 离线失败-fallback到线上
在某些情况下(例如,离线包本地验签失败),H5容器无法从本地获取所需的资源,客户端会转而从线上地址获取这些资源。基本过程如下:
- 客户端向MDS服务端发送请求,请求中提供了本地已有的H5 App的ID和本地的版本号;
- 服务端返回该离线包的相关信息(如果存在);
- 客户端根据返回信息中的
Package URL
并结合返回信息中的下载配置参数
,主动去下载离线包的amr文件。 - H5容器由于某些原因在本地获取离线包资源失败,则根据返回信息中的
fallback base URL
去线上(CDN)加载H5 App的应用资源(HTML/JS/CSS等文件,不是amr文件)。
1. 客户端正确获取到离线包信息,下载amr文件成功:
2. 客户端从fallback地址上下载H5应用资源:
Fallback模式日志样本:3-offline-package-fallback-example.chls.zip
2.2.4. 预置包
如果预置包配置成功,MDS服务端也不存在对应的更新发布,理论上在Charles中见不到下载amr的过程,更见不到fallback到线上的情况。
预置包的样例代码项目:
iOS:
32基线:4-offline-package-preset-sample-project-base-32.zip
60基线:4-offline-package-preset-sample-project-base-60.zip.zip
2.3. 初步分诊
病人去医院就诊时,护士台首先会根据患者的症状进行初步的分诊,把患者送到合适的科室做进一步的详细诊断。问题的排查也有类似的步骤,根据症状并结合日志上的表现,把问题初步划分到特定的组件或模块上,再集中力量进行深入地分析。如4.1所述,在mPaaS框架下,离线包相关的问题一般来自三个层面:客户端,服务端和网络层。
2.3.1. 基本“分诊”规则
a) 离线包应用加载更新异常
当离线包加载不正常时,如白屏、H5容器报错已经无法获取更新等。可以抓取HTTP网络包,根据网络包分析离线包的下载、更新和fallback等行为是否符合预期。
- 如果HTTP报文显示客户端请求异常(无请求或目标地址错误等),则问题多发生在客户端,需要检查接入方式和配置文件是否正确;
- 如果服务端返回异常,一般有两种情况:
- 如果客户端请求参数不正确(如签名错误)导致服务端返回错误码,一般属于客户端问题,结合错误码做进一步分析;
- 如果客户端请求参数正确,但是服务端返回异常(无返回或返回值不符合预期),可能是网络问题或服务端问题。
b) H5页面行为异常
当H5 App页面行为异常时(需要排查离线包加载问题),常见的有两种原因:
- 页面本身的问题;
- H5容器的问题;
页面逻辑问题(如JavaScrip代码错误,Mixed Content问题等),可以通过Safari/Chrome对H5页面进程调试,查看有无异常现象。
如果怀疑是H5容器本身的问题(如页面卡顿卡死等),可以利用下面的技巧进行初步的排除:
- 编写一个简单的原生App项目,在项目中使用原生的WebView加载目标页面;
- 对于离线包应用,可以把
fallback URL
作为调试的入口地址。相当于在原生WebView中加载一个普通的Internet站点。需要注意的是,在两个平台上,UIWebView和WKWebView,以及UCWebView和系统WebView本身也是细微差异的,测试的时候不妨把不同WebView的情况都考虑上。
iOS原生UIWebView和WKWebVIew的样例项目:
TODO:Android原生UCWebView和系统WebView的样例项目:
- 如果原生WebView可以重现问题,则大概率和容器本身无关,和页面逻辑或原生WebView有关(容器也是基于原生WebView的);
- 如果原生WebView上页面正常,则原始问题可能和容器的特性相关,需要进一步详细排查。
c) 页面与移动网关交互异常
页面与移动网关的交互一般是通过JsAPI发起RPC调用,如果PRC返回结果不正确,需要根据具体的错误进行进一步的排查。需要注意的是,RPC返回的错误码有三种类型:网关侧错误码,业务侧错误码和客户端错误码,参见网关结果码说明。
- 对于网关侧错误,一般和客户端或网关的配置相关;
- 对于业务侧错误,一般和后段应用服务相关;
- 对于客户端错误,根据具体的错误码,可能是客户端对返回数据的处理存在异常,需要查看原始的返回数据内容,也可能是TCP网络层存在异常,需要通过Wireshark抓取网络包做进一步分析。
2.4 小结
客户端的世界纷繁复杂,问题的原因极度发散,开发者需要通过不同的日志从不同的角度反复观察H5应用的行为。根据异常行为做出合理推测,拟定好排查方向。本节的内容和方法论是一个模糊的、概括的排查思路,由于客户端问题的特性,我们很难给出一个万全的诊断路径。在实际操作中,需要不断地积累经验,总结归纳问题的根因。
在下一节中,我们会分享一些具有代表性的问题和排查过程,希望帮助开发者进一步掌握H5容器和离线包相关问题的排查与解决。
3. 常见问题诊断案例
3.1. 离线包验签失败
离线包验签的原理与作用如2.2.4所述,验签失败并不直接导致H5应用不可用,但会导致H5容器无法从本地获取到H5应用的资源,所有流量都要fallback到线上,对用户体验和性能有影响。
因此,这种问题常见的症状可能是:1. 有网的情况下,离线包应用可以正常打开;关闭网络,离线包应用就无法打开;2. H5应用页面加载较慢。
遇到这种症状,可以做的基本检查动作如下:
- 确认控制台是否开启了离线包验签(上传了加签私钥);确认客户端H5容器是否启用了离线包验签功能。常见的配置组合及结果如下:
- ⚠️控制台开启/客户端开启:验签可能失败,失败的原因可能是验签密钥不匹配。
- ❓控制台开启/客户端关闭:问题可能与验签无关。
- ❌控制台关闭/客户端开启:验签必然失败。
- ❓控制台关闭/客户端关闭:问题可能与验签无关。
⚠️注意:客户端验签的配置是否生效是该项检查一项容易忽略的地方。
- 抓取Charles/Fiddler数据包,确认是否有amr下载动作,确认是否有fallback流量(如果4.2.3.所述)。
- ⚠️如果观察到存在对应离线包的amr下载动作,且存在fallback流量,则可能是验签失败。
- ❌如果没有观察到amr的下载动作,但是依然存在fallback流量,不能完全排查其他原因(离线包预置失败/下载失败等等,在此不展开)。
- 查看客户端日志,判断验签结果。
- ❌Android客户端日志中搜索
signature verify result
,根据日志上下文判断是否存在验签失败
- ❌Android客户端日志中搜索
3.1.1. 案例:
问题描述:
- 控制台已经配置了私钥(iOS客户配置了公钥):
- 从控制台下载离线资源并预置到iOS客户端内。
- 断开网络运行app:
a. 当nebulaNeedVerify为NO时正常加载预置的离线资源;
b. 当nebulaNeedVerify为YES时不能正常加载预置资源。 - 打开网络后重新运行,nebulaNeedVerify为NO或为YES均可以正常加载资源。
分析:
根据描述,控制台已经开启了离线包验签功能,意味着预置的amr文件中肯定需要有离线包的签名字段。
客户端验签关闭时(无论网络是否接通),资源总是可以正常加载(从预置包中获取),符合预期;
客户端验签开启时,断网情况下无法加载,有网情况下可以加载。从症状上推测,客户端验签可能失败,原因为:
- 在无网络时,fallback机制无法加载资源,应用无法加载;
- 在有网时,可以通过fallback模式从线上获取资源,应用最终成功加载。
进一步的排查方向是通过Charles/Fiddler抓包看看是否有线上流量,确认是否触发了fallback机制。
用户抓包确认了有网情况下fallback流量的存在,因此怀疑客户端公钥和服务端私钥不匹配。
用户重新生成了一对密钥后,问题得到解决。
3.2 H5 App加载失败
一般来说,H5 App加载失败最直观的症状是H5应用打开白屏。从原理上讲,这种问题类似与“网页打不开”的情况。排查思路也是从服务端、客户端和网络链路上去逐一排查。需要注意的是,如果H5应用本身在前端上展示上存在异常(例如引用的JavaScript库文件不存在),也可能导致白屏的状态。
可以做的基本检查动作如下:
- 检查服务端对应的H5 App是否存在处于“发布中”状态的包。
- 通过普通浏览器访问在线fallback地址,查看页面是否可以加载。
- 使用原生WebView加载
3.2.1. 案例1:
问题描述
除了标题,加载的离线包页面是空白页面,离线包使用全路径可以在电脑网页中打开。出现问题的环境:之前加载离线包是没有问题的,但是修改了meta.config文件中的"rpcGW","mpaasapi","pushGW","logGW","syncserver"等参数,其他不变,运行之后就无法加载了。
3.3 全局资源包加载失败
如2.1.2中所述,全局包一般承载的是多个H5 App之间共用的资源。全局包加载失败的常见场景一般有两种:
- 全局包离线失败,全局包资源实际通过fallback通过线上获取;
- 全局包资源完全引用不到。
场景1较为隐蔽,从症状上看,H5 App加载变慢或在离线情况下不可用。
场景2可能有两种情况:a. 全局包配置错误;b. 在H5 App中,引用全局资源包中的资源时填写的URL不正确。