WKWebView与App不在同一个进程运行,不会从App的标准Cookie容器NSHTTPCookieStorage
读取Cookie。跨进程的数据同步是一个麻烦及容易出现问题的场景,Apple在iOS11之前没有专门的API用于Cookie操作,在iOS11之后提供了WKHTTPCookieStore
,但是自测发现存在一些奇怪的bug,无法使用,此部分后续会做说明。
网上关于WKWebView的Cookie同步我有查到多种方案,但是均无法解决跨域请求的Cookie问题,后来发现使用WKProcessPool可以解决跨域问题。
UIWebView为什么没有Cookie同步问题?
网络请求完成后,会返回一个Response,如果Response中带有Set-Cookie
字段,如:"Set-Cookie" = "wifi_jsessionid=3aea4df28ab14b4e8714132cb911c15a; Domain=.pingan.com.cn; Path=/";
,操作系统就会将此条Cookie信息写入到NSHTTPCookieStorage
中
当UIWebView中有任意请求时(App进程中的请求也是一样),会去NSHTTPCookieStorage查找对应的Cookie信息,如果存在符合条件的Cookie信息,就会在请求头中带上此条Cookie信息。所以UIWebView进行跨域请求是没有任何问题的,只要NSHTTPCookieStorage有符合条件的Cookie信息即可带上。
WKWebView Cookie同步一般解决方案
WKWebView上请求不会自动带上NSHTTPCookieStorage
中的Cookie, 目前的主要解决方案是通过手动的方式直接在请求头上带上Cookie或者使用JS脚本进行Cookie注入:
在请求头中设置Cookie, 解决首个请求Cookie带不上的问题:
1 2 3 4 5
| WKWebView * webView = [WKWebView new]; NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://h5.qzone.qq.com/mqzone/index"]];
[request addValue:@"skey=skeyValue" forHTTPHeaderField:@"Cookie"]; [webView loadRequest:request];
|
此方法只适合解决简单针对性的场景。
通过document.cookie
设置Cookie解决后续页面(同域)Ajax、iframe请求的Cookie问题:
1 2 3 4 5 6 7 8 9 10
| WKUserContentController* userContentController = WKUserContentController.new;
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"document.cookie = 'TeskCookieKey1=TeskCookieValue1';document.cookie = 'TeskCookieKey2=TeskCookieValue2';" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userContentController addUserScript:cookieScript]; WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new; webViewConfig.userContentController = userContentController; WKWebView * webView = [[WKWebView alloc] initWithFrame:CGRectMake() configuration:webViewConfig];
|
设定Cookie的辅助方法:
1 2 3 4 5 6 7 8 9 10 11 12
| - (NSString *)cookieString { NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@", self.name, self.value, self.domain, self.path ?: @"/"];
if (self.secure) { string = [string stringByAppendingString:@";secure=true"]; } return string; }
|
因为Cookie注入无法跨域,此方法无法解决跨域请求的Cookie问题。
WKProcessPool同步方案解决跨域问题
苹果开发者文档对WKProcessPool的定义是:A WKProcessPool object represents a pool of Web Content process. 通过让所有WKWebView共享同一个WKProcessPool实例,可以实现多个 WKWebView之间共享Cookie(session Cookie and persistent Cookie)数据。
既然可以多个WKWebView共享一个WKProcessPool实例,那么是不是可以先访问跨域的URL,将Cookie注入,不就得到了一个有跨域Cookie的WKProcessPool实例吗?再用此实例去访问原始的Web页面,不就可以解决跨域访问的Cookie问题了吗? 经过实测,此方案是OK的。 样例如下:
可以创建一个单例管理WKProcessPool。如果Cookie更新了,更新WKProcessPool即可同步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| - (void)updateProcessPool { _isUpdatePooling = YES; if (_webView) { _webView.navigationDelegate = nil; [_webView removeFromSuperview]; _webView = nil; } if (_delegate && [_delegate respondsToSelector:@selector(willUpdateCookieWithPool)]) { [_delegate willUpdateCookieWithPool]; } NSString *jsStr = [self updateCookieScriptString]; NSURL *url = [NSURL URLWithString:@"http://www.pingan.com.cn/"]; WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:jsStr injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; WKUserContentController* userContentController = WKUserContentController.new; [userContentController addUserScript:cookieScript]; WKWebViewConfiguration* configuration = WKWebViewConfiguration.new; configuration.userContentController = userContentController; configuration.processPool = self.pool; WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]; [[UIApplication sharedApplication].keyWindow addSubview:webView]; NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:3]; webView.navigationDelegate = self; [webView loadRequest:request]; _webView = webView; }
#pragma mark - WKNavigationDelegate - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { LOG_INFO("WEB", @"didFinishNavigation :%@", navigation); webView.navigationDelegate = nil; [webView removeFromSuperview]; @synchronized(self) { for (void(^block)(WKProcessPool *pool) in _handlers) { block(_pool); } [_handlers removeAllObjects]; _isUpdatePooling = NO; } if (_delegate && [_delegate respondsToSelector:@selector(didUpdateCookieWithPool)]) { [_delegate didUpdateCookieWithPool]; } }
#pragma mark - - (void)getCookieWithProcessPoolHandler:(void(^)(WKProcessPool *pool))handler { if (_isUpdatePooling) { [_handlers addObject:handler]; } else { handler(_pool); } }
|
由于WKProcessPool只能在初始化时传入有效,所以调用有一点特殊:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @weakify(self); PAWFWebProcessPoolManager *cookieManager = [PAWFWebProcessPoolManager sharedManager]; [cookieManager getCookieWithProcessPoolHandler:^(WKProcessPool * _Nonnull pool) { @strongify(self); self.webView = [self creatWebViewWithPool:pool]; [self.view addSubview:self.webView]; NSURLRequest *request = [[NSURLRequest alloc] initWithURL:self.URL]; [self.webView loadRequest:request]; }];
- (WKWebView *)creatWebViewWithPool:(WKProcessPool *)pool { WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init]; configuration.processPool = pool; CGRect frame = CGRectMake(0, 0, kScreenWidth, CGRectGetHeight(self.contentView.frame) - kGrwonStatusBarHeight); WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:frame configuration:configuration]; wkWebView.navigationDelegate = self; return wkWebView; }
|
WKHTTPCookieStore同步cookie问题
WKHTTPCookieStore
,存在一些奇怪的bug,完全无法使用,已知问题如下:
无法正常写入cookie的Bug:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionary]; [cookieProperties setObject:@"wifi_jsessionid" forKey:NSHTTPCookieName]; [cookieProperties setObject:jsessionid forKey:NSHTTPCookieValue]; [cookieProperties setObject:@".pingan.com.cn" forKey:NSHTTPCookieDomain]; [cookieProperties setObject:@"" forKey:NSHTTPCookieOriginURL]; [cookieProperties setObject:@"/" forKey:NSHTTPCookiePath]; [cookieProperties setObject:@"0" forKey:NSHTTPCookieVersion]; NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
WKWebsiteDataStore *store = [WKWebsiteDataStore defaultDataStore]; [store.httpCookieStore setCookie:cookie completionHandler:^{ [store.httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> * _Nonnull cookies) { }]; }];
|
使用迂回方式解决写入问题:
1 2 3 4 5 6 7 8 9 10 11 12
| [store.httpCookieStore addObserver:self];
- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore { WKWebsiteDataStore *store = [WKWebsiteDataStore defaultDataStore]; [store.httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> * _Nonnull cookies) { LOG_INFO("WEB", @"cookiesDidChangeInCookieStore: %@", cookies); }]; }
|
cookie添加成功后,WKWebVie无法使用WKWebsiteDataStore中的cookie信息。详情可见:#140191
- 自测添加跨域的cookie, Cookie添加成功后进行跨域访问,cookie无法生效。
- 自测添加正常cookie也无法生效。
参考资料