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

原因

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

驗證過程

通過注冊NSURLProtocol并注冊私有API后進行NSURLRequest攔截,可以獲取 H5 發送的請求頭,但無法獲取 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 負責請求接口數據,再將數據返回給 JS。
    2.2 注入一段 HookAjax 的 JS 代碼,攔截所有的 XMLHttpRequest 的 POST 請求轉移給移動端處理。將 POST 請求通過 JS 和 Native 交互的方式將請求轉交給 Native 處理并且在 Native 處理完后將結果返回給 JS。

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

解決方法

上述的方案1、2.1對于前端改動較大,為了避免牽扯過多人員導致項目進展緩慢,則本文采用方案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.設置與卸載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已經結束,H5的post在被我們攔截后也能正常請求到數據了。代碼中涉及到的部分代碼來源于IMYWebLoader。不過經測試,如果H5加入eruda框架那么會導致沖突。于是筆者經過修改后編寫了一份新的js文件:github:WKHookAjax里的ajaxhook.js


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


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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

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