1. 背景
在作者將 App 里的 UIWebView 切換到 WKWebView 之后琢唾,有些功能得到了加強(qiáng)闹击,包括煩人的 Cookie 也很好的解決了,唯獨(dú)離線包和 webView 里的資源請求攔截一直是個(gè)心病,沒有很優(yōu)雅的解決方案国葬。
坊間流傳的自定義攔截 https、http 的方案,可以實(shí)現(xiàn)一部分功能汇四。但是有很大的缺點(diǎn)接奈,
- 調(diào)用私有的 API,審核和以后版本升級有隱患
- XHR 請求丟失 body 的問題通孽,所以要封裝一層 myXHR 代替 XHR
- 攔截 form 請求里的 body 參數(shù)序宦。
而緩存和資源攔截作為 webView 框架應(yīng)該提供的基本功能,AppHost【注1】也嘗試用一個(gè)更優(yōu)雅的方式來實(shí)現(xiàn)背苦,所以嘗試用 iOS11+ 上提供的新接口互捌。
2. WKURLSchemeHandler 的 checklist
WKURLSchemeHandler 被引入是在WWDC 2017 Customized Loading in WKWebView ,當(dāng)時(shí)演示的時(shí)候是以加載自定義圖片為例的行剂。
請記住這句話“加載自定義圖片為例”秕噪。
結(jié)合我們的需求,我們需要加載本地資源硼讽、攔截請求巢价、發(fā)出 ajax 請求,同時(shí)這些請求都包含正確的** Cookie **固阁,我們需要解決以下幾個(gè)子問題壤躲;
- 自定義 scheme ,可以定義哪些 scheme备燃?
根據(jù)自己測試和閱讀源碼碉克,下面類型的都屬于內(nèi)置協(xié)議不可用
static const StringVectorFunction functions[] {
builtinSecureSchemes,
builtinSchemesWithUniqueOrigins,
builtinEmptyDocumentSchemes,
builtinCanDisplayOnlyIfCanRequestSchemes,
builtinCORSEnabledSchemes,
};
結(jié)論:已知的約定俗稱的都不能定義,包括并齐,https漏麦、http、about况褪,當(dāng)然也包括撕贞,data、blob测垛、ftp 等捏膨,如果你這樣做了,會收到一個(gè)錯(cuò)誤''https' is a URL scheme that WKWebView handles natively'
- 自定義 scheme 食侮,哪些 HTML 里元素會觸發(fā)自定義請求号涯?
根據(jù)作者在常見的 html 元素里搜集的會觸發(fā)資源下載或者 navigation 邏輯的標(biāo)簽,整理了下面的表格锯七。
標(biāo)簽 | domain 是 http 情況 | domain 是 https 情況 | 是否觸發(fā) decidePolicyForNavigation (此時(shí)意味著 webkit 有 bug) |
---|---|---|---|
img 的 src | ? | ? | -- |
script 的 src | ? | ? | -- |
link 的 href | ? | ? | -- |
iframe 的 src | ? | ? | -- |
css 的 background-image 語法 | ? | ? | -- |
css 的 cursor 語法 | ? | ? | -- |
object 標(biāo)簽的 data 屬性 | ? | ? | -- |
audio 標(biāo)簽的 src 屬性 | ? | ? | -- |
video 標(biāo)簽的 source 屬性 | ? | ? | -- |
a 標(biāo)簽的 href 屬性 | ? | ? | ?? |
xhr 的 url | ? | ? | |
form post | ? | ? | ?? |
form get | ? | ? | ?? |
總結(jié)一下链快;按照 MDN 里混合內(nèi)容的說法,混合被動(dòng)/顯示內(nèi)容在任何情況下都會觸發(fā)眉尸,混合活動(dòng)內(nèi)容 在 https 下不會觸發(fā)域蜗。
在 https 下的 xhr 的請求和混合活動(dòng)內(nèi)容巨双,都不能觸發(fā)的原因其實(shí)是兩個(gè)機(jī)制;
- 混合活動(dòng)內(nèi)容是受限 CSP地消,目前基本無法繞過炉峰。
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; script-src 'self' apphost: ;">
上面的寫法并沒有生效,依然阻止 apphost://a.js
的文件加載脉执。(可能是我姿勢不對)
- xhr 不能發(fā)出去,原因是 same-origin 策略導(dǎo)致的戒劫。
Definition of an originSection
Two URLs have the same origin if the protocol, port (if specified), and host are the same for both.
- 自定義 scheme 半夷,可以帶 Cookie 嗎?
作者通過網(wǎng)上搜索迅细、閱讀了 RFC6265 里對 Cookie 的規(guī)范巫橄、實(shí)踐,得出了以下結(jié)論茵典。
- 自定義 scheme 是可以帶 Cookie湘换,而且和相同 domain 的其他協(xié)議共享 Cookie。這就厲害了统阿,然后實(shí)際上不完全是——初步測試彩倚,
http
,https
,ftp
等 scheme 其實(shí)是完全沒問題,包括設(shè)置 session 級別的 Cookie扶平;在此之外帆离,設(shè)置自定義 scheme 的 session Cookie 會 fail,持久化 Cookie 是可行的结澄。 沒想到吧
- 自定義 scheme 哥谷,可以發(fā) post 請求嗎?
是的麻献。在 webkit 實(shí)現(xiàn)有 bug 的情況下们妥,可以用 form 發(fā)送 post 請求。不僅如此勉吻,使用 form post监婶,可以成功將 body 發(fā)送到 native,這個(gè)就厲害了餐曼。
- 自定義 scheme 压储,可以攔截 xhr 請求?是否丟失 POST 請求的 body源譬?
事實(shí)上是可以攔截的集惋。在上面表格里雖然 http、https 下都是 ?踩娘,但是那是因?yàn)?domain 是標(biāo)準(zhǔn)協(xié)議刮刑。如果是
[self.webView loadHTMLString:html baseURL:[NSURL URLWithString:@"apphost://test.com/index.2.html"]];
那么 xhr 依然也可以發(fā)送出去喉祭,但是不幸的時(shí),xhr 里 body 還是丟了雷绢。
3 實(shí)踐
有了上述的前提泛烙。我們有一個(gè)簡單的離線包渲染的方案。
- 把加載頁面的 domain 改為 自定義協(xié)議如 apphost翘紊;
[self.webView loadHTMLString:html baseURL:[NSURL URLWithString:@"apphost://test.com/index.2.html"]];
- 加載的 HTML 文件應(yīng)用的資源全部用
://user/a.png
這種相對的寫法蔽氨。這樣 http 和 自定義協(xié)議都可以用。 - HTML 里面 ajax 請求使用絕對路徑帆疟,修改后端的 Response 頭鹉究,使用 Cross-Origin Resource Sharing (CORS) 技術(shù),配合對 ajax 請求追加幾個(gè)參數(shù)踪宠,也可以實(shí)現(xiàn)帶上 Cookie 的請求
上面的做法自赔,可以攔截基本上傳統(tǒng) UIWebview 時(shí)代可攔截的所有類型。但是缺點(diǎn)也很明顯柳琢。
- iOS 11+ 以上才能用
- https 下支持有限
- 對 xhr js 代碼小幅度的改動(dòng)
- 最最嚴(yán)重的是绍妨,不支持 session 級別的 Cookie 設(shè)置。
在 AppHost 框架里柬脸,作者沒有采用這種方式他去,而是采用了讀 html,解析靜態(tài)資源的方式來實(shí)現(xiàn)加載離線包的功能肖粮,同時(shí)不影響 session 級別的 Cookie 設(shè)置孤页。
總的來說 WKURLSchemeHandler 的使用場景還是比較有限,不能攔截 http 請求涩馆,有些結(jié)合 NSURLSession行施,先下載 html 的方式,然后再代理的方式魂那,對 webview 的加載速度反而是拖累蛾号,還需要想想其它辦法。
結(jié)論:WKURLSchemeHandler 最好的場景還是用在加載本地圖片上涯雅,其它方面不穩(wěn)定鲜结,慎用。
4. 注
- AppHost.framework 是作者在網(wǎng)易有錢活逆、嚴(yán)選工作中精刷,抽離出來 JSBridge 的庫,實(shí)現(xiàn) native 和 h5 之間的通訊蔗候,內(nèi)置諸多常用的功能怒允,在業(yè)務(wù)簡單的情況下可開箱即用,復(fù)雜情況允許靈活定制锈遥。預(yù)計(jì)在4月底開源