說起 WKWebview 代替 UIWebview 帶來的好處你可以舉出一堆堆的例子情萤,但說到 WKWebview 的問題,你繞不過的就是 WKWebview cookie 和 NSHTTPCookieStorage cookie 不共享的問題候齿。你可以在網(wǎng)絡(luò)上搜到如何將他們相互同步的帖子。
如何將 NSHTTPCookieStorage 同步給 WKWebview ,大概要處理很多種情況妻献,包括但不限于以下;
- 初次加載頁面時(shí)团赁,同步 cookie 到 WKWebview
- 處理 ajax 請(qǐng)求時(shí)育拨,需要的 cookie
- 如果 response 里有 set-cookie 還需要緩存這些 cookie
- 如果是 302 還需要處理 cookie 傳遞的問題
所以,如果你按照上面的要求編寫了代碼欢摄,你會(huì)發(fā)現(xiàn)總有漏網(wǎng)之魚的情況沒有處理熬丧,比方說請(qǐng)求 response 設(shè)置了 cookie,為了在后續(xù)跳轉(zhuǎn)中帶上這些 cookie怀挠,你需要暫存下來析蝴,這樣可能會(huì)污染到 NSHTTPCookieStorage ;再舉一個(gè)極端的真實(shí)的案例绿淋,如果有個(gè)網(wǎng)站的鑒權(quán)是通過 302 鑒權(quán) 和 response set-cookie 的闷畸,那么你會(huì)發(fā)現(xiàn)這個(gè)網(wǎng)站在鑒權(quán)那里陷入了死循環(huán),因?yàn)?302 response set-cookie 后 302 的 location 地址加載時(shí)并沒有攜帶上 302 時(shí)設(shè)置的 cookie吞滞,進(jìn)而繼續(xù) 302 set-cookie的跳轉(zhuǎn)腾啥。
那如果解決 302 response set-cookie 的問題,我們不能在上述方案里修修補(bǔ)補(bǔ)冯吓,上述方案對(duì)正常的數(shù)據(jù)請(qǐng)求已經(jīng)有很大的侵入性倘待,對(duì)很多沒有必要進(jìn)行 cookie 設(shè)置的頁面做了處理,一定程度上對(duì)性能也有影響组贺。讓我們跳脫原來的方案凸舵,重新審視下 WKWebview cookie 相關(guān)的資料。
WKWebview cookie 是怎么存儲(chǔ)的
-
session 級(jí)別的 cookie
session 級(jí)別的 cookie 是保存在WKProcessPool
里的失尖,每個(gè) WKWebview 都可以關(guān)聯(lián)一個(gè)WKProcessPool
的實(shí)例啊奄,如果需要在整個(gè) App 生命周期里訪問 h5 保留 h5 里的登錄狀態(tài)的,可以將使用WKProcessPool
的單例來共享登錄狀態(tài)掀潮。
WKProcessPool
是個(gè)沒有屬性和方法的對(duì)象菇夸,唯一的作用就是標(biāo)識(shí)是不是需要新的 session 級(jí)別的管理對(duì)象,一個(gè)實(shí)例代表一個(gè)對(duì)象仪吧。
-
未過期的 cookie
有有效期的 cookie 被持久化存儲(chǔ)在NSLibraryDirectory
目錄下的Cookies/
文件夾庄新。
注意,cookie 持久化文件地址在 iOS 9+ 上在
/Users/Mac/Library/Developer/CoreSimulator/Devices/D2F74420-D59B-4A15-A50B-774D3D01FADE/data/Containers/Data/Application/E8646AD5-1110-43F3-95D9-DE6A32E78DB7/Library/Cookies
.
但是在 iOS 8 上 cookie 被保存在兩部分,一部分如上所述择诈,還有一部分保存在 App 無法獲取的地方械蹋,/Users/Mac/Library/Developer/CoreSimulator/Devices/D2F74420-D59B-4A15-A50B-774D3D01FADE/data/Library/Cookies
,大概就是后者的 Cookie 是 iOS 的 Safari 使用 羞芍。
在 Cookies 目錄下兩個(gè)文件比較重要;
- Cookie.binarycookies
- <appid>.binarycookies
兩者的區(qū)別是 <appid>.binarycookies 是 NSHTTPCookieStorage 文件對(duì)象哗戈;Cookie.binarycookies 則是 WKWebview 的實(shí)例化對(duì)象。
這也是為什么 WKWebview 和 NSHTTPCookieStorage 的原因——因?yàn)楸槐4嬖诓煌奈募?dāng)中荷科。
為了驗(yàn)證唯咬,你可以打開這兩者文件進(jìn)行查看,這里不再展開畏浆。
當(dāng)然兩個(gè)文件都是 binary file胆胰,直接用文本瀏覽器打開是看不到,有一個(gè) python 寫的腳本
BinaryCookieReader
https://gist.github.com/sh1n0b1/4bb8b737370bfe5f5ab8全度。可以讀出來
WKWebview Cookie 是如何工作的斥滤?
- 當(dāng) webview loadRequest 或者 302 或者在 webview 加載完畢将鸵,觸發(fā)了 ajax 請(qǐng)求時(shí)伦籍,WKWebview 所需的 Cookie 會(huì)去 Cookie.binarycookies 里讀取本域名下的 Cookie 子房,加上
WKProcessPool
持有的 Cookie 一起作為 request 頭里的 Cookie 數(shù)據(jù)镰惦。 - 但是如果仔細(xì)查看
NSURLRequest.h
源代碼拯杠,而不是僅僅查看NSDictionary<NSString *, NSString *> *allHTTPHeaderFields;
的 quick help唇礁,你會(huì)發(fā)現(xiàn)這句話号醉;
@abstract Sets the HTTP header fields of the receiver to the given
dictionary.
@discussion This method replaces all header fields that may have
existed before this method call.
再查看下HTTPShouldHandleCookies
的 quick help悉抵,
@property BOOL HTTPShouldHandleCookies;
Description
A boolean value that indicates whether the receiver should use the default cookie handling for the request.
YES if the receiver should use the default cookie handling for the request, NO otherwise. The default is YES.
If your app sets the Cookie header on an NSMutableURLRequest object, then this method has no effect, and the cookie data you set in the header overrides all cookies from the cookie store.
SDKs iOS 8.0+, macOS 10.10+, tvOS 9.0+, watchOS 2.0+
結(jié)合兩者岭皂,你也會(huì)發(fā)現(xiàn)一個(gè)核心的概念-如果設(shè)置了 allHTTPHeaderFields茬贵,則不用使用 the cookie manager by default簿透。
所以我們的方案是-在頁面加載過程中不去設(shè)置 allHTTPHeaderFields,全部使用默認(rèn) Cookie mananger
管理解藻,這樣就不會(huì)有 Cookie 污染也不會(huì)有 302 Cookie 丟失的問題了老充,下面讓我們驗(yàn)證一下。
唯一的問題——如何將 NSHTTPCookieStorage 的 Cookie 共享給 WKWebview螟左。
解決方案
在首次加載 url 時(shí)啡浊,檢查是否已經(jīng)同步過 Cookie。如果沒有同步過胶背,則先加載 一個(gè) cookieWebivew巷嚣,它的主要目的就是將 Cookie 先使用 usercontroller 的方式寫到 WKWebview 里,這樣在處理正式的請(qǐng)求時(shí)钳吟,就會(huì)帶上我們從 NSHTTPCookieStorage 獲取到的 Cookie了廷粒。
核心代碼如下,
if ([AppHostCookie loginCookieHasBeenSynced] == NO) {
//
NSURL *cookieURL = [NSURL URLWithString:kFakeCookieWebPageURLString];
NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:cookieURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:120];
WKWebView *cookieWebview = [self getCookieWebview];
[self.view addSubview:cookieWebview];
[cookieWebview loadRequest:mutableRequest];
DDLogInfo(@"[JSBridge] preload cookie for url = %@", self.loadUrl);
} else {
[self loadWebPage];
}
// 注意红且,CookieWebview 和 正常的 webview 是不同的
- (WKWebView *)getCookieWebview
{
// 設(shè)置加載頁面完畢后评雌,里面的后續(xù)請(qǐng)求树枫,如 xhr 請(qǐng)求使用的cookie
WKUserContentController *userContentController = [WKUserContentController new];
WKWebViewConfiguration *webViewConfig = [[WKWebViewConfiguration alloc] init];
webViewConfig.userContentController = userContentController;
webViewConfig.processPool = [AppHostCookie sharedPoolManager];
NSMutableArray<NSString *> *oldCookies = [AppHostCookie cookieJavaScriptArray];
[oldCookies enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
NSString *setCookie = [NSString stringWithFormat:@"document.cookie='%@';", obj];
WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:setCookie injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
[userContentController addUserScript:cookieScript];
}];
WKWebView *webview = [[WKWebView alloc] initWithFrame:CGRectMake(0, -1, SCREEN_WIDTH,ONE_PIXEL) configuration:webViewConfig];
webview.navigationDelegate = self;
webview.UIDelegate = self;
return webview;
}
這里需要處理的問題是,加載完畢或者失敗后需要清理舊 webview 和設(shè)置標(biāo)記位景东。
static NSString * _Nonnull kFakeCookieWebPageURLString = @"http://ai.api.com/xhr/user/getUid.do?26u-KQa-fKQ-3BD"
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
NSURL *targetURL = webView.URL;
if ([AppHostCookie loginCookieHasBeenSynced] == NO && targetURL.query.length > 0 && [kFakeCookieWebPageURLString containsString:targetURL.query]) {
[AppHostCookie setLoginCookieHasBeenSynced:YES];
// 加載真正的頁面砂轻;此時(shí)已經(jīng)有 App 的 cookie 存在了。
[webView removeFromSuperview];
[self loadWebPage];
return;
}
}
同時(shí)記得刪掉原來對(duì) webview 的 Cookie 的所有處理的代碼斤吐。
處理至此搔涝,大功告成,這樣的后續(xù)請(qǐng)求和措, WKWebview 都用自身所有的 Cookie 和 NSHTTPCookieStorage 的 Cookie庄呈,這樣既達(dá)到了 Cookie 共享的目的, WKWebview 和 NSHTTPCookieStorage 的 Cookie 也做了個(gè)隔離派阱。