十分钟轻松看懂Cookie

35次阅读

共计 4733 个字符,预计需要花费 12 分钟才能阅读完成。

从经典的购物车说起
发现很多地方在介绍 cookie 的作用时,都喜欢使用购物车的例子,那我们也不妨从这个例子开始说起。
场景示例:有用户 a 和 b,都登陆了某个购物网站,此时如果两个用户都点击了【查看购物车】按钮,那么此时服务器就同时收到了 2 个请求,请求的内容都是:“请告诉我我的购物车有什么商品”。现在问题来了——服务器怎么区分哪个请求对应哪个用户呢?
其实这就是经常看到的那句话 http 协议是无状态的。为什么说无状态呢,因为 http 协议可以看成一种简单的应答模式:

客户端发送一个请求,服务端返回相应的内容;
客户端(可能不是同一个客户端了)又再发送一次请求,服务端(服务端还是同一个)就再返回相应的内容,

那么服务端知道两次访问的客户端是不是同一个吗?显然是母鸡呀!

为了解决这个问题,就要介绍今天的主角 –Cookie。
cookie 登场
可以联想一下生活中。我们如果去一家生意火爆的火锅店吃火锅的场景(我忽然有点想去吃个火锅再回来写了),这时如果有空座了,店员怎么知道在门口等候的顾客应该轮到谁了呢?很简单,在顾客等候之前,先发个号码牌,后面根据号码辨别身份。
?!!
看到这里,是不是觉得有点饿了?啊呸,是不是觉得灵光乍现,上面说的问题和这个场景不是如出一辙吗?所以,为了能够让会话可以被识别,http 中也引入了一个号码牌——cookie 的机制。(会话这个词出现地有点突兀,简单解释下:a 用户登录购物网站之后,接下来发生的一系列请求,都应该属于服务端与 a 的会话)
好了,原理大概说完了,那 cookie 具体怎么实现呢?很简单,放在 http 的头部 (headers)。在前一篇讲状态码时,已经稍稍涉猎了一点 http 头部的相关知识,简单的来说 http 的头部是一个 js 对象,里面存放了一系列的键值对(key:value)用来表示各种信息,http 的【请求】和【响应】都有头部,而 cookie 机制的实现,则是借助了其中的两个字段:Cookie 和 Set-Cookie,整个运行流程可以分成以下步骤:

客户端发送一个 http 请求到服务端(我去告诉店员,我要吃火锅了)
服务端响应该请求,并且在响应头中包含了 Set-Cookie(店员回应我,并且给了我一张号码牌,告诉我有任何需要都带着号码牌来申请)
客户端发送请求,请求头包含字段 Cookie(我到店里后,呼叫服务员来一份雪花牛肉,并且告诉他,我的号码牌是 6 号)
服务端根据请求中 Cookie 的内容响应。(服务员听到后,根据我的号码牌,给我给上了对应的菜)

在上述过程中,可能同时也有其他号码牌的顾客在与服务员进行类似的交流过程,由于服务员发给每个顾客的号码牌上的数字都是唯一的,所以不必害怕弄混。
cookie 的基本原理就是这么简单!
当然,在生活中吃完饭我们就把号码牌丢掉了,下次来吃重新取号就行;但是在 http 请求中,cookie 可以设置使用期限,比如说 6 个月后才过期。那么客户端接到这个内容后,就会把 cookie 缓存报本地的某个位置, 之后如果访问相同的站点,就可以直接取出对应的 cookie 来使用。接下来我们看个实际案例(又到了紧张刺激的举例子环节,这次不仅有例子,还有精美配图,我觉得可以点个赞再走~)。
首先,我们打开 chrome 浏览器的【设置】-【内容设置】-【cookie】(也可以在设置的顶部直接搜索),进入后可以查看【所有的 cookie 和网站数据】,看看 segmengt 这一条数据。里面一共有 7 个 cookie,展开看详细内容,可以看到域名,脚本可访问、到期时间等属性,

这些属性后面再介绍,为了看到整个流程,我们先清除这个站点下的 cookie,当然也可以直接 ctrl+shift+ n 打开一个隐身模式来直接测试。(隐身模式不会记录 cookie,咳咳,我当然也是为了学习写代码才发现这个功能的!)
然后我们打开 segment 站点,同时打开 f12 调试工具的 network 面板。可以看到这个请求(可以直接选 doc 类型查看,或者搜索框里搜 segment.com 快速过滤),
可以看到这就对面前面说的第一个步骤:客户端发送请求,服务端响应并提供 set-cookie(也就是发放号码牌的步骤)。
接下来我们登录这个网站并且再次请求这个站点。此时响应头里已经不再有供 set-cookie 字段,因为 cookie 已经被缓存了(可以去前面的设置里再看看),而请求头里多了一个 cookie 字段。(这就是使用号码牌的步骤了)。
接下来我们把面板的请求类型切换到 xhr,再点点页面上的功能,比如收藏本文,或者给作者点赞、打赏(疯狂暗示)等等。可以看到请求里都带上了 cookie 这个头部。
直到 cookie 过期,或者下次我们又手动删除这个站点 cookie 之前,原有的 cookie 都可以继续使用。

set-cookie 和 cookie

接下来我们来说说 set-cookie 和 cookie 字段的具体内容。按照流程首先是来自服务端的 set-cookie,直接 copy 一个前文的 cookie 下来,依此介绍:
set-cookie: test_cookie=CheckForPermission; expires=Mon, 25-Feb-2019 00:28:09 GMT; path=/; domain=.doubleclick.net

首先 test_cookie=CheckForPermission,表示这个 cookie 的键值对,每个 cookie 都会有自己的名称和值

expires=Mon, 25-Feb-2019 00:28:09 GMT 表示 cookie 的有效期,这个值如果不指定,那么默认值为到浏览器关闭之前为止

path=/, 将服务器上的某个文件目录作为 cookie 的使用对象(这么抽象的解释肯定是书上说的),简单的来说,是指定服务端有权限访 问 Cookie 的路径,例如 /session/,表示只有 /session/ 下才可以访问 cookie,默认为文档所在的目录。

domain=.doubleclick.net,表示 cookie 所在的域。默认为创建 cookie 的域。也就是请求地址,比如前面的 segment.com

secure,这个属性前面没有,表示只在 https 安全通信时才发送 cookie

httpOnly,这个属性前面也没有,是用来限制使用脚本访问 cookie 的,设置了这个值后,无法在浏览器客户端使用 js 的 document.cookie 读取 Cookie 内容(页面内部是可以访问的),这个功能可以用来防止 xss 攻击中利用 js 劫持 cookie。

而 cookie 字段就简单的多:同样看一个实例:
cookie:HPSESSID=web2~a667ecfoft7u5e3umvgam3vs65; Hm_lvt_e23800c454aa573c0ccb16b52665ac26=1551052787; _ga=GA1.2.113018985.1551052787; _gid=GA1.2.1438865670.1551052787;
直接以键值对的形式发送需要的 cookie,使用分号分割表示多个 cookie。
使用 cookie 时,服务端会对发送来的 cookie 进行校验,校验的内容为过期时间(expire)、域(domain)、路径 (path)、协议 (是否 secure),从而判断 cookie 是否有效。如果服务端已经发出了一个 cookie,之后想修改 cookie 的值怎么办呢?那就要创建一个同名的 cookie 进行覆盖删除。同名的要求是除了 name 和 expire 以外的属性要和原来的 cookie 一致,否则会被当作不同的 cookie 保存。
cookie 的优缺点
从前面的介绍我们至少可以看出 cookie 至少有以下几个优点:

cookie 的内容保存在客户端,不占用服务器资源
有效时间可配置,使用灵活
简单的键值对结构,较为轻量

当然缺点也显而易见:

cookie 的长度一般会被限制在 4k 左右,超出部分会被丢弃
浏览器可存储的 cookie 数量一般也是有数量限制的,ie8、Firefox 限制为每个域名 50 个,chrome 没有限制,但是由于规则 1 的存在,一个域名下 cookie 肯定也不能非常多,否则内容就超过 4k 了
cookie 功能可以被禁用,这就意味着基于 cookie 开发的功能要考虑如何应该这种情况
不安全,容易被截取并篡改。
某些数据必须存在服务端,比如下面即将介绍的跨设备数据同步。

cookie 的使用场景
大部分讲 cookie 的文章都会提到这两个例子,但是很少有具体的说明大概是怎么实现的。这里做下简单的介绍。
自动登录
假设某网站登录时,提供了一个可以勾选的【7 天内免登录】复选框,用户勾选并正确登录之后的流程大概是这样的:

客户端发送请求到服务端,请求信息里包含用户名密码(当然一般是加密过的,但是我发现 segment 这里登录的时候居然直接把密码明文放在 post 的 data 里,应该提个改进类的 bug 过去给他 -_-!)

服务端接收请求后,创建一个 id 比如 111,用于表示当前发送请求的客户端,并存在服务端的某个位置,简单点可以认为就存在一个名叫 sessions 的数组里面吧。之后,把这个 id 放在响应的 set-cookie 里面返回。例如:
set-cookie:sessonId=111

同时,服务端设定数组 sessions 中的保存的 111 在 7 天后删除

客户端收到这个 cookie 之后保存,之后再次访问的时候都带上这个 cookie,服务端接收到 cookie 里附带的 sessionId=111 后,去 sessions 数组查询是否含有 111,

如果发现有,说明当前用户可以自动登录, 可以直接跳转到登录之后的页面
如果不存在 (已经被自动删除了),那么说明要重新登录

(其实在上面已经悄咪咪的说了一丢丢 session 的内容,但是本着每次着重说明一个知识点、尽量剥离无关内容的原则,依然不在本文插入 session 的相关知识)
未登录时的购物车
本文已经多次提到购物车了,但是细心的朋友可以看到,这里的前面添加了“未登录”,那登录时候为什么不用呢?因为我们前面比较优缺点的时候,有提到 cookie 毕竟只能存储在发送请求的那个客户端设备,而电商网站一般是允许多终端登录的(x 宝,x 猫,x 夕夕等),如果使用 cookie 来保存登录后的购物车内容,那更换设备的时候就无法查到了,所以登录状态下的购物车一般是存到数据库中的,只在离线的时候使用 cookie 的方式来处理比较合适。
核心思路:

首先初次进入页面时,客户端发送 get 请求,服务端在响应的 set-cookie 返回给客户端一个商品列表

用户点击【将某商品添加到购物车】,客户端发送请求携带 cookie 发送请求,并且在请求体中携带商品 id

服务端接受到请求后,从请求头的 cookie 中取出商品列表,从请求体中取出本次商品 id,然后查找商品列表是否包含该商品 id,如果包含,那对应商品 id 增加;如果不存在,则从数据库查找该商品,并添加该商品信息和数量,并更新到 cookie 中

如何读写 cookie
js 读写 cookie 的方法到处都有,随便搜索下应该就能找到,本文还是着重于说明 cookie 的原理,就不在此赘述了。
小结
本文对 cookie 的原理和应用场景进行了说明,依然延续以往的“一篇文章只说一件事”的习惯(程序员的事,怎么能叫偷懒呢,这叫模块化封装)。希望能对看完的同学有所帮助(就算是只收获了表情包也是极好的)。顺便说一下,前一篇文章似乎效果不错,骗到了很多收藏和点赞,非常开心(再次疯狂暗示)

惯例:如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果有帮助,欢迎点赞和收藏,转载请征得同意后著明出处,如果有问题也欢迎私信交流,主页有邮箱地址

正文完
 0