WKWebView的cookie探究

最近在学习WKWebView中的cookie方案,本来以为只是简单的设置一下就好了,参考了很多资料,发现里面的坑越来越大,为了弄清楚这些坑,我做了一系列实验对比分析,加上了一些推测,也算是基本解决了心中的很多谜团。虽然整个过程比较折腾,但是学习重在解决问题的过程,麻烦一点也无所谓了。本文总结了大概的探索过程,如果感兴趣,可以跟着我的方式一起学习,讨论出更加合理的结论。如果不感兴趣,可以直接看文末的推测的结论。

关于Cookie

首先需要理解cookie的概念,cookie是在http协议中非常重要的角色。http是无状态协议,也就是说http不会根据之前的访问情况来处理下次请求,在很多涉及账号的网页中,页面需要根据是否登录的状态来显示内容。为了避免每次访问都要登录,可以在第一次登录完成后讲登录信息写入cookie,添加到之后的请求中,这样就解决了http不能记录状态的问题。从开发者层面来说,cookie本质是包含了一系列key-value的数组。


无cookie

有cookie

如图所示,客户端第一次向服务器发送请求的时候,没有cookie,服务器收到后,会生成可以表示客户端身份的cookie,服务器将cookie封装到响应包的头部的set-cookie字段,返回给客户端,客户端根据set-cookie的内容设置cookie,并在之后的请求中带上cookie内容,这样服务器就能识别到这个客户端了。

关于WKWebView

本文重点在于对WKWebView中的cookie问题做出一些总结,不会详细说明WKWebView的API的协议的使用。WKWebView是在iOS8后UIWebView的替代品,具有低内存(实际是开启另外的进程),与Safari具有相同的JavaScript引擎,高效的app与web的通信(注入JavaScript脚本,messageHandler回调JavaScript方法)等。

Starting in iOS 8.0 and OS X 10.10, use WKWebView to add web content to your app. Do not use UIWebView or WebView.

因此,在iOS8以后,建议都使用WKWebView,但是使用过程中有很多坑,具体请看WKWebView 那些坑。这里主要讨论一下其中的cookie问题。

关于WKWebView 那些坑

在UIWebView中,在每次请求之前,会讲NSHTTPCookieStorage里面的cookie自动添加到请求中,因此UIWebView上的cookie问题并不突出。然而经过测试发现,WKWebView中并不会自动向请求中添加cookie,这也是很多文章提到的问题,总结了《WKWebView 那些坑》中的相关内容:

  • WKWebView Cookie 问题在于 WKWebView 发起的请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie。
  • 实践发现 WKWebView 实例其实也会将 Cookie 存储于 NSHTTPCookieStorage 中,但存储时机有延迟,在iOS 8上,当页面跳转的时候,当前页面的 Cookie 会写入 NSHTTPCookieStorage 中,而在 iOS 10 上,JS 执行 document.cookie 或服务器 set-cookie 注入的 Cookie 会很快同步到 NSHTTPCookieStorage 中

WKWebView中cookie的解决方案有:

方案1:WKWebView loadRequest 前,在 request header 中设置 Cookie, 解决首个请求 Cookie 带不上的问题;
缺陷:只能解决指定的一个URL的请求,涉及到同域的Ajax跳转还是会缺失cookie

方案2: 通过 document.cookie 设置 Cookie 解决后续页面(同域)Ajax、iframe 请求的 Cookie 问题;
缺陷:不能解决跨域cookie的问题

方案3:每次页面跳转的时候回调用decidePolicyForNavigationAction回调,那么就可以在回调里拦截请求,加入cookie后重新发送。
缺陷:loadRequest是加载mainFrame请求,所以依然解决不了页面 iframe 跨域请求的 Cookie 问题

本文主要是对这些结论中的一些疑惑点进行了探索和测试,确实有了更多的了解。

关于NSHTTPCookieStorage

前面说过,其实是NSHTTPCookieStorage不再自动管理cookie,导致了这么多问题。下面简单介绍一下NSHTTPCookieStorage的概念和常用的方法,具体使用还是参考官方文档。

简单介绍

NSHTTPCookieStorage提供了一个管理cookie的单例对象,这个对象里的cookie是进程同步共享的,常用方法有:

//只读数组,包含所有的cookie的一个数组
@property (nullable , readonly, copy) NSArray<NSHTTPCookie *> *cookies;
//设置cookie
- (void)setCookie:(NSHTTPCookie *)cookie;
//删除cookie
- (void)deleteCookie:(NSHTTPCookie *)cookie;

注意:NSHTTPCookie的创建一般是用存储了描述cookie的字典初始化,

NSMutableDictionary *properties = [NSMutableDictionary dictionary];
[properties setObject:@"cookie_test" forKey:NSHTTPCookieName];
[properties setObject:@"value" forKey:NSHTTPCookieValue];
[properties setObject:@".xxx.xxx.com" forKey:NSHTTPCookieDomain];
[properties setObject:@"/" forKey:NSHTTPCookiePath];
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:properties];

这些描述字段有NSHTTPCookieName,NSHTTPCookieValue,NSHTTPCookieDomain等,这里的NSHTTPCookieDomain和NSHTTPCookieName共同组成了主键,也就是说NSHTTPCookieStorage里面可以存在多个NSHTTPCookieName相同的cookie对象。

准备工作

工欲善其事必先利其器,研究cookie少不了客户端和服务器的交互,所以接下来需要:用mac搭建本地服务器,iOS设备和mac设备处于同一个局域网即可。和Windows系统上繁琐的配置不同,mac系统上自带Apache服务器和php环境,我们只需要几步就可以设置成功。
1.开启Apache
打开terminal,输入sudo apachectl start,打开浏览器,输入localhost或127.0.0.1,显示it works!说明服务器开启成功

访问本机地址

这个页面是位于服务器根目录下/Library/WebServer/Documents下的index.html.en.
用记事本打开这个文件,可以看到html的代码:

<html><body><h1>It works!</h1></body></html>

我们把文件名修改为index.php,浏览器访问localhost/index.php,发现浏览器不能解析其内容,因为还没配置php环境。
2.配置php
找到php的配置文件的路径:/etc/apache2/httpd.conf
用记事本打开,搜索定位到php7_module这句话,然后将前面的#去掉,保存退出。

httpd.conf

3.重启Apache
在terminal中输入:sudo apachectl restart
再次用浏览器访问localhost/index.php,当你看到it works!显示说明配置成功了。
4.客户端设置
这里的客户端指的是iOS设备,只要客户端与服务器处在同一个局域网下,就可以直接通过ip地址访问。ip地址通过打开网络偏好设置查看。
查看局域网中的ip

然后在手机浏览器中输入ip/index.php,看到it works!表示访问成功。
手机访问服务器

客户端和服务器coding

服务器

服务器设置cookie很容易,在页面开始的时候设置即可:

?php 
 setcookie("name_test", "value_test", time()+3600);
?>

这句代码设置了名为name_test, 值为value_test,过期时间为1小时的cookie。
读取cookie有两种方式:
1.$_COOKIE[属性名],php的方式
2.document.cookie,js的方式
我们也需要看看这两种方式在处理cookie上有什么区别,页面很简单(懒得做布局了,能看就行😂):


服务器页面

这里做了一个跳转页面,只在页面1上设置cookie,页面2只读取cookie,这样可以测试页面跳转后读取cookie的情况。

客户端

241537778685_.pic.jpg

为了测试出cookie的情况,我设置了五个按钮:
清理磁盘:清理了library目录下的cookie文件
清理内存:清理NSHTTPCookieStorage的cookie数组,是模拟进程退出的情况
新建(普通):新建一个WKWebView,并发送请求
新建(带Script):区别于普通的新建,新建WKWebView的时候,会在configuration中通过userScript的方式设置cookie,cookie的name为name_test,value为Config setting
刷新:页面刷新,不涉及新建,重新发送请求

测试与分析

申明:测试环境是xcode9.4.1, 机型是iPhone 7P, 系统11.4,模拟器和真机差别很大,请使用真机。
测试中发现以下现象:
1.整个实验中,php有时读取不到cookie,而js读取一直成功,即使是第一次访问页面。
分析:其实不太明白这两种方式的应用场景,从现象来看,js的逻辑似乎是,优先读取客户端请求里的cookie,读不到时会读取服务器设置的cookie。后续有了新的了解再完善。
:由于js读取一直成功,后面现象的描述都省略js的情况

2.对于两种新建方式(带script和不带的)来说,首次新建页面,PHP读取为空,连续多次点击刷新按钮或者是跳转页面,PHP读取到cookie。
分析:符合正常的cookie特点,首次请求没有cookie,响应回来后设置,后续的请求就会带上cookie,可以说明在这种刷新的情况下,WKWebView请求是会自动带上cookie的。

3.在cookie已经成功读取的情况下,连续多次点击新建(普通)按钮,cookie一直读取成功。点击清理磁盘,然后点击新建(普通)按钮,cookie读取不到。点击清理内存,然后新建,cookie有时读不到。
分析:这里主要是想分析出WKWebView请求的时候自动带上的cookie来自于哪里。现象来看,当cookie已经生成时,新建WKWebView不影响cookie的读取,而一旦清理了磁盘时,cookie就没有了。而清理内存,内存里的内容来自于读取磁盘里的内容,有时可以读取的到。

4.新建(带script)的方式,只有点击刷新才会读取到cookie,新建则读取不到。
分析:userScript是设置在configuration里,而configuration作为参数初始化WKWebView,每次新建就相当于一个新的设置,这种方式的cookie存储和WKWebView的生命周期绑定了,同生共灭。

5.读取不到cookie的情况下(可以通过清理磁盘+新建的方式实现),快速连续点击新建(普通)按钮,会发现一直读不到cookie,而慢速点击的时候,就可以读到cookie了
分析:测试中,慢速和快速的大约范围是3到5秒,也就是说,当客户端没有cookie的时候,第一次访问会设置cookie,但是这个设置是需要时间的。如果在规定的时间内没有设置成功,而下一次请求已经到来,就会cancel当前的设置,导致一直没有cookie

翻阅一些相关资料,并没有找到完整而合理的解释,在此基于测试现象,我做出了可能的解释:
1.WKWebView对象内部有一套自己的cookie管理配置,有相应的存储和策略,是和对象绑定的,当WKWebView对象存在时,内部管理会在第一次请求返回的时候设置cookie到。当WKWebView对象销毁的时候,管理配置也就没有了,存储的cookie也就没了。
2.NSHTTPCookieStorage的单例对象,在每次响应返回的时候,会自动把响应包里的set-cookie字段里的内容写到磁盘中并同步在内存,每一次请求来了,NSHTTPCookieStorage对象会在自身cookie中查看是否有值并且和磁盘cookie一致(可能是校验码之类的),如果有,就直接将NSHTTPCookieStorage中的cookie自动添加到请求头中,如果没有,就会从磁盘里读取。如果过于迅速的请求页面,可能导致还没有读取完成就发送了请求,这时就会读取失败了。
3.WKUserScript本质是在页面插入一段js代码,和configuration绑定,因此也是和WKWebView对象的生命周期一致。
4.WKWebView发送请求前会查找cookie,首先查找对象内部的存储,如果没有,就去NSHTTPCookieStorage对象中查找,如果NSHTTPCookieStorage对象里没有或者个和磁盘cookie不一致,就会异步地去磁盘里找。
ps: 在进程开启和退出的时候,都会有NSHTTPCookieStorage和磁盘内容同步。因此在应用中,需要注意第一次请求没有cookie,然后又连续多次发送同一请求的情况,这种情况可能导致磁盘和内存设置失败,从而读取不到cookie。

如何设置cookie

通过测试,我们发现WKWebView里cookie的坑确实不少,在实际使用中,可以考虑与后台一起商量方案:
1.交给NSHTTPCookieStorage处理。这个处理方式是自动的,不用做额外操作。
使用场景:无需第一次发送cookie给服务器,无需更改cookie内容,不会连续多次请求第一个页面(后续的ajax跳转可以连续多次)
2.请求头里添加Cookie的方式 + userScript的方式。请求头的方式解决了第一次设置,userScript解决了后续页面ajax跳转的问题。

  [request addValue:@"cookieName = value" forHTTPHeaderField:@"Cookie"];

使用场景:客户端给服务器设置cookie,但是cookie一旦设置,就不能改变了。
3.拦截请求 + 请求头里添加Cookie的方式。在回调里拦截请求,同域的ajax跳转都可以在回调里收到消息,然后在请求头里添加cookie后重新设置即可。
使用场景:处理变化的cookie,即在交互中,cookie信息可能会一直变化(增加字段等)

代码下载

https://github.com/wszxy/WKWebViewCookie

参考文章:

  1. WKWebView 那些坑

  2. NSHTTPCookieStorage state not saved on app exit. Any definitive knowledge/documentation out there?

  3. WKWebView - WebKit | Apple Developer Documentation

4.https://blog.csdn.net/ismilesky/article/details/53894259

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容