原因
首先 WebKit 進(jìn)程是獨(dú)立于 app 進(jìn)程之外的,兩個(gè)進(jìn)程之間使用消息隊(duì)列的方式進(jìn)行進(jìn)程間通信竣贪。比如 app 想使用 WKWebView 加載一個(gè)請(qǐng)求,就要把請(qǐng)求的參數(shù)打包成一個(gè) Message,然后通過(guò) IPC 把 Message 交給 WebKit 去加載玛瘸,反過(guò)來(lái) WebKit 的請(qǐng)求想傳到 app 進(jìn)程的話(比如 URLProtocol ),也要打包成 Message 走 IPC苟蹈。出于性能的原因糊渊,打包的時(shí)候 HTTPBody 和 HTTPBodyStream 這兩個(gè)字段被丟棄掉了,這個(gè)可以參考 WebKit 的源碼慧脱,這就導(dǎo)致 -[WKWebView loadRequest:] 傳出的 HTTPBody 和 NSURLProtocol 傳回的 HTTPBody 全都被丟棄掉了渺绒。所以如果通過(guò) NSURLProtocol 注冊(cè)攔截 http scheme,那么由 WebKit 發(fā)起的所有 http POST 請(qǐng)求就全都無(wú)效了菱鸥,這個(gè)從原理上就是無(wú)解的宗兼。同時(shí)攔截后對(duì) ATS 支持不好。
驗(yàn)證過(guò)程
通過(guò)注冊(cè)NSURLProtocol并注冊(cè)私有API后進(jìn)行NSURLRequest攔截氮采,可以獲取 H5 發(fā)送的請(qǐng)求頭殷绍,但無(wú)法獲取 H5 端的請(qǐng)求。
1.WKWebView 攔截如圖:
2.UIWebView 攔截如圖:
解決方案
修改Scheme
將 H5 的資源文件與 POST 請(qǐng)求的鏈接使用不同的 Scheme 鹊漠,移動(dòng)端只攔截資源文件的 Scheme 主到,不攔截 POST 地址茶行。
攔截方式:iOS 11 以上可使用 WKURLSchemeHandler 進(jìn)行攔截,且只允許攔截自定義 Scheme 的請(qǐng)求登钥,不允許攔截“http”畔师、“https”、“ftp”怔鳖、“file”等請(qǐng)求茉唉,否則會(huì) crash。在 iOS 11 以下只能使用私有API:WKBrowsingContextController 和 registerSchemeForCustomProtocol 结执,通過(guò)反射的方式拿到了私有的 class/selector度陆。POST 請(qǐng)求改為與原生交互
2.1 將 H5 對(duì) POST 的交互改為與 Native 的橋接,由 Native 負(fù)責(zé)請(qǐng)求接口數(shù)據(jù)献幔,再將數(shù)據(jù)返回給 JS懂傀。
2.2 注入一段 HookAjax 的 JS 代碼,攔截所有的 XMLHttpRequest 的 POST 請(qǐng)求轉(zhuǎn)移給移動(dòng)端處理蜡感。將 POST 請(qǐng)求通過(guò) JS 和 Native 交互的方式將請(qǐng)求轉(zhuǎn)交給 Native 處理并且在 Native 處理完后將結(jié)果返回給 JS蹬蚁。
小結(jié):
方案1,移動(dòng)端修改小郑兴,前端需要對(duì)數(shù)據(jù)所在的站點(diǎn)重新部署犀斋;
方案2.1,移動(dòng)端情连、前端修改均較大叽粹;
方案2.2,移動(dòng)端較大却舀、前端修改較小虫几,但需要有人幫忙寫 HookAjax 的 JS 代碼。
解決方法
上述的方案1挽拔、2.1對(duì)于前端改動(dòng)較大辆脸,為了避免牽扯過(guò)多人員導(dǎo)致項(xiàng)目進(jìn)展緩慢,則本文采用方案2.2螃诅。
1.注冊(cè)與注銷攔截
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
Class cls = NSClassFromString(@"IMYWebURLProtocol");
[NSURLProtocol registerClass:cls];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
Class cls = NSClassFromString(@"IMYWebURLProtocol");
[NSURLProtocol unregisterClass:cls];
}
2.設(shè)置與卸載WKWebViewConfiguration的hookAjax
WKUserContentController *wkUController = [[WKUserContentController alloc] init];
WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init];
wkWebConfig.userContentController = wkUController;
[wkWebConfig.userContentController imy_installHookAjax]; // hookAjax
//卸載hookAjax
[wkConfig.userContentController imy_uninstallHookAjax];
至此啡氢,hookAjax已經(jīng)結(jié)束,H5的post在被我們攔截后也能正常請(qǐng)求到數(shù)據(jù)了术裸。代碼中涉及到的部分代碼來(lái)源于IMYWebLoader空执。不過(guò)經(jīng)測(cè)試,如果H5加入eruda框架那么會(huì)導(dǎo)致沖突穗椅。于是筆者經(jīng)過(guò)修改后編寫了一份新的js文件:github:WKHookAjax里的ajaxhook.js
2020.03.23更新
對(duì)Get請(qǐng)求方式也進(jìn)行了Hook,因?yàn)閕OS9下的Get方式請(qǐng)求體也為空奶栖。
參考資料
iOS - NSProtocol 攔截 WKWebView POST 請(qǐng)求 body 會(huì)被清空的問(wèn)題解決
Web的一系列優(yōu)化方案
Ajax-hook 原理解析
WKWebView 那些坑
iOS app秒開H5優(yōu)化探索