在浏览器端,虽然以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
的。之前我曾遇到这样一个案例:后端工程师使用NodeJS+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定义的字符全部转义。例子:
window.location.href = `https://auth.suning.com?redir=${encodeURIComponent('https://www.suning.com')}`;
// https://auth.suning.com?redir=https%3A%2F%2Fwww.suning.com
window.location.href = `${encodeURI('https://www.suning.com/prod.do?location=北京')}`;
// https://www.suning.com/prod.do?location=%E5%8C%97%E4%BA%AC
复制代码
最终的目的,是在不破坏URI的前提下,完整传递参数。
encodeURI在Cookie操作中的应用
我们获取cookie的途径,不仅是响应头的set-cookie
。也许是localStorage数据,也许是一次API请求。
由于HTTP规范中,头中只允许使用ASCII字符,所以在向请求头塞cookie的时候,需要对cookie来源的字符串做预处理。这时候,就用到了encodeURI
/encodeURIComponent
。由于在GMT时间格式中存在URI的敏感字,也就是说,被encodeURI的忽略的字符集,小于header允许的字符集。所以一次性encode并不可取,需在cookie拼接完成后,做全量分析和处理。
Cookie字符串的切割与分析
(未完)