为什么要提cookie

在很多人看来,提及cookie必定要说session,但是我这里说的不是session,而是想从web前端的角度详细说明下cookie;cookie的设置服务器端和客户端都可以做,这里先看服务器端,以我较为熟悉的jsp为例:

1
2
3
4
5
6
7
8
9
10
11
12
//创建Cookie对象
Cookie cookie = new Cookie("key", "value");
//设置cookie有效期
cookie.setMaxAge(60*60*24);
//设置cookie域名
cookie.setDomain("testsaas-1014-100001.m.izhuazhua.com");
//设置cookie的路径。默认为当且页面目录下的所有url,还有目录的所有子目录
cookie.setPath("/");
//设置cookie是否加密传输
cookie.setSecure(true);
//将cookie添加到响应头,这样浏览器端会自动将cookie写入
response.addCookie(cookie);

与此对应的客户端的cookie对应的操作方法,我们也可以在mdn查到:
实际浏览器中的api只有一个document.cookie:
它是一个可读写的属性,所以客户端完全可以依据这个属性封装一套cookie操作的api,这里我摘取一段jquery.cookie.js的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as anonymous module.
define(['jquery'], factory);
} else {
// Browser globals.
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
} catch(e) {
return;
}
try {
// If we can't parse the cookie, ignore it, it's unusable.
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (value !== undefined && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// Read
var result = key ? undefined : {};
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = parts.join('=');
if (key && key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) !== undefined) {
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return true;
}
return false;
};
}));

这里详细介绍下三个属性
1.max-age和expires: 这两个属性类似,与页面缓存的机制类似;max-age是较新的属性,max-age以时间段为单位,比expires的优势就在于解决了服务器端和客户端时间不同步的问题。由于document.cookie中没有直接的删除api。所以一般删除cookie使用的是max-age=0或者负数或者expires设置为一个过去的时间。

2.path: 用于设置cookie的相对路径,举个例子

1
2
3
4
5
//当前页面为http://testsaas-1014-100001.m.izhuazhua.com/appMarket/dist/home.jsp。cookie domain为testsaas-1014-100001.m.izhuazhua.com
document.cookie="testpath="+encodeURIComponent('path value')+"; path=/appMarket/"; //在http://testsaas-1014-100001.m.izhuazhua.com/index.jsp#/index页面中就读取不到该cookie
//当前页面为http://testsaas-1014-100001.m.izhuazhua.com/appMarket/dist/home.jsp。cookie domain为testsaas-1014-100001.m.izhuazhua.com
document.cookie="testpath1="+encodeURIComponent('path1 value')+"; path=/"; //在http://testsaas-1014-100001.m.izhuazhua.com/index.jsp#/index页面中能读取到该cookie

上面的例子说明了如下问题:
1.如果不设置domain。cookie默认设置在当前最子级域名下;
2.域名相同的情况下,path的设置没有限制(即可以在任何页面设置任何path,也就是说可以在子页面中设置path为根目录,也可以在根目录下设置path在子目录下)。
3.当且页面目录下的所有url,还有目录的所有子目录可以访问到当前path的cookie。但是path如果设置为子目录。父目录中读取不到。
在这个地方我是有一次遇到问题,有深切体会。

3.domain:用于设置cookie在哪个域名下

1
2
3
4
5
6
7
//当前页面http://testsaas-1014-100001.m.izhuazhua.com/index.jsp#/index
document.cookie="testdomain="+encodeURIComponent('domain value')+"; domain=m.izhuazhua.com";
//当前页面http://testsaas-1014-100001.m.izhuazhua.com/index.jsp#/index
document.cookie="testdomain1="+encodeURIComponent('domain1 value')+"; domain=testsaas-1014-100001.m.izhuazhua.com";
//切换到页面http://testsaas-1000-100001.m.izhuazhua.com/index.jsp#/index 只能看到testdomain cookie

总结与设想类似。子域名下的永远能访问到父域名下的cookie。但是跨域的读取不到。
这个我们的项目也是有实际的使用场景。做宠物商城,以1014这个为宠物店标识,不同的宠物店三级域名是不一样的。这个时候就要将一个公用的cookie设置到二级以上的域名

4.secure: 用于设置cookie加密传输
这个没实际用过,拉上mdn的一句话,理解起来其实也很简单

;secure Cookie to only be transmitted over secure protocol as https. Before Chrome 52, this flag could appear with cookies from http domains.

另外还有一点:jsapi似乎并不能读取这些属性。document.cookie永远返回这种格式

“sname=zhuo; JSESSIONID=aaa6225A_CoOdfocn49Gv; sid=AA8yXxJNfRhAVUPHs%2F%2F3Jd6s9IGzGKgq6HPYTGawjDA%3D; testdomain=domain%20value”

只有使用chrome devtools查看这些详细属性