采用UIWebView時(shí),用微信授權(quán)后進(jìn)入綁定手機(jī)號頁面,綁定手機(jī)號成功,然后重新生成一個(gè)頁面(UIViewController主頁),進(jìn)入新頁面銷毀綁定手機(jī)號h5頁面(UIViewController),主頁正常顯示。但是采用WKWebView,同樣的處理,這個(gè)主頁顯示是沒有綁定手機(jī)號的下載二維碼頁面。網(wǎng)上搜索到的說WKWebView的cookie需要用戶注入,而UIWebView是cookie自己注入和保存。我把cookie從綁定手機(jī)號頁面取出,傳遞到主頁頁面并且注入這個(gè)cookie還是不能顯示主頁。分析是cookie的問題,不知道獲取和注入的cookie哪里出問題了,希望大神指點(diǎn)?沒有辦法,現(xiàn)在暫時(shí)不跳轉(zhuǎn)頁面,一個(gè)控制器處理所有js頁面的顯示了。
原因是: WKWebView 是一個(gè)多進(jìn)程組件,每個(gè)WKWebView頁面進(jìn)程都有自己的cookie,它們向服務(wù)器發(fā)送請求時(shí)都自己帶上自己的cookie,所以你在app中無論怎么攔截都發(fā)現(xiàn)請求中沒有帶cookie,實(shí)際上WKWebView頁面進(jìn)程肯定代了帶了cookie,不然服務(wù)器返回錯(cuò)誤。打印的cookie是:Cookie:JSESSIONID=A2B33F508E609B8208D8EA148114794E; _bl_uid=sOjwaley6yX6OFcn3nap0qt6p8dR。并且我測試發(fā)現(xiàn)_bl_uid有低概率沒有,JSESSIONID都存在,還存在兩個(gè)相同的_bl_uid帶不同的值的情況。估計(jì)這就是WKWebView的cookie返回的說法吧,至于兩對bl_uid鍵值對,估計(jì)是強(qiáng)制想向請求的HTTPHeaderFields注入cookie引起。而一旦注冊 http(s) scheme 后,你發(fā)現(xiàn)你跳轉(zhuǎn)的新頁面就正常,并且cookie的鍵值對還多了一對(如:Cookie:JSESSIONID=143219E3B0D66946C4D949D50811F88C; _bl_uid=e9jqhlz96tX9Fhhv9fh94s2qtRvm; PS=o8SkWwD1RV7VYdPH_PGzgC5EYdv4。注意:這里指的是通過[NSHTTPCookieStorage sharedHTTPCookieStorage]獲取到的app本地cookie, 不是通過通常decidePolicyForNavigationResponse(實(shí)際上兩種情況通過該函數(shù)獲取的cookie只有JSESSIONID=143219E3B0D66946C4D949D50811F88C一對鍵值,只有讀本地cookie時(shí)不同)獲取到的cookie。),fsCachedData存在大量緩存數(shù)據(jù)(注冊 http(s) scheme前的app沒有那么多數(shù)據(jù)),但是這樣做的嚴(yán)重后果是post 請求 body 數(shù)據(jù)被清空(這個(gè)問題我遇到過,是現(xiàn)在一直真實(shí)存在的問題)。若從正常的微信授權(quán)成功h5頁面A跳轉(zhuǎn)的新控制器頁面(h5頁面)B,若在B頁面加載發(fā)送請求時(shí)設(shè)置cookie,那么B頁面加載失敗,從B頁面返回A頁面,刷新A頁面的相同控制器的子頁面請求全部失敗。我研究了三天了,想在不注冊http(s) scheme 的情況下正常加載B頁面成功都不可能。
WKWebView中Cookie混亂問題:按道理來說每個(gè)WKWebView都有一個(gè)單獨(dú)的存儲Cookies的空間,相互不影響,但是,奇妙之處就是我在一個(gè)UIViewController中生成了一個(gè)WKWebView,然后進(jìn)行了一系列的網(wǎng)絡(luò)訪問后,推出并銷毀這個(gè)UIViewcontroller;在下次進(jìn)來的時(shí)候這個(gè)WKWebView會攜帶上次訪問的部分Cookies。
??這個(gè)原因是WKWebView會將Cookie存儲到沙盒目錄的文件中,下次WKWebView被實(shí)例化的時(shí)候,會去同步這個(gè)文件中的Cookies。
??decidePolicyForNavigationAction函數(shù)中navigationAction.request是只讀的,decidePolicyForNavigationResponse函數(shù)的navigationResponse.response也是只讀的。你在這些函數(shù)中也沒有辦法重置請求的allHTTPHeaderFields的字段。
使用UIWebView沒有這樣的問題,這也許是UIWebView沒有完全代替WKWebView原因之一吧!
參考文章:《【騰訊Bugly干貨分享】WKWebView 那些坑》https://blog.csdn.net/tencent_bugly/article/details/54668721/。
測試使用的代碼如一,它實(shí)際上及時(shí)更新cookie文件,由于WKWebView和本app不在一個(gè)進(jìn)程中,它們不在一個(gè)程序空間,他們都有自己的cookie,它們兩者之間資源共享需要進(jìn)程間通信,你及時(shí)更新的是app空間的cookie,不能處理WKWebView的cookie及時(shí)同步到app空間,所以不能解決該問題:
//這個(gè)是網(wǎng)頁加載完成,導(dǎo)航的變化
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
NSString *strRequest = [webView.URL.absoluteString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
FLDDLogVerbose(@"isHaveTelLoginPage:%d, webView.URL strRequest:%@",[AWSingleObject sharedInstance].isHaveTelLoginPage, strRequest);
// 獲取加載網(wǎng)頁的標(biāo)題
self.titleLabel.text = self.wkWebView.title;
//取出cookie
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
//js函數(shù)
NSString *JSFuncString =
@"function setCookie(name,value,expires)\
{\
var oDate=new Date();\
oDate.setDate(oDate.getDate()+expires);\
document.cookie=name+'='+value+';expires='+oDate+';path=/'\
}\
function getCookie(name)\
{\
var arr = document.cookie.match(new RegExp('(^| )'+name+'=({FNXX==XXFN}*)(;|$)'));\
if(arr != null) return unescape(arr[2]); return null;\
}\
function delCookie(name)\
{\
var exp = new Date();\
exp.setTime(exp.getTime() - 1);\
var cval=getCookie(name);\
if(cval!=null) document.cookie= name + '='+cval+';expires='+exp.toGMTString();\
}";
//拼湊js字符串
NSMutableString *JSCookieString = JSFuncString.mutableCopy;
for (NSHTTPCookie *cookie in cookieStorage.cookies) {
NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", cookie.name, cookie.value];
[JSCookieString appendString:excuteJSString];
}
//執(zhí)行js
[webView evaluateJavaScript:JSCookieString completionHandler:^(id obj, NSError * _Nullable error) {
NSLog(@"%@",error);
}];
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
for (NSHTTPCookie *cookie in cookies) {
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
NSLog(@"cookie:%@", cookie);
}
decisionHandler(WKNavigationResponsePolicyAllow);
}
測試方法二,B頁面加載出來的不是期望的cookie校驗(yàn)成功的頁面, 由于WKWebView和本app不在一個(gè)進(jìn)程中,它們不在一個(gè)程序空間,他們都有自己的cookie,它們兩者之間資源共享需要進(jìn)程間通信,你及時(shí)更新的是app空間的cookie,不能處理WKWebView的cookie及時(shí)同步到app空間,所以不能解決本問題:
#pragma mark - 頁面加載前處理
- (void)beforePush:(NSDictionary *)params
{
[super beforePush:params];
NSDictionary *userInfo = params[MGJRouterParameterUserInfo];
if ([[userInfo safeObjectForKey:@"jsWebEntity"] isKindOfClass:[AWJsWebEntity class]]) {
AWJsWebEntity *jsWebEntity = [userInfo safeObjectForKey:@"jsWebEntity"];
NSMutableArray *cookiesArr = [NSMutableArray array];
/** 獲取NSHTTPCookieStorage cookies */
NSHTTPCookieStorage * shareCookie = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in shareCookie.cookies){
[cookiesArr addObject:cookie];
}
self.cookiesArr = cookiesArr;
// self.cookies= [userInfo safeObjectForKey:@"cookies"];
[self loadWebURLSring:jsWebEntity.url];
}
}
#pragma mark ================ 加載方式 ================
- (void)webViewloadURLType{
// NSMutableURLRequest * Request_zsj = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.URLString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
NSMutableURLRequest *Request_zsj = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.URLString]];
NSString *cookie = [self readCurrentCookieWithDomain:self.URLString];
//cookie = [NSString stringWithFormat:@"%@;PS=o8SkWwD1RV7VYdPH_PGzgC5EYdv4", cookie];
[Request_zsj addValue:cookie forHTTPHeaderField:@"Cookie"];
NSString *cookieSting = @"";
for (NSHTTPCookie *cookie in self.cookiesArr){
if(!isEmptyString(cookieSting))
{
cookieSting = [NSString stringWithFormat:@"%@; %@=%@",cookieSting, cookie.name,cookie.value];
}
else
{
cookieSting = [NSString stringWithFormat:@"%@=%@",cookie.name,cookie.value];
}
}
// [Request_zsj setValue:cookieSting forHTTPHeaderField:@"Cookie"];
NSLog(@"Cookie:%@", cookieSting);
[Request_zsj setValue:cookieSting forHTTPHeaderField:@"Cookie"];
// [Request_zsj setValue:@"Mozilla/5.0 (iPhone; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15G77Yixiangweipai/0.0.1" forHTTPHeaderField:@"User-Agent"];
NSLog(@"task.url:%@ \n currentRequest.allHTTPHeaderFields:%@", [NSString stringWithFormat:@"%@", Request_zsj.URL], Request_zsj.allHTTPHeaderFields);
// WKHTTPCookieStore *cookieStore = self.wkWebView.configuration.websiteDataStore.httpCookieStore;
// [cookieStore setCookie:self.cookies completionHandler:nil];
//加載網(wǎng)頁
[self.wkWebView loadRequest:Request_zsj];
}
- (NSString *)readCurrentCookieWithDomain:(NSString *)domainStr{
NSHTTPCookieStorage*cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSMutableString * cookieString = [[NSMutableString alloc]init];
for (NSHTTPCookie*cookie in [cookieJar cookies]) {
[cookieString appendFormat:@"%@=%@;",cookie.name,cookie.value];
}
//刪除最后一個(gè)“;”
[cookieString deleteCharactersInRange:NSMakeRange(cookieString.length - 1, 1)];
return cookieString;
}
測試方法三:在A頁面注冊http(s) scheme 的情況[NSURLProtocol wk_registerScheme:@”https”]; 當(dāng)然你使用的h5頁面地址是以http:開頭的修改為:[NSURLProtocol wk_registerScheme:@”http”];具體SURLProtocol怎么用,大家在網(wǎng)上搜索一下吧,后期我會寫一篇關(guān)于js標(biāo)簽圖片在iOS替換的文章有相關(guān)的介紹)。app會攔截該應(yīng)用向服務(wù)器發(fā)送的所有https網(wǎng)絡(luò)請求(包括js,cs,png等資源請求,注意:攔截的請求是WKWebView在HTTPHeaderField加入cookie前的請求,所以在canInitWithRequest函數(shù)打印NSLog(@”canInitWithRequest request.URL.absoluteString = %@,請求方式 == %@,scheme:%@,request.allHTTPHeaderFields:%@”,urlStr,request.HTTPMethod,scheme, request.allHTTPHeaderFields);得到是null,如:2018-08-23 14:16:51.390805+0800 ArtEnjoymentWeChatAuction[12030:1142813] canInitWithRequest request.URL.absoluteString = https://m.1-joy.com/market/product/cat/list.htm,請求方式 == GET,scheme:https,request.allHTTPHeaderFields:(null)),并且在fsCachedData緩存絕大部份網(wǎng)頁數(shù)據(jù)(如何獲取fsCachedData文件夾下的文件,見文章《如何在不越獄的情況下,獲取app中的所有常用文件和文件夾》https://blog.csdn.net/jia12216/article/details/81536960)。當(dāng)不注冊http(s) scheme 的情況,fsCachedData文件夾下一般有很少的文件,幾乎沒有網(wǎng)頁數(shù)據(jù)。這樣做的嚴(yán)重后果是post 請求 body 數(shù)據(jù)被清空(這個(gè)問題我遇到過,是現(xiàn)在一直真實(shí)存在的問題),就是h5頁面自己向后臺發(fā)送的帶參數(shù)的post請求,后臺收到的請求參數(shù)全部沒有。例如在h5頁面上創(chuàng)建聯(lián)系地址,填寫成功以post的方式上傳后臺,那么參數(shù)全部掉丟失。當(dāng)然h5頁面調(diào)用iOS原生方法,由iOS使用afnet等控件發(fā)送請求發(fā)送給后臺,參數(shù)不會丟失,也就是只有h5頁面(WKWebView直接管理)直接向后臺發(fā)送post請求才會參數(shù)丟失。這種方式能解決頁面間的跳轉(zhuǎn),但是有問題。
現(xiàn)在我只找到這么多的方法,只有第三種不完美的方法能解決該問題。網(wǎng)上說的WKWebView的cookie解決方案也就上面兩個(gè)類似的方案。大家抄來抄去,根本就沒有實(shí)際測試過,解決不了我們的問題。為何需要從A頁面(h5頁面,單獨(dú)的UIViewController)跳轉(zhuǎn)到B頁面(h5頁面,單獨(dú)的UIViewController),因?yàn)檫@樣可以把兩者的邏輯分離,若不分離,A頁面和B頁面的邏輯混在一起,就會造成邏輯過于復(fù)雜,不利于組件化。
UIWebView是和app在一個(gè)進(jìn)程里,它們的數(shù)據(jù)是共享,操作app的cookie和請求就是操作UIWebView的cookie和請求。UIWebView在發(fā)送請求時(shí)都自帶cookie。而WKWebView和app不在一個(gè)進(jìn)程中,操作app的cookie和請求并不都能影響操作WKWebView的cookie和請求。有人說使用WKWebView的app,app的cookie有延遲,這個(gè)是客觀存在的,因?yàn)樗鼈冊诓煌倪M(jìn)程中,進(jìn)程中的資源是不共享的,它們不是實(shí)時(shí)同步的,是有同步時(shí)機(jī)的。當(dāng)然你想它們實(shí)時(shí)同步也可以就是注冊http(s) scheme。
有人說WKWebView發(fā)送請求時(shí)是不自帶cookie的,這種方法是不正確的,它在WKWebView自己的進(jìn)程中發(fā)送請求是自帶cookie,只是它怎么自帶cookie發(fā)送請求蘋果系統(tǒng)沒有向用戶開放,你不知道它怎么發(fā)送的,它只提供了提供一個(gè)NSMutableURLRequest請求給WKWebView。至于你想在這個(gè)請求中自己去app的cookie給他,若你沒有跳轉(zhuǎn)到其他控制器,那么請求仍舊成功,只是出現(xiàn)本地的cookie可能出現(xiàn)同鍵不同值的鍵值對,若你跳轉(zhuǎn)了控制器,那么你添加的cookie無效,因?yàn)槟悴恢繵KWebView怎么自帶cookie的。若真的WKWebView的請求不自帶cookie ,那么我們微信授權(quán)成功進(jìn)入首頁,讓后在首頁里跳轉(zhuǎn)頁面不會都正常的(我們的頁面除了協(xié)議頁面基本都校驗(yàn)cookie的)。我推測可能是你新起一個(gè)WKWebView頁面,也就是新起了一個(gè)WKWebView進(jìn)程,這個(gè)進(jìn)程首先找自己的cookie文件,若沒有直接把請求的cookie設(shè)置為空了,當(dāng)然它剛建立的進(jìn)程,當(dāng)然它的cookie文件不存在了,所以肯定被設(shè)置為空了,因此新的WKWebView頁面發(fā)送出去的請求就不自帶cookie了??梢娪捎赪KWebView是多進(jìn)程組件,cookie也真夠混亂的。
既然頁面不能完美解決兩個(gè)h5頁面控制器之間的cookie問題,但是咱們的普通https請求卻不受影響,下面是在h5頁面控制器里向后臺服務(wù)器發(fā)送https請求的代碼片段:
NSURL * url = [NSURL URLWithString:@"https://m.1-joy.com/market/user/weixin/subscribe.htm"];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
NSURLSession * session = [NSURLSession sharedSession];
NSString *cookie = [self readCurrentCookieWithDomain:self.URLString];
// cookie = [NSString stringWithFormat:@"%@;PS=o8SkWwD1RV7VYdPH_PGzgC5EYdv4", cookie];
[request addValue:cookie forHTTPHeaderField:@"Cookie"];
// 發(fā)送請求
NSURLSessionTask * sessionTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
return;
}
NSString *mmmmmmm = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"mmmmmmm: %@, response:%@, error:%@, request.allHTTPHeaderFields:%@", mmmmmmm, response, error, request.allHTTPHeaderFields);
}];
[sessionTask resume];