2017年4月18日 星期二

【iOS】WKWebView使用Cookies遇到的坑

【iOS】WKWebView使用Cookies遇到的坑

144
作者 狍子君 
2017.02.16 21:20* 字數 909 閱讀 107評論 2
  Apple推出WKWebView已經有一段時間了,相對於UIWebView而言,內存佔用只有UIWebView的一半左右,但是響應速度和效率上卻是UIWebView的兩倍。
  總結WKWebView使用方法的帖子文章很多,這裡不再贅述,這裡重點總結一下cookies共享問題。
WKWebView會忽視默認的網絡存儲, NSURLCache, NSHTTPCookieStorage, NSCredentialStorage。目前是這樣的,WKWebView有自己的進程,同樣也有自己的存儲空間用來存儲cookie和cache, 其他的網絡類如NSURLConnection是無法訪問到的。同時WKWebView發起的資源請求也是不經過NSURLProtocol的,導致無法自定義請求。
  由於以上原因,導致WKWebView無法與App自身的Cookies、UIWebView之間共享Cookies數據。WKWebView這樣做也有一定的好處,在不用操作原有Cookies的基礎上,獨立的一套Cookies,有效的防止了Web與App Api接口的Cookie相互污染。
  但是在使用過程中也遇到了一些坑。
  有的時候因為業務需求,就是需要相互共享Cookies,這樣的案例也有很多,支付寶中大量的Web中,應該就有很多Cookie是和App共享的吧。

NSHTTPCookieStorage Cookies共享

  在WkWebView接收到Response後,將Response帶的Cookies取出,然後直接放入[NSHTTPCookieStorage sharedHTTPCookieStorage] 容器中:
- (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];
    }
    decisionHandler(WKNavigationResponsePolicyAllow);
}
  然後在完全加載完成後:
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    //取出cookie
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    //js函数
    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];
    }
    //执行js
    [webView evaluateJavaScript:JSCookieString completionHandler:^(id obj, NSError * _Nullable error) {
        NSLog(@"%@",error);
    }];
}
  為什麼在完全加載完成後需要重新給WKWebView設置Cookie呢?如果你不這樣做的話很有可能因為a標籤跳轉,導致下一次跳轉的時候Cookie丟失。

302跳轉Set-Cookie丟失

  上面的方法可以將服務器Set-Cookie攜帶到下一次請求中。但是如果302跳轉出現在你的第一次加載並且你使用了下面的方法設置第一次加載的Cookies,那麼在302跳轉時服務器Set-Cookie將不會被攜帶到下一次302跳轉的目標請求中。
NSMutableURLRequest *request= [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https:/a.com/test1"]];
[request setValue:[NSString stringWithFormat:@"%@=%@",@"a", @"1"] forHTTPHeaderField:@"Cookie"];
  比如:第一次加載https:/a.com/test1,然後加載https:/a.com/test1設置Cookie為a=1,服務器在https:/a.com/test1中Set-Cookie a=2;然後302跳轉到https:/a.com/test2,這個時候會發現https:/a.com/test2中獲取到的a還是1,Set-Cookie並沒有成功。
解決辦法
1.加載一個本地為空的html,域名指向你的第一次加載的url的域名。
//加载本地html
[self.webView loadHTMLString:@"" baseURL:[NSURL URLWithString:@"https:/a.com"]];
2.通過以下方法,在第一次加載完成後,將需要設置的Cookies設置到WKWebView中,因為是加載的本地的html以下方法會立即執行。
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{

    if (isFirstLoaded) {
        NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        //js函数
        NSString *JSFuncString =
        @"function setCookie(name,value,expires)\
        {\
        var oDate=new Date();\
        oDate.setDate(oDate.getDate()+expires);\
        document.cookie=name+'='+value+';expires='+oDate+';path=/'\
        }";

        //拼凑js字符串,按照自己的需求拼凑Cookie
        NSMutableString *JSCookieString = JSFuncString.mutableCopy;
        for (NSHTTPCookie *cookie in cookieStorage.cookies) {
            if (![cookie.name isEqualToString:@"__cust"]) {
                NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 3);", cookie.name, cookie.value];
                [JSCookieString appendString:excuteJSString];
            }
        }

        //执行js
        [webView evaluateJavaScript:JSCookieString completionHandler:^(id obj, NSError * _Nullable error) {
            //加载真正的第一次Request
            [self loadRealRequest];
        }];
    }
}

WKWebView中Cookie混亂

  按道理來說每個WKWebView都有一個單獨的存儲Cookies的空間,相互不影響,但是,奇妙之處就是我在一個UIViewController中生成了一個WKWebView,然後進行了一系列的網絡訪問後,推出並銷毀這個UIViewcontroller;在下次進來的時候這個WKWebView會攜帶上次訪問的部分Cookies。
  這個原因是WKWebView會將Cookie存儲到沙盒目錄的文件中,下次WKWebView被實例化的時候,會去同步這個文件中的Cookies,如果不希望它去同步之歌Cookies,那麼直接刪掉好了。
- (void)deleteWebCache {
    if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
        NSSet *websiteDataTypes = [NSSet setWithArray:@[
                                WKWebsiteDataTypeDiskCache,
                                //WKWebsiteDataTypeOfflineWebApplicationCache,
                                WKWebsiteDataTypeMemoryCache,
                                //WKWebsiteDataTypeLocalStorage,
                                //WKWebsiteDataTypeCookies,
                                //WKWebsiteDataTypeSessionStorage,
                                //WKWebsiteDataTypeIndexedDBDatabases,
                                //WKWebsiteDataTypeWebSQLDatabases
                                ]];
        //// All kinds of data
        //NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
        //// Date from
        NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
        //// Execute
        [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
            // Done
        }];
    } else {
        NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        NSString *cookiesFolderPath = [libraryPath stringByAppendingString:@"/Cookies"];
        NSError *errors;
        [[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:&errors];
    }

}
不足之處,歡迎輕輕地噴一下......

沒有留言:

張貼留言

編程的智慧

編程的智慧 作者   正義的花生   關注 2015.11.22 15:28*  字數17768  閱讀94218  評論0  喜歡2660 編程是一種創造性的工作,是一門藝術。 精通任何一門藝術,都需要很多的練習和領悟,所以這裡提出的“智慧”,並不是號稱...