WKWebView的饼干的处理方式

说起WKWebview代替UIWebview带来的好处你可以举出一堆堆的例子,但说到WKWebview的问题,你绕不过的就是WKWebview cookie和NSHTTPCookieStorage cookie不共享的问题。你可以在网络上搜到如何将他们相互同步的帖子。

曲奇饼

如何将NSHTTPCookieStorage同步给WKWebview,大概要处理很多种情况,包括但不限于以下;

  1. 初次加载页面时,同步cookie到WKWebview
  2. 处理ajax请求时,需要的cookie
  3. 如果响应里有set-cookie还需要缓存这些cookie
  4. 如果是302还需要处理cookie传递的问题

所以,如果你按照上面的要求编写了代码,你会发现总有漏网之鱼的情况没有处理,比方说请求响应设置了cookie,为了在后续跳转中带上这些cookie,你需要暂存下来,这样可能会污染到NSHTTPCookieStorage;再举一个极端的真实的案例,如果有个网站的鉴权是通过302鉴权和响应set-cookie的,那么你会发现这个网站在鉴权那里陷入了死循环,因为302响应set-cookie后302的位置地址加载时并没有携带上302时设置的cookie,进而继续302 set-cookie的跳转。

那如果解决302响应set-cookie的问题,我们不能在上述方案里修修补补,上述方案对正常的数据请求已经有很大的侵入性,对很多没有必要进行cookie设置的页面做了处理,一定程度上对性能也有影响。让我们跳脱原来的方案,重新审视下WKWebview cookie相关的资料。

WKWebview cookie是怎么存储的

  1. 会话级别的cookie 会话级别的cookie是保存在WKProcessPool里的,每个WKWebview都可以关联一个WKProcessPool的实例,如果需要在整个App生命周期里访问h5保留h5里的登录状态的,可以将使用WKProcessPool的单例来共享登录状态。

WKProcessPool 是个没有属性和方法的对象,唯一的作用就是标识是不是需要新的会话级别的管理对象,一个实例代表一个对象。

  1. 未过期的cookie 有效期的cookie被持久化存储在NSLibraryDirectory目录下的Cookies/文件夹。

image.png

注意,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目录下两个文件比较重要;

  • Cookie.binarycookies
  • <appid> .binarycookies 两者的区别是<appid> .binarycookies是NSHTTPCookieStorage文件对象; <appid> .binarycookies则是WKWebview的实例化对象。 这也是为什么WKWebview和NSHTTPCookieStorage的原因 - 因为被保存在不同的文件当中。

为了验证,你可以打开这两者文件进行查看,这里不再展开。

当然两个文件都是二进制文件,直接用文本浏览器打开是看不到,有一个python写的BinaryCookieReader脚本gist.github.com/sh1n0b1/4bb ...。可以读出来

WKWebview Cookie是如何工作的?

  1. 当webview loadRequest或者302或者在webview加载完毕,触发了ajax请求时,WKWebview所需的Cookie会去Cookie.binarycookies里读取本域名下的Cookie,加上WKProcessPool持有的Cookie以前作为请求头里的Cookie数据。
  2. 但是如果仔细查看NSURLRequest.h源代码,而不是仅仅查看NSDictionary<NSString *, NSString *> *allHTTPHeaderFields;的快速帮助,你会发现这句话;

再查看下HTTPShouldHandleCookies的快速帮助,

结合两者,你也会发现一个核心的概念 - 如果设置了allHTTPHeaderFields,则不使用默认的cookie管理器

所以我们的方案是 - 在页面加载过程中不去设置allHTTPHeaderFields,全部使用默认Cookie mananger管理,这样就不会有Cookie污染也不会有302 Cookie丢失的问题了,下面让我们验证一下。

唯一的问题 - 如何将NSHTTPCookieStorage的Cookie共享给WKWebview。

解决方案

在首次加载url时,检查是否已经同步过Cookie。如果没有同步过,则先加载一个cookieWebivew,它的主要目的就是将Cookie先使用usercontroller的方式写到WKWebview里,这样在处理正式的请求时,就会带上我们从NSHTTPCookieStorage获取到的Cookie了。核心代码如下,

这里需要处理的问题是,加载完毕或者失败后需要清理旧webview和设置标记位。

同时记得删掉原来对webview的Cookie的所有处理的代码。

处理至此,大功告成,这样的后续请求,WKWebview都用自身所有的Cookie和NSHTTPCookieStorage的Cookie,这样既达到了Cookie共享的目的,WKWebview和NSHTTPCookieStorage的Cookie也做了个隔离。