19

从经典的购物车说起

发现很多地方在介绍cookie的作用时,都喜欢使用购物车的例子,那我们也不妨从这个例子开始说起。

场景示例:有用户a和b,都登陆了某个购物网站,此时如果两个用户都点击了【查看购物车】按钮,那么此时服务器就同时收到了2个请求,请求的内容都是:“请告诉我我的购物车有什么商品”。现在问题来了——服务器怎么区分哪个请求对应哪个用户呢

其实这就是经常看到的那句话http协议是无状态的。为什么说无状态呢,因为http协议可以看成一种简单的应答模式:

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

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

http的无状态

为了解决这个问题,就要介绍今天的主角--Cookie。

cookie登场

可以联想一下生活中。我们如果去一家生意火爆的火锅店吃火锅的场景(我忽然有点想去吃个火锅再回来写了),这时如果有空座了,店员怎么知道在门口等候的顾客应该轮到谁了呢?很简单,在顾客等候之前,先发个号码牌,后面根据号码辨别身份。

?!!
灵光一闪

看到这里,是不是觉得有点饿了?啊呸,是不是觉得灵光乍现,上面说的问题和这个场景不是如出一辙吗?所以,为了能够让会话可以被识别,http中也引入了一个号码牌——cookie的机制。(会话这个词出现地有点突兀,简单解释下:a用户登录购物网站之后,接下来发生的一系列请求,都应该属于服务端与a的会话

好了,原理大概说完了,那cookie具体怎么实现呢?很简单,放在http的头部(headers)。在前一篇讲状态码时,已经稍稍涉猎了一点http头部的相关知识,简单的来说http的头部是一个js对象,里面存放了一系列的键值对(key:value)用来表示各种信息,http的【请求】和【响应】都有头部,而cookie机制的实现,则是借助了其中的两个字段:CookieSet-Cookie,整个运行流程可以分成以下步骤:

  1. 客户端发送一个http请求到服务端(我去告诉店员,我要吃火锅了)
  2. 服务端响应该请求,并且在响应头中包含了Set-Cookie(店员回应我,并且给了我一张号码牌,告诉我有任何需要都带着号码牌来申请)
  3. 客户端发送请求,请求头包含字段Cookie(我到店里后,呼叫服务员来一份雪花牛肉,并且告诉他,我的号码牌是6号
  4. 服务端根据请求中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(也就是发放号码牌的步骤)。

接下来我们登录这个网站并且再次请求这个站点。此时响应头里已经不再有供set-cookie字段,因为cookie已经被缓存了(可以去前面的设置里再看看),而请求头里多了一个cookie字段。(这就是使用号码牌的步骤了)。

接下来我们把面板的请求类型切换到xhr,再点点页面上的功能,比如收藏本文,或者给作者点赞、打赏(疯狂暗示)等等。 可以看到请求里都带上了cookie这个头部。
请求头的cookie

直到cookie过期,或者下次我们又手动删除这个站点cookie之前,原有的cookie都可以继续使用。

set-cookiecookie

接下来我们来说说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
  1. 首先test_cookie=CheckForPermission,表示这个cookie的键值对,每个cookie都会有自己的名称和值
  2. expires=Mon, 25-Feb-2019 00:28:09 GMT表示cookie的有效期,这个值如果不指定,那么默认值为到浏览器关闭之前为止
  3. path=/,将服务器上的某个文件目录作为cookie的使用对象(这么抽象的解释肯定是书上说的),简单的来说,是指定服务端有权限访 问Cookie的路径,例如/session/,表示只有/session/下才可以访问cookie,默认为文档所在的目录。
  4. domain=.doubleclick.net,表示cookie所在的域。默认为创建cookie的域。也就是请求地址,比如前面的segment.com
  5. secure,这个属性前面没有,表示只在https安全通信时才发送cookie
  6. httpOnly,这个属性前面也没有,是用来限制使用脚本访问cookie的,设置了这个值后,无法在浏览器客户端使用jsdocument.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至少有以下几个优点:

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

当然缺点也显而易见:

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

cookie的使用场景

大部分讲cookie的文章都会提到这两个例子,但是很少有具体的说明大概是怎么实现的。这里做下简单的介绍。

自动登录

假设某网站登录时,提供了一个可以勾选的【7天内免登录】复选框,用户勾选并正确登录之后的流程大概是这样的:

  1. 客户端发送请求到服务端,请求信息里包含用户名密码(当然一般是加密过的,但是我发现segment这里登录的时候居然直接把密码明文放在post的data里,应该提个改进类的bug过去给他-_-!)
  2. 服务端接收请求后,创建一个id比如111,用于表示当前发送请求的客户端,并存在服务端的某个位置,简单点可以认为就存在一个名叫sessions的数组里面吧。之后,把这个id放在响应的set-cookie里面返回。例如:

    set-cookie:sessonId=111
    

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

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

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

(其实在上面已经悄咪咪的说了一丢丢session的内容,但是本着每次着重说明一个知识点、尽量剥离无关内容的原则,依然不在本文插入session的相关知识)

未登录时的购物车

本文已经多次提到购物车了,但是细心的朋友可以看到,这里的前面添加了“未登录”,那登录时候为什么不用呢?因为我们前面比较优缺点的时候,有提到cookie毕竟只能存储在发送请求的那个客户端设备,而电商网站一般是允许多终端登录的(x宝,x猫,x夕夕等),如果使用cookie来保存登录后的购物车内容,那更换设备的时候就无法查到了,所以登录状态下的购物车一般是存到数据库中的,只在离线的时候使用cookie的方式来处理比较合适。

核心思路:

  1. 首先初次进入页面时,客户端发送get请求,服务端在响应的set-cookie返回给客户端一个商品列表
  2. 用户点击【将某商品添加到购物车】,客户端发送请求携带cookie发送请求,并且在请求体中携带商品id
  3. 服务端接受到请求后,从请求头的cookie中取出商品列表,从请求体中取出本次商品id,然后查找商品列表是否包含该商品id,如果包含,那对应商品id增加;如果不存在,则从数据库查找该商品,并添加该商品信息和数量,并更新到cookie中

如何读写cookie

js读写cookie的方法到处都有,随便搜索下应该就能找到,本文还是着重于说明cookie的原理,就不在此赘述了。
图片描述

小结

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


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


安歌
7k 声望5.5k 粉丝

目前就职于Ringcentral厦门,随缘答题, 佛系写文章,欢迎私信探讨.