在浏览器端, 虽然以 LocalStorage 为代表的本地持久化方案已经非常成熟, 但 cookie 因其可跨三级及以上域名, 可后端参与操作等特性, 在涉及用户验证等诸多业务中依然被广泛运用.
Cookie 规格
Cookie 的基本组成主要由 4 部分组成:
Key => Value 存储数据
Path 作用路径
Domain 作用域
Expires 过期时间, 或者叫生命周期
我们可以用谷歌控制台体会一下.
打开 suning.com, 然后在控制台输入以下代码:
document.cookie = `my_data=123; expires=${new Date(2021,0,1).toGMTString()};`
然后进入 Application->Cookie->https://www.suning.com, 可以看到 my_data 已经被写入列表.
在上面的例子中, 我们并没有指定 Path 和 Domain 两个值, 所以浏览器给出了默认值:
Path=/ 作用于根及以下分支目录
Domain=www.suning.com
作用于该域
如果希望 Cookie 在三级及以上域名作用, 可简单加一条:
document.cookie = `my_data=123; domain=suning.com; expires=${new Date(2021,0,1).toGMTString()};`
可以看到, Cookie 中增加了一条 my_data 记录 domain=.suning.com. 这里的. suning.com 可以理解为 *.suning.com.
关于 Path 项及其他 Cookie 项, 这里就不多做解释了, 大家可以自行尝试或者查询资料, 在控制台里尽情虐待浏览器的 cookie.
这里有几个小的经验追加一下:
日期格式非常松散. GMT 时间格式是 Cookie 的标准时间格式, 但其他时间格式谷歌浏览器也可以识别, 这个方面大家可以自己探索一下, 就不赘述了. 因为单纯是日期格式问题, 就可以写好多字.
Expires 时间是 UTC 时间.
浏览器的 document.cookie 虽然是以字符串形式赋值与取值, 但其背后的管理机制却是类似 Map 类型.
Cookie 的销毁, 是通过设置 expires 为一个过去时, 但需要注意其 domain path 等需要对应. 比如:
document.cookie = `my_data=123; domain=suning.com; expires=${new Date(0).toGMTString()};`
后端 Set-Cookie
后端往前端塞 cookie 是一种常用操作, 方法是在响应头中设置 set-cookie. 浏览器会接收这个头部, 并原样设置到本地 cookie 中.
- Set-Cookie: <name>=<value>[; <name>=<value>]...
- [; expires=<date>]
- [; domain=<domain_name>]
- [; path=<some_path>]
- [; secure]
- [; httponly]
这个格式看起来是不是与刚才设置 document.cookie 一致? 补充最后两项说明:
secure 表示 cookie 只能被发送到 http 服务器.
httponly 表示 cookie 不能被客户端脚本获取到.
关于 HTTP 响应头, 这里提两个注意事项:
头部的 KEY 可以重复多个. 也就是说, 头部是有可能出现多个 set-cookie 的. 之前我曾遇到这样一个案例: 后端工程师使用 Node.JS+KOA 做服务, 对允许跨域的请求设置了头部
Access-Control-Allow-Origin: *
. 当服务使用 Docker 部署后, 运维的同学对前置的 Nginx 也配置了
Access-Control-Allow-Origin
, 所以前端拿到的响应头中出现两个
Access-Control-Allow-Origin
. 结果浏览器判定此项头配置无效, 造成跨域请求失败. 当然, set-cookie 出现多个没有问题, 只是此种情况需要做适配处理.
头部的 KEY 大小写不敏感. 但这不代表处理头部的应用会大小写不敏感.
Cookie 与 encodeURI
关于 URI/URL/URN
- URI: Uniform Resource Identifier
- URL: Uniform Resource Locator
- URN: Uniform Resource Name
在进行 web 访问时, 一个 Web 地址 (URL) 由 protocol+domain+path 组成.
protocol: http/https/ftp 等等
domain: 类似 www.suning.com
path: /aaa/b/cc/d.html 酱婶儿的 这样就形成了对一个网络资源的有效定位, 也就是一个 Locator. 而这个 URL 实际上在全网范围也是一个唯一标识, 所以也是一个 URI. 简单点说, URL 是 URI 的一种.
URN 我没有遇到 (意识到?) 典型的场景, 按 RFC 来讲同样是 URI 的一个子集.
关于 encodeURI 与 encodeURIComponent
这两者的区别在于对 URI 转义的字符集不同. encodeURI 并不会对一个 URI 的分割标记做出转移, 比如:// 啦 #啦 () 啦?&=..., 等等.
对 encodeURIComponent 望文生义的话, 是对 URI 部分的转义, 意图是被转义的部分不影响 URL 的解析, 那么就必须将其中的 URI 定义的字符全部转义. 例子:
- Windows.location.href = `https://auth.suning.com?redir=${encodeURIComponent('https://www.suning.com')}`;
- // https://auth.suning.com?redir=https://www.suning.com
- Windows.location.href = `${encodeURI('https://www.suning.com/prod.do?location = 北京')}`;
- // https://www.suning.com/prod.do?location=北京
最终的目的, 是在不破坏 URI 的前提下, 完整传递参数.
encodeURI 在 Cookie 操作中的应用
我们获取 cookie 的途径, 不仅是响应头的 set-cookie. 也许是 localStorage 数据, 也许是一次 API 请求.
由于 HTTP 规范中, 头中只允许使用 ASCII 字符, 所以在向请求头塞 cookie 的时候, 需要对 cookie 来源的字符串做预处理. 这时候, 就用到了 encodeURI/encodeURIComponent. 由于在 GMT 时间格式中存在 URI 的敏感字, 也就是说, 被 encodeURI 的忽略的字符集, 小于 header 允许的字符集. 所以一次性 encode 并不可取, 需在 cookie 拼接完成后, 做全量分析和处理.
Cookie 字符串的切割与分析
(未完)
来源: https://juejin.im/post/5c1f9a11f265da615304c261