WKWebView 使用 NSURLProtocol 攔截后的 POST 請求異常及解決方法

原因

首先 WebKit 進程是獨立于 app 進程之外的,兩個進程之間使用消息隊列的方式進行進程間通信。比如 app 想使用 WKWebView 加載一個請求,就要把請求的參數(shù)打包成一個 Message,然后通過 IPC 把 Message 交給 WebKit 去加載,反過來 WebKit 的請求想傳到 app 進程的話(比如 URLProtocol ),也要打包成 Message 走 IPC。出于性能的原因,打包的時候 HTTPBody 和 HTTPBodyStream 這兩個字段被丟棄掉了,這個可以參考 WebKit 的源碼,這就導(dǎo)致 -[WKWebView loadRequest:] 傳出的 HTTPBody 和 NSURLProtocol 傳回的 HTTPBody 全都被丟棄掉了。所以如果通過 NSURLProtocol 注冊攔截 http scheme,那么由 WebKit 發(fā)起的所有 http POST 請求就全都無效了,這個從原理上就是無解的。同時攔截后對 ATS 支持不好。

驗證過程

通過注冊NSURLProtocol并注冊私有API后進行NSURLRequest攔截,可以獲取 H5 發(fā)送的請求頭,但無法獲取 H5 端的請求。
1.WKWebView 攔截如圖:


WKWebView 攔截

2.UIWebView 攔截如圖:


UIWebView 攔截

解決方案

  1. 修改Scheme
    將 H5 的資源文件與 POST 請求的鏈接使用不同的 Scheme ,移動端只攔截資源文件的 Scheme ,不攔截 POST 地址。
    攔截方式:iOS 11 以上可使用 WKURLSchemeHandler 進行攔截,且只允許攔截自定義 Scheme 的請求,不允許攔截“http”、“https”、“ftp”、“file”等請求,否則會 crash。在 iOS 11 以下只能使用私有API:WKBrowsingContextController 和 registerSchemeForCustomProtocol ,通過反射的方式拿到了私有的 class/selector。

  2. POST 請求改為與原生交互
    2.1 將 H5 對 POST 的交互改為與 Native 的橋接,由 Native 負(fù)責(zé)請求接口數(shù)據(jù),再將數(shù)據(jù)返回給 JS。
    2.2 注入一段 HookAjax 的 JS 代碼,攔截所有的 XMLHttpRequest 的 POST 請求轉(zhuǎn)移給移動端處理。將 POST 請求通過 JS 和 Native 交互的方式將請求轉(zhuǎn)交給 Native 處理并且在 Native 處理完后將結(jié)果返回給 JS。

小結(jié):
方案1,移動端修改小,前端需要對數(shù)據(jù)所在的站點重新部署;
方案2.1,移動端、前端修改均較大;
方案2.2,移動端較大、前端修改較小,但需要有人幫忙寫 HookAjax 的 JS 代碼。

解決方法

上述的方案1、2.1對于前端改動較大,為了避免牽扯過多人員導(dǎo)致項目進展緩慢,則本文采用方案2.2。
1.注冊與注銷攔截

- (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在被我們攔截后也能正常請求到數(shù)據(jù)了。代碼中涉及到的部分代碼來源于IMYWebLoader。不過經(jīng)測試,如果H5加入eruda框架那么會導(dǎo)致沖突。于是筆者經(jīng)過修改后編寫了一份新的js文件:github:WKHookAjax里的ajaxhook.js


2020.03.23更新
對Get請求方式也進行了Hook,因為iOS9下的Get方式請求體也為空。


參考資料
iOS - NSProtocol 攔截 WKWebView POST 請求 body 會被清空的問題解決
Web的一系列優(yōu)化方案
Ajax-hook 原理解析
WKWebView 那些坑
iOS app秒開H5優(yōu)化探索

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • WKWebView 是蘋果在 WWDC 2014 上推出的新一代 webView 組件,用以替代 UIKit 中笨...
    Aiana閱讀 4,802評論 1 8
  • 1、WKWebView 白屏問題WKWebView 自詡擁有更快的加載速度,更低的內(nèi)存占用,但實際上 WKWebV...
    無名感恩閱讀 2,277評論 0 3
  • 1、WKWebView 白屏問題WKWebView 自詡擁有更快的加載速度,更低的內(nèi)存占用,但實際上 WKWebV...
    iosRn閱讀 2,172評論 1 10
  • 導(dǎo)語 WKWebView 是蘋果在 WWDC 2014 上推出的新一代 webView 組件,用以替代 UIKit...
    Jecky丶閱讀 8,749評論 2 22
  • 你深邃眼眸 是一望無際的大海 我終其一生 是張望你的天空
    妃卿閱讀 266評論 0 4

友情鏈接更多精彩內(nèi)容