0. Cookie概念
定义
记录服务器与客户端之间的状态,最早用来判断用户是否第一次访问网站。
现在已经是识别用户,实现持久会话的较好方式。 它定义了一些新的HTTP首部。
作用
Cookies的作用就是用于解决“如何记录客户端的用户信息”
当用户访问web页面时,它的名字可以记录在cookie中,当下一次访问时可以
在cookie中读取用户的访问记录。
Cookie的类型
- 会话cookie
会话cookie是一种临时cookie,记录了用户访问站点的设置和偏好preference。 用户退出浏览器时,会话cookie就被删除了。 - 持久cookie
持久cookie是存储在硬盘上的,浏览器退出后,计算机重启时它们仍然存在。持久cookie维护某个用户周期性访问的信息,比如登录名。
Cookie的本质
Cookie本质就是一个名值对构成的字符串。
一般是以下这些信息构成:
名称 :需要被URL编码,不区分大小写
值 :必须被URL编码
域: 对哪个域是有效的。默认就是发送cookie的那个域
路径: 对于指定域中的那个路径。 比如指定了
http://www.wrox.com/books , 那么
http://www.wrox.com的页面就不会发送cookie!~
失效时间(过期时间): 何时被删除的时间戳。默认是浏览器关闭时候所有cookie删除。 这个值是一个GMT格式的时间 (Wdy,DD-Mon-YYYY HH:MM:SS GMT)
安全标志 指定后,cookie只有在使用SSL连接的时候才发送到服务器。
HTTP response示例:
HTTP/1.1 200 OK
Content-type:text/html
Set-Cookie:name=value; expire=Mon,22-Jan-07 07:10:24 GMT; domain=.wrox.com; path=/;secure
值得注意的是:域 路径 失效时间 secure标志都是服务器给浏览器的指示,指定何时发送cookie。这些参数不会作为发送给服务器的cookie信息的一部分,只有名值对会发送!
Secure是唯一一个非名值对的。
客户端存储的cookie示例
如:
Cookie: NOWCODERUID=7CAEE918A9211F1F5B3D8DB4913496AB; t=1204D82E035A9BD09D0AA90A96DBA998; NOWCODERCLINETID=CBD2F59F5E8C7B7403269B5E9E1BEAC7; CNZZDATA1253353781=155670659-1487244501-https%253A%252F%252Fwww.nowcoder.com%252F%7C1487472599; SERVERID=aff739a092fc0d444b24c3a30d4864b6|1487477718|1487244493
持久Cookie和会话cookie的区别其实就是过期时间不同。 一般是Expire或Max_Age来决定。
Cookie的局限
- IE6或更低版本最多20个cookie
- IE7和之后的版本最后可以有50个cookie。
- Firefox最多50个cookie
- chrome和Safari没有做硬性限制,
IE和Opera 会清理近期最少使用的cookie,Firefox会随机清理cookie。 - cookie的最大大约为4096字节,为了兼容性,一般不能超过4095字节。
1.COOKIE处理:
服务器向客户端发Cookie
COOKIE处理:
A. 服务器向客户端发Cookie
例如:
HTTP/1.1 200 OK
Content-type:text/html
Set-Cookie:name=value
Other-header:other-header-value
B 浏览器将Cookie保存
C 每次浏览器会将Cookie发到服务器端
例如:
GET /index.html HTTP/1.1
Cookie:name=value
Other-header:other-header-value
如下图:
我们的Web服务器会通过HTTP扩展首部 Set-Cookie或Set-Cookie2 把cookie贴到每个用户上。
浏览器会存储从服务器返回的Set-Cookie中的内容,并将cookie存储在浏览器的cookie文件夹中。也就是说会存储在硬盘上。
IE对Cookie的处理
IE将cookie存储在高速缓存目录下独立的文本文件中。可以在硬盘中查看到。
IE特有的cookie格式文件
CNZZDATA3258975
cnzz_eid%3D958325568-1466428170-http%253A%252F%252Fwww.cnki.net%252F%26ntime%3D1473251452
adp.cnki.net/
1600
3240902912
30578703
2587134064
30542091
*
Cookie一个接一个地存储在文件中,每个cookie由多行构成。
名字 CNZZDATA3258975
值 cnzz_eid%3D958325568-1466428170-http%253A%252F%252Fwww.cnki.net%252F%26ntime%3D1473251452
域/路径 adp.cnki.net/
不同web站点使用不同的cookie
浏览器中可能有成百上千的cookie,但浏览器不会把每个cookie都发给所有的站点! 也就是说哪个站点产生的cookie,就会发送给哪个。
这就是所谓的绑定域名,设定了一个cookie后,再给创建它的域名发送请求时都会包含这个cookie~
原因:
- 对所有cookie进行传输会严重降低性能。浏览器实际传输的cookie字节数比实际内容字节数多!
- cookie中包含的是服务器特有的名值对,大部分站点来说,大多数cookie没有意义
- 将所有的cookie发送给所有站点会引发潜在的隐私问题,那些你不信任的站点也会获得其他站点的信息。
例如: joes-hardware.com 产生的cookie只会发送给joes-hardware.com,不会发送给baidu.com
2.客户端JS操作Cookie
document.cookie=”username=John doe”;
创建cookie
可以添加一个过期时间,默认是浏览器关闭时删除
您可以使用 path 参数告诉浏览器 cookie 的路径。默认情况下,cookie 属于当前页面。
例如:
document.cookie="username=John doe;expire=Thu, 18 Dec 2013"
读取Cookie
var x=document.cookie;
var ca=x.split(";");
修改cookie :
document.cookie=”xx” 就会自动覆盖旧的cookie了
var x=document.cookie;
x="ID=223;exire="THU,18,DEC 2015";
//常用用法
document.cookie=cname+"="+cvalue+": "+expires;
查询cookie
function checkCookie(){
var user=getCookie("username");
if(user!=""){
alert("Welcome again"+user);
}
else{
user=prompt("Please enter your name:","");
if(user!=""&&user!=null){
setCookie("username",user,30);
}
}
}
封装操作
写一个CookieUtil对象来包含这些处理
注意,其实BOM提供了专门的函数decodeURIComponent和encodeURICompoent来URL编码和URL解码!
document.cookie解释:
当用来获取属性值时,返回当前页面可用的(根据cookie的域、路径、失效时间和安全设置) 所有cookie字符串。 由分号分隔。
当用来设置时,设置一个新的cookie字符串,不会覆盖,只会被解释并添加到现有的cookie集合中。
除非设置的cookie名称已经存在。
代码:
var CookieUtil={
get:function(name){
var cookieName=encodeURIComponent(name)+"=",
cookieStart=document.cookie.indexOf(";"+cookieName),
cookieValue=null;
if(cookieStart>-1){
var cookieEnd=document.cookie.indexOf(";",cookieStart); //;cookieStart 的index
if(cookieEnd==-1){
cookieEnd=document.cookie.length;
}
cookieValue=decodeURIComponent(document.cookie.substring(cookieStrart+cookieName.length,cookieEnd ));
}
return cookieValue;
},
set:function(name,value,expires,path,domain,secure){
var cookieText=encodeURIComponent(name)+"="+encodeURIComponent(value);
if(expires instanceof Date){//instanceof
cookieText+="; expires="+expires.toGMTString();
}
if(path){
cookieText+="; path="+path;
}
if(domain){
cookieText+="; domain="+domain;
}
if(secure){
cookieText+="; secure"
}
document.cookie=cookieText;
},
unset:function(name,path,domain,secure){
this.set(name,"",new Date(0),path,domain,secure);
}
}
解释:
get()方法根据cookie的名字获取相应的值。它会在document.cookie字符串中查找cookie名加上”=” 的位置。 如果找到了,使用indexOf查找该位置之后第一个分号。 如果没有找到分号,就说明这个cookie是字符串最后一个, 则余下的字符串都是cookie的值。 用decodeURIComponent解码并返回。
set()方法很简单,接收前面介绍的几个参数值。除了值以外的都是可选的。 名值进行了URL编码。其他就是添加 分号 和本身的关键字。
删除没有直接方法,我们只能使用unset,它的原理是设置 相同的路径,域,和安全选项再次设置cookie,并将过期时间设置为过去的时间!
new Date(0) 就是 1970年1月1日返回的值。
子cookie
为了突破浏览器单域名下的cookie数量的限制,使用子cookie。 它就是存放下单个cookie下的跟小段的数据。 就是使用cookie值来存放多个名值对!
如:
name=name1=value1&name2=value2&name3=value3
子cookie以查询字符串的格式进行格式化。 这些值可以使用单个cookie进行存储和访问! 但是注意的是全部cookie的大小仍然限制在4KB。
var SubCookieUtil={
get:function(name,subName){
var subCookies=this.getAll(name);
if(subCookies){
return subCookies[subName];
}else{
return null;
}
},
// name=value; cookieStart就是 name= cookieEnd就是 value;
getAll:function(name){
var cookieName=encodeURIComponent(name)+'=',
cookieStart=document.cookie.indexOf(cookieName),
cookieValue=null,
cookieEnd,
subCookies,
i,
parts,
result={};
if(cookieStart>-1){
cookieEnd=document.cookie.indexOf(";",cookieStart);
if(cookieEnd==-1){
cookieEnd=document.cookie.length;
}
cookieValue=document.cookie.substring(cookieStart+cookieName.length,cookieEnd);
if(cookieValue.length>0){
subCookies=cookieValue.split("&");
for(i=0,len=subCookies.length;i<len;i++){
parts=subCookies[i].split("=");
result[decodeURIComponent(parts[0])]=decodeURIComponent(parts[1]);
}
return result;
}
}
}
return null;
};
对于获取子cookie。 其中get()方法获取单个子cookie的值,getAll()获取所有子cookie。
get()方法其实就是调用getAll()获取所有的子cookie,返回所需的那个。不存在就返回null。
而getAll()方法和CookieUtil.get()方法相似关键是 先根据&解析每个子cookie并放到一个数组中,然后对于每个子cookie再根据=分割。 这样在parts数组中前一部分就是cookie名字后一部分就是cookie值。
var SubCookieUtil={
get:function(name,subName){
var subCookies=this.getAll(name);
if(subCookies){
return subCookies[subName];
}else{
return null;
}
},
// name=value; cookieStart就是 name= cookieEnd就是 value;
getAll:function(name){
//URL编码,其实就是对一些特殊字符进行转码!例如把中文"你好"
//转成 %e4%bd%a0%e5%a5%bd 百分号的形式
var cookieName=encodeURIComponent(name)+'=',
cookieStart=document.cookie.indexOf(cookieName),
cookieValue=null,
cookieEnd,
subCookies,
i,
parts,
result={};
if(cookieStart>-1){
//找到cookie的后面部分,从cookieStart开始搜索
cookieEnd=document.cookie.indexOf(";",cookieStart);
if(cookieEnd==-1){
//是否为末尾,为-1就是末尾,没找到。
cookieEnd=document.cookie.length;
}
cookieValue=document.cookie.substring(cookieStart+cookieName.length,cookieEnd);
if(cookieValue.length>0){
//先分割出每一个子cookie
subCookies=cookieValue.split("&");
for(i=0,len=subCookies.length;i<len;i++){
parts=subCookies[i].split("=");
result[decodeURIComponent(parts[0])]=decodeURIComponent(parts[1]);
}
return result;
}
}
return null;
},
//设置子cookie
set:function(name,subName,value,expires,path,domain,secure){
var subcookies=this.getAll(name)||{};
subcookies[subName]=value;
this.setAll(name,subcookies,expires,path,domain,secure);
},
setAll:function(name,subcookies,expires,path,domain,secure){
var cookieText=encodeURIComponent(name)+"=",
subcookiesParts=[],
subName;
for(subName in subcookies){
if(subName.length>0&&subcookies.hasOwnProperty(subName)){
subcookiesParts.push(encodeURIComponent(subName)+"="+
encodeURIComponent(subcookies[subName]));
}
}
if(subcookiesParts.length>0){
cookieText+=subcookiesParts.join("&");
if(expires instanceof Date){
cookieText+="; expires="+expires.toGMTString();
}
if(path){
cookieText+="; path="+path;
}
if(domain){
cookieText+="; domain"+domain;
}
if(secure){
cookieText+="; secure";
}
}else{
//设置一个过期时间
cookieText+="; expires="+(new Date(0)).toGMTString();
}
document.cookie=cookieText;
}
};
注意,上面的所有这些可选参数都是作用于整个cookie本身,而非单个子cookie。 为了在同一个cookie中存储多个子cookie,这些标志都必须一致的。
set方法,第一步是获取指定cookie的名称对应的所有子cookie。 || 可以在当getAll返回null的时候为subcookies设置一个新的对象。 然后在subcookies对象上设置好子cookie值并传给setAll() 接收6个参数,可以设置名值以及其他可选参数。 使用for in遍历subcookies中的属性。注意for in 会枚举包括原型链上的属性,所以加了hasOwnProperty来保证只有实例属性被序列化到子cookie中。
注意 由于可能存在属性名为空字符串的情况,还要检查一下属性名的长度。
将每个子cookie的名值对都存入subcookiesParts数组中,然后用join方法连接并存到cookieText中
删除子cookie
unset:function(name,subName,path,domain,secure){
var subcookies=this.getAll(name);//subcookie name
if(subcookies){
delete subcookies[subName];
this.setAll(name,subcookies,null,path,domain,secure);
}
},
unsetAll:function (name,path,domain,secure) {
// body...
// new Date(0)
this.setAll(name,null,new Date(0),path,domain,secure);
}
注意删除子cookie是不能直接将cookie的失效时间设置为过去的时间。 为了删除一个子cookie必须获取包含在某个cookie中的所有子cookie,然后仅删除需要删除的那个子cookie,然后再将余下的子cookie值保存为cookie的值。
unset 用于删除某个cookie中的单个子cookie。
unsetAll用于删除整个cookie
三 cookie的思考
还有一类称为 HTTP专有cookie。 这种cookie只能被从服务器读取,可以在浏览器或者服务器设置,因为JS无法获取HTTP专有cookie的值。
缺点
- 所有的cookie都会由浏览器作为请求头发送。 cookie信息越大,完成对服务器请求的时间就越长。
- Cookie数量和长度的限制。每个domian最多20条cookie,每个cookie长度最多不超过4KB,所以尽管你使用子cookie还是不能突破这个长度限制。 超过的会被截掉
- 安全性问题,cookie被人拦截了,它就可以获取所有的session信息。 即使加密也没办法。因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。
- 有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。