当前位置: 首页 > news >正文

客户端数据存储----Cookie From 《高程3》


前言

本篇主要介绍Cookie技术的读书总结,但是我认为逻辑上最好会和Web Storage技术放在一起进行对比,因此后续会再总结一篇关于WEB存储的姊妹总结,敬请期待。

首先先来一段总结:Cookie用于本地数据存储,出现在服务器和浏览器交互的响应Set-Cookie头部和请求Cookie头部中,受到单域名下Cookie的数量、单个Cookie大小、性能、安全限制。子Cookie技术的出现缓解了单域名下Cookie的数量限制,关于子Cookie有一整套工具函数可以使用。

HTTP Cookie 简介

用户的信息最好存储在客户端上,这就对客户端数据存储提出了要求。最早的解决方式就是Cookie。HTTP Cookie,通常直接叫做 cookie,最初是在客户端用于存储会话信息的。该标准要求服务器对任意 HTTP 请求发送 Set-Cookie HTTP 头作为响应的一部分,其中包含会话信息。

一个典型的响应头部:

 
 
  1. HTTP/1.1 200 OK 
  2. Content-type: text/html 
  3. Set-Cookie: name=value 
  4. Other-header: other-header-value  

这个 HTTP 响应设置以 name 为名称、以 value 为值的一个 cookie,名称和值在传送时都必须是URL 编码的。浏览器会存储这样的会话信息,并在这之后,通过为每个请求添加 Cookie 头将信息发送回服务器:

 
 
  1. GET /index.html HTTP/1.1 
  2. Cookie: name=value 
  3. Other-header: other-header-value  

Cookie的访问、数量和大小限制

cookie 在性质上是绑定在特定的域名下的。当设定了一个 cookie 后,再给创建它的域名发送请求时,都会包含这个 cookie。这个限制确保了储存在 cookie 中的信息只能让批准的接受者访问,而无法被其他域访问。

由于 cookie 是存在客户端计算机上的,还加入了一些限制确保 cookie 不会被恶意使用,同时不会占据太多磁盘空间。每个域的 cookie 总数是有限的,不过浏览器之间各有不同:

1)IE6 以及更低版本限制每个域名最多 20 个 cookie。

2)IE7 和之后版本每个域名最多 50 个。 IE7 最初是支持每个域名最大 20 个 cookie,之后被微软的一个补丁所更新。

3)Firefox 限制每个域最多 50 个 cookie。

4)Opera 限制每个域最多 30 个 cookie。

5)Safari 和 Chrome 对于每个域的 cookie 数量限制没有硬性规定。

当超过单个域名限制之后还要再设置 cookie,浏览器就会清除以前设置的 cookie。 IE 和 Opera 会删除最近最少使用过的(LRU, Least Recently Used) cookie,腾出空间给新设置的 cookie。 Firefox 看上去好像是随机决定要清除哪个 cookie,所以考虑 cookie 限制非常重要,以免出现不可预期的后果。

浏览器中对于 cookie 的尺寸也有限制。大多数浏览器都有大约 4096B(加减 1)的长度限制。为了最佳的浏览器兼容性,最好将整个 cookie 长度限制在 4095B(含 4095)以内。尺寸限制影响到一个域下所有的 cookie,而并非每个 cookie 单独限制。如果你尝试创建超过最大尺寸限制的 cookie,那么该 cookie 会被悄无声息地丢掉。

cookie 的构成

1)名称:一个唯一确定 cookie 的名称。cookie 名称是不区分大小写的。cookie 的名称必须是经过 URL 编码的。

2)值:储存在 cookie 中的字符串值。值必须被 URL 编码。

3)域: cookie 对于哪个域是有效的。所有向该域发送的请求中都会包含这个 cookie 信息。

4)路径:对于指定域中的那个路径,应该向服务器发送 cookie。

5)失效时间:表示 cookie 何时应该被删除的时间戳(也就是,何时应该停止向服务器发送这个cookie)。默认情况下,浏览器会话结束时即将所有 cookie 删除;不过也可以自己设置删除时间。这个值是个 GMT 格式的日期(Wdy, DD-Mon-YYYY HH:MM:SS GMT),用于指定应该删除cookie 的准确时间。因此, cookie 可在浏览器关闭后依然保存在用户的机器上。如果你设置的失效日期是个以前的时间,则 cookie 会被立刻删除。

6)安全标志:指定后, cookie 只有在使用 SSL 连接的时候才发送到服务器。例如, cookie 信息只能发送给 https://www.wrox.com,而 http://www.wrox.com 的请求则不能发送 cookie。

每一段信息都作为 Set-Cookie 头的一部分,使用分号加空格分隔每一段。secure 标志是 cookie 中唯一一个非名值对儿的部分,直接包含一个 secure 单词。尤其要注意,域、路径、失效时间和 secure 标志都是服务器给浏览器的指示(是从服务器发回的响应),以指定何时应该发送 cookie。这些参数并不会作为发送到服务器的 cookie 信息的一部分,只有名值对儿才会被发送到服务器。

设置 cookie 的格式如下,和 Set-Cookie 头中使用的格式一样,如下:

name=value; expires=expiration_time; path=domain_path;

domain=domain_name; secure

创建、删除和访问Cookie的工具函数

由于 JavaScript 中读写 cookie 不是非常直观,常常需要写一些函数来简化 cookie 的功能。基本的cookie 操作有三种:读取、写入和删除。创建cookie的工具函数:

 
 
  1. var CookieUtil = { 
  2.     get: function (name) { 
  3.     var cookieName = encodeURIComponent(name) + '='
  4.     cookieStart = document.cookie.indexOf(cookieName), 
  5.     cookieValue = null
  6.     if (cookieStart > - 1) { 
  7.       var cookieEnd = document.cookie.indexOf(';', cookieStart); 
  8.       if (cookieEnd == - 1) { 
  9.         cookieEnd = document.cookie.length; 
  10.       } 
  11.       cookieValue = decodeURIComponent(document.cookie.substring(cookieStart 
  12.       + cookieName.length, cookieEnd)); 
  13.     } 
  14.     return cookieValue; 
  15.   }, 
  16.   setfunction (name, value, expires, path, domain, secure) { 
  17.     var cookieText = encodeURIComponent(name) + '=' + 
  18.     encodeURIComponent(value); 
  19.     if (expires instanceof Date) { 
  20.       cookieText += '; expires=' + expires.toGMTString(); 
  21.     } 
  22.     if (path) { 
  23.       cookieText += '; path=' + path; 
  24.     } 
  25.     if (domain) { 
  26.       cookieText += '; domain=' + domain; 
  27.     } 
  28.     if (secure) { //secure在这里是布尔值 
  29.       cookieText += '; secure'
  30.     } 
  31.     document.cookie = cookieText; 
  32.   }, 
  33.   unset: function (name, path, domain, secure) { 
  34.     this.set(name'', new Date(0), path, domain, secure); 
  35.   } 
  36.  
  37. };  

CookieUtil.get()方法根据 cookie 的名字获取相应的值。它会在 document.cookie 字符串中查找 cookie 名加上等于号的位置。如果找到了,那么使用 indexOf()查找该位置之后的第一个分号(表示了该 cookie 的结束位置)。如果没有找到分号,则表示该 cookie 是字符串中的最后一个,则余下的字符串都是 cookie 的值。该值使用 decodeURIComponent()进行解码并最后返回。如果没有发现 cookie,则返回 null。

CookieUtil.set()方法在页面上设置一个 cookie,接收如下几个参数: cookie 的名称, cookie 的值,可选的用于指定 cookie 何时应被删除的 Date 对象, cookie 的可选的 URL 路径,可选的域,以及可选的表示是否要添加 secure 标志的布尔值。参数是按照它们的使用频率排列的,只有头两个是必需的。在这个方法中,名称和值都使用encodeURIComponent()进行了URL编码,并检查其他选项。如果expires参数是 Date 对象,那么会使用 Date 对象的 toGMTString()方法正确格式化 Date 对象,并添加到expires 选项上。方法的其他部分就是构造 cookie 字符串并将其设置到 document.cookie 中。

没有删除已有 cookie 的直接方法。所以,需要使用相同的路径、域和安全选项再次设置 cookie,并将失效时间设置为过去的时间。CookieUtil.unset()方法可以处理这种事情。它接收 4 个参数:要删除的 cookie 的名称、可选的路径参数、可选的域参数和可选的安全参数。这些参数加上空字符串并设置失效时间为 1970 年 1 月 1 日(初始化为 0ms 的 Date 对象的值),传给 CookieUtil.set()。这样就能确保删除 cookie。

FireBug测试结果

FireBug对应哪个页面,设置的cookie就存储在那个页面对应的域。打开本地apache服务器的/localhost/alien/页面,在其中打开firebug。测试实例1:

 
 
  1. CookieUtil.set("name""Nicholas"); 
  2.     CookieUtil.set("book""Professional JavaScript"); 
  3.     //读取 cookie 的值 
  4.     console.log(CookieUtil.get("name")); //"Nicholas" 
  5.     console.log(CookieUtil.get("book")); //"Professional JavaScript" 

测试实例2 删除cookie:

CookieUtil.unset("name");

CookieUtil.unset("book");

此时FireBug中不显示任何Cookie。

测试实例3 打开本地服务器localhost主页,设置安全的cookie。

CookieUtil.set("name","Nicholas", null, null, null, true);

console.log(CookieUtil.get("name"));

设置secure为true时,前面缺少的参数都定义为null。这是因为JavaScript会按照顺序对应参数。

测试结果:安全项显示“安全”。

子Cookie

子Cookie的目的是为了突破单域名下的Cookie数量限制,也就是在一个Cookie中存储多个名值对,常见格式如下:name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5

关于子Cookie的设置、获取和删除有以下工具函数:

 
 
  1. var SubCookieUtil = { 
  2.   get: function (name, subName) { 
  3.     var subCookies = this.getAll(name); 
  4.     if (subCookies) { 
  5.       return subCookies[subName]; 
  6.     } else { 
  7.       return null
  8.     } 
  9.   }, 
  10.   getAll: function (name) { 
  11.     var cookieName = encodeURIComponent(name) + '='
  12.     cookieStart = document.cookie.indexOf(cookieName), 
  13.     cookieValue = null
  14.     cookieEnd, 
  15.     subCookies, 
  16.     i, 
  17.     parts, 
  18.     result = { 
  19.     }; 
  20.     if (cookieStart > - 1) { 
  21.       cookieEnd = document.cookie.indexOf(';', cookieStart); 
  22.       if (cookieEnd == - 1) { 
  23.         cookieEnd = document.cookie.length; 
  24.       } 
  25.       cookieValue = document.cookie.substring(cookieStart + 
  26.       cookieName.length, cookieEnd); 
  27.       if (cookieValue.length > 0) { 
  28.         subCookies = cookieValue.split('&'); 
  29.         for (i = 0, len = subCookies.length; i < len; i++) { 
  30.           parts = subCookies[i].split('='); 
  31.           result[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); 
  32.         } 
  33.         return result; 
  34.       } 
  35.     } 
  36.     return null
  37.   }, 
  38.   setfunction (name, subName, value, expires, path, domain, secure) { 
  39.     var subcookies = this.getAll(name) || { 
  40.     }; 
  41.     subcookies[subName] = value; 
  42.     this.setAll(name, subcookies, expires, path, domain, secure); 
  43.   }, 
  44.   setAll: function (name, subcookies, expires, path, domain, secure) { 
  45.     var cookieText = encodeURIComponent(name) + '='
  46.     subcookieParts = new Array(), 
  47.     subName; 
  48.     for (subName in subcookies) { 
  49.       //由于采用push方法,新的子Cookie被延续到原来的Cookie中 
  50.       if (subName.length > 0 && subcookies.hasOwnProperty(subName)) { 
  51.         subcookieParts.push(encodeURIComponent(subName) + '=' + 
  52.         encodeURIComponent(subcookies[subName])); 
  53.       } 
  54.     } 
  55.     if (subcookieParts.length > 0) { 
  56.       cookieText += subcookieParts.join('&'); 
  57.       if (expires instanceof Date) { 
  58.         cookieText += '; expires=' + expires.toGMTString(); 
  59.       } 
  60.       if (path) { 
  61.         cookieText += '; path=' + path; 
  62.       } 
  63.       if (domain) { 
  64.         cookieText += '; domain=' + domain; 
  65.       } 
  66.       if (secure) { 
  67.         cookieText += '; secure'
  68.       } 
  69.     } else { 
  70.       cookieText += '; expires=' + (new Date(0)).toGMTString(); 
  71.     } 
  72.     document.cookie = cookieText; 
  73.   }, 
  74.   unset: function (name, subName, path, domain, secure) { 
  75.     var subcookies = this.getAll(name); 
  76.     if (subcookies) { 
  77.       delete subcookies[subName]; 
  78.       this.setAll(name, subcookies, null, path, domain, secure); 
  79.     } 
  80.   }, 
  81.   unsetAll: function (name, path, domain, secure) { 
  82.     this.setAll(namenull, new Date(0), path, domain, secure); 
  83.   } 
  84. };  

以下是对上述方法的解析:

获取子 cookie 的方法有两个: get()和 getAll()。其中 get()获取单个子 cookie 的值, getAll()获取所有子 cookie 并将它们放入一个对象中返回,对象的属性为子 cookie 的名称,对应值为子 cookie对应的值。 get()方法接收两个参数: cookie 的名字和子 cookie 的名字。它其实就是调用 getAll()获取所有的子 cookie,然后只返回所需的那一个(如果 cookie 不存在则返回 null)。

SubCookieUtil.getAll()方法和 CookieUtil.get()在解析 cookie 值的方式上非常相似。区别在于 cookie 的值并非立即解码,而是先根据&字符将子 cookie 分割出来放在一个数组中,每一个子 cookie再根据等于号分割,这样在 parts 数组中的前一部分便是子 cookie 名,后一部分则是子 cookie 的值。这两个项目都要使用 decodeURIComponent()来解码,然后放入 result 对象中,最后作为方法的返回值。如果 cookie 不存在,则返回 null。

set()方法接收 7 个参数: cookie 名称、子 cookie 名称、子 cookie 值、可选的 cookie 失效日期或时间的 Date 对象、可选的 cookie 路径、可选的 cookie 域和可选的布尔 secure 标志。所有的可选参数都是作用于 cookie本身而非子 cookie。为了在同一个 cookie中存储多个子 cookie,路径、域和 secure标志必须一致;针对整个 cookie 的失效日期则可以在任何一个单独的子 cookie 写入的时候同时设置。在这个方法中,第一步是获取指定 cookie 名称对应的所有子 cookie。逻辑或操作符“ ||”用于当 getAll()返回 null 时将 subcookies 设置为一个新对象。然后,在 subcookies 对象上设置好子 cookie 值并传给setAll()。

setAll()方法接收 6 个参数: cookie 名称、包含所有子 cookie 的对象以及和 set()中一样的 4个可选参数。这个方法使用 for-in 循环遍历第二个参数中的属性。为了确保确实是要保存的数据,使用了 hasOwnProperty()方法,来确保只有实例属性被序列化到子 cookie 中。由于可能会存在属性名为空字符串的情况,所以在把属性名加入结果对象之前还要检查一下属性名的长度。将每个子 cookie的名值对儿都存入 subcookieParts 数组中,以便稍后可以使用 join()方法以&号组合起来。

普通 cookie 可以通过将失效时间设置为过去的时间的方法来删除,但是子 cookie 不能这样做。为了删除一个子 cookie,首先必须获取包含在某个 cookie中的所有子 cookie,然后仅删除需要删除的那个子 cookie,然后再将余下的子 cookie 的值保存为 cookie的值。unset()方法用于删除某个 cookie 中的单个子 cookie而不影响其他的;而 unsetAll()方法则等同于 CookieUtil.unset(),用于删除整个 cookie。和 set()及 setAll()一样,路径、域和 secure 标志必须和之前创建的 cookie 包含的内容一致。

firebug测试实例

 
 
  1. //设置两个 cookie 
  2.  
  3. SubCookieUtil.set("data""name""Nicholas"); 
  4.  
  5. SubCookieUtil.set("data""book""Professional JavaScript"); 

 
 
  1. //设置全部子 cookie 和失效日期 
  2.  
  3. SubCookieUtil.setAll("data", { name"Nicholas", book: "Professional JavaScript" },new Date("January 1, 2018")); 

 
 
  1. //修改名字的值,并修改 cookie 的失效日期 
  2.  
  3. SubCookieUtil.set("data""name""Michael", new Date("February 1, 2010")); 

 
 
  1. //删除所有子CookieSubCookieUtil.unsetAll('data'); 

Cookie的限制

1)单域名下数目限制和大小限制:子Cookie只是突破了单个域名下Cookie数目限制,但是Cookie的大小依旧受限,因此要注意子Cookie的大小不能使单个Cookie超出大小限制。

2)性能限制:由于所有的 cookie 都会由浏览器作为请求头发送,所以在 cookie 中存储大量信息会影响到特定域的请求性能。 cookie 信息越大,完成对服务器请求的时间也就越长。尽管浏览器对 cookie 进行了大小限制,不过最好还是尽可能在 cookie 中少存储信息,以避免影响性能。

3)安全限制:cookie 数据并非存储在一个安全环境中,其中包含的任何数据都可以被他人访问。所以不要在 cookie 中存储诸如信用卡号或者个人地址之类的数据。

cookie 的性质和它的局限使得其并不能作为存储大量信息的理想手段,所以又出现了其他方法。


作者:zhangding

来源:51CTO

相关文章:

  • Hadoop2.6下安装Hive
  • Windows 10份额稳步上升 Win7继续下滑
  • 初识Rust语言的所有权概念
  • 《SEO的艺术(原书第2版)》——3.1 SEO从业者所能完成的策略性目标
  • 警告:未来互联网安全动荡
  • 《人民日报》教你如何正确使用路由器
  • 窥探“大数据”全貌(技术篇、产业篇、企业转型篇)
  • 揭秘大数据安全分析”架构”
  • 《深入理解Android:Telephony原理剖析与最佳实践》一3.1 何为同步和异步
  • 《C语言程序设计》一 2.5 案例学习——华氏温度与摄氏温度的转换
  • 安防傍上物联网能否迎来硬件免费时代?
  • 深入探析koa之异步回调处理篇
  • 方法和数组
  • 物联网网络技术市场的发展
  • centos中安装mysql
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • angular2开源库收集
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • Fastjson的基本使用方法大全
  • OSS Web直传 (文件图片)
  • React中的“虫洞”——Context
  • SpiderData 2019年2月16日 DApp数据排行榜
  • 电商搜索引擎的架构设计和性能优化
  • - 概述 - 《设计模式(极简c++版)》
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 关于List、List?、ListObject的区别
  • 计算机在识别图像时“看到”了什么?
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (搬运以学习)flask 上下文的实现
  • (备忘)Java Map 遍历
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (九十四)函数和二维数组
  • (转)Linux整合apache和tomcat构建Web服务器
  • (转)Scala的“=”符号简介
  • (转)关于多人操作数据的处理策略
  • (轉)JSON.stringify 语法实例讲解
  • .Net CF下精确的计时器
  • .net 托管代码与非托管代码
  • .NET运行机制
  • .pop ----remove 删除
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • @RequestBody与@ResponseBody的使用
  • [ 隧道技术 ] 反弹shell的集中常见方式(四)python反弹shell
  • [20161214]如何确定dbid.txt
  • [2021 蓝帽杯] One Pointer PHP
  • [383] 赎金信 js
  • [c]扫雷
  • [c]统计数字
  • [C和指针].(美)Kenneth.A.Reek(ED2000.COM)pdf
  • [datastore@cyberfear.com].Elbie、[thekeyishere@cock.li].Elbie勒索病毒数据怎么处理|数据解密恢复
  • [Django ]Django 的数据库操作
  • [DL]深度学习_Feature Pyramid Network
  • [iOS]-NSTimer与循环引用的理解
  • [KMP求最小循环节][HDU1358][Period]