随着Web应用程序的出现,产生了对于能够直接在客户端上存储用户信息能力的要求。比如登录信息、偏好设定或其他数据,这个问题的第一个方案是以cookie的形式出现的,今天cookie只是在客户端存储数据的其中一种选项。

cookie

HTTP Cookie,通常直接叫做cookie,最初是在客户端用于存储会话信息的。该标准要求服务器对任意HTTP请求发送Set-Cookie HTTP头作为响应的一部分,其中包含会话信息。例如,这种服务器响应的头可能如下:

HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value
Other-header: other-header-value

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

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

发送回服务器的额外信息可以用于唯一验证客服来自于发送的哪个请求。

限制

cookie在性质上是绑定在特定的域名下的。当设定了一个cookie后,再给创建它的域名发送请求时,都会包含这个cookie。这个限制确保了存储在cookie中的信息只能让批准的接受者访问,而无法被其他域访问。由于cookie是存在客户端计算机上的,还加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间。每个域的cookie总数是有限的,不过浏览器之间各有不同。如下所示:

IE6以及更低版本限制每个域名最多20个cookie。
IE7和之后版本每个域名最多50个。IE7最初是支持每个域名最大20个cookie之后被微软的一个补丁所更新。
Firefox限制每个域最多50个cookie。
Opera限制每个域最多30个cookie。
Safari和Chrome对于每个域的cookie数量限制没有硬性规定。

当超过单个域名限制之后还要再设置cookie,浏览器就会清除以前设置的cookie。IE和Opera会删除最近最少使用过的(LRU,Least Recently Used)cookie,腾出空间给新设置的cookie。Firefox看上去好像是随机决定要清除哪个cookie,所以考虑cookie限制非常重要,以免出现不可预期的后果。
浏览器中对呀cookie的尺寸也有限制。大多数浏览器都有大约4095B(含4095)以内。尺寸限制影响到一个域下所有的cookie,而并非每个cookie单独限制。
如果你尝试创建超过最大尺寸限制的cookie,那么该cookie会被悄无声息地丢掉。注意,虽然一个字符通常占用一字节,但是多字节情况则有所不同。

cookie的构成

cookie由浏览器保存的以下几块信息构成。

名称:一个唯一确定cookie的名称。cookie名称是不区分大小写的,所以myCookie和MyCookie被认为是同一个cookie。然而,实践中最好将cookie名称看作是区分大小写的,因为某些服务器会这样处理cookie。cookie的名称必须是经过URL编码的。
值:储存在cookie中的字符串值。值必须被URL编码。
域:cookie对于哪个域是有效的。所有向该域发送的请求中都会包含这个cookie信息。这个值可以包含子域(subdomain,如www.wrox.com),也可以不包含它(如.wrox.com,则对于wrox.com的所有子域都有效)。如果没有明确设定,那么这个域会被认作来自设置 cookie 的那个域。
路径:对于指定域中的那个路径,应该向服务器发送 cookie。例如,你可以指定 cookie 只有从http://www.wrox.com/books/ 中才能访问,那么 http://www.wrox.com 的页面就不会发送 cookie 信息,即使请求都是来自同一个域的。
失效时间:表示 cookie 何时应该被删除的时间戳(也就是,何时应该停止向服务器发送这个cookie)。默认情况下,浏览器会话结束时即将所有 cookie 删除;
         不过也可以自己设置删除时间。这个值是个 GMT 格式的日期(Wdy, DD-Mon-YYYY HH:MM:SS GMT),用于指定应该删除cookie 的准确时间。因此,cookie 可在浏览器关闭后依然保存在用户的机器上。如果你设置的失效日期是个以前的时间,则 cookie 会被立刻删除。
安全标志:指定后,cookie 只有在使用 SSL 连接的时候才发送到服务器。例如,cookie 信息只能发送给 https://www.wrox.com,而 http://www.wrox.com 的请求则不能发送 cookie。

每一段信息都作为 Set-Cookie 头的一部分,使用分号加空格分隔每一段,如下例所示。

HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.wrox.com
Other-header: other-header-value

该头信息指定了一个叫做 name 的 cookie,它会在格林威治时间 2007 年 1 月 22 日 7:10:24 失效,同时对于 www.wrox.com 和 wrox.com 的任何子域(如 p2p.wrox.com)都有效。

secure 标志是 cookie 中唯一一个非名值对儿的部分,直接包含一个 secure 单词。如下:
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value; domain=.wrox.com; path=/; secure
Other-header: other-header-value

这里,创建了一个对于所有 wrox.com 的子域和域名下(由 path 参数指定的)所有页面都有效的cookie。因为设置了 secure 标志,这个 cookie 只能通过 SSL 连接才能传输。
尤其要注意,域、路径、失效时间和 secure 标志都是服务器给浏览器的指示,以指定何时应该发送 cookie。这些参数并不会作为发送到服务器的 cookie 信息的一部分,只有名值对儿才会被发送。

JavaScript 中的 cookie

在JavaScript中处理cookie有些复杂,因为其众所周知的蹩脚的接口,即BOM的document. cookie属性。这个属性的独特之处在于它会因为使用它的方式不同而表现出不同的行为。当用来获取属性值时,document.cookie 返回当前页面可用的(根据 cookie 的域、路径、失效时间和安全设置)所有 cookie的字符串,一系列由分号隔开的名值对儿,如下例所示。

name1=value1;name2=value2;name3=value3

**所有名字和值都是经过 URL 编码的,所以必须使用 decodeURIComponent()来解码。
当用于设置值的时候,document.cookie 属性可以设置为一个新的 cookie 字符串。这个 cookie 字符串会被解释并添加到现有的 cookie 集合中。设置 document.cookie 并不会覆盖 cookie,除非设置的cookie 的名称已经存在。设置 cookie 的格式如下,和 Set-Cookie 头中使用的格式一样。**

name=value; expires=expiration_time; path=domain_path; domain=domain_name; secure

这些参数中,只有 cookie 的名字和值是必需的。下面是一个简单的例子。

document.cookie = "name=Nicholas";

这段代码创建了一个叫 name 的 cookie,值为 Nicholas。当客户端每次向服务器端发送请求的时候,都会发送这个 cookie;当浏览器关闭的时候,它就会被删除。虽然这段代码没问题,但因为这里正好名称和值都无需编码,所以最好每次设置 cookie 时都像下面这个例子中一样使用 encodeURIComponent()。

document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas");

要给被创建的 cookie 指定额外的信息,只要将参数追加到该字符串,和 Set-Cookie 头中的格式一样,如下所示。

document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas") + "; domain=.wrox.com; path=/";

由于 JavaScript 中读写 cookie 不是非常直观,常常需要写一些函数来简化 cookie 的功能。基本的cookie操作有三种:读取、写入和删除。它们在 CookieUtil 对象中如下表示。

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);
            if (cookieEnd == -1){
                cookieEnd = document.cookie.length;
            }
            cookieValue = decodeURIComponent(document.cookie.substring(cookieStart
            + cookieName.length, cookieEnd));
        }
        return cookieValue;
    },
    set: function (name, value, expires, path, domain, secure) {
        var cookieText = encodeURIComponent(name) + "=" +
                         encodeURIComponent(value);
        if (expires instanceof Date) {
            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);
     }
}

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。可以像下面这样使用上述方法。

//设置 cookie
CookieUtil.set("name", "Nicholas");
CookieUtil.set("book", "Professional JavaScript");
//读取 cookie 的值
alert(CookieUtil.get("name")); //"Nicholas"
alert(CookieUtil.get("book")); //"Professional JavaScript"
//删除 cookie
CookieUtil.unset("name");
CookieUtil.unset("book");
//设置 cookie,包括它的路径、域、失效日期
CookieUtil.set("name", "Nicholas", "/books/projs/", "www.wrox.com",
 new Date("January 1, 2010"));
//删除刚刚设置的 cookie
CookieUtil.unset("name", "/books/projs/", "www.wrox.com");
//设置安全的 cookie
CookieUtil.set("name", "Nicholas", null, null, null, true);
CookieExample01.htm 

这些方法通过处理解析、构造 cookie 字符串的任务令在客户端利用 cookie 存储数据更加简单。

关于 cookie 的思考

由于所有的 cookie 都会由浏览器作为请求头发送,所以在 cookie 中存储大量信息会影响到特定域的请求性能。cookie 信息越大,完成对服务器请求的时间也就越长。尽管浏览器对 cookie 进行了大小限制,不过最好还是尽可能在 cookie 中少存储信息,以避免影响性能。cookie 的性质和它的局限使得其并不能作为存储大量信息的理想手段,所以又出现了其他方法。一定不要在 cookie 中存储重要和敏感的数据。cookie 数据并非存储在一个安全环境中,其中包含的任何数据都可以被他人访问。所以不要在 cookie 中存储诸如信用卡号或者个人地址之类的数据。

cookie与session:

cookie:存在浏览器里,容量有限,不安全(用户和浏览器都能看到)
1.防篡改 2.加密
session:存在服务器,容量不用担心,安全(用户看不到)

session分为两种:
常用的cookie-session 基于cookie实现
浏览器第一次访问服务器的时候,服务器生成一个session的id,sess_id,服务器会把这个sess_id作为一个cookie还给浏览器
第二次访问服务器,浏览器带着cookie访问服务器,服务器借助浏览器带的sess_id来识别用户。

Web存储机制

Web Storage 最早是在 Web 超文本应用技术工作组(WHAT-WG)的 Web 应用 1.0 规范中描述的。这个规范的最初的工作最终成为了 HTML5 的一部分。Web Storage 的目的是克服由 cookie 带来的一些限制,当数据需要被严格控制在客户端上时,无须持续地将数据发回服务器。Web Storage 的两个主要目标是:

提供一种在 cookie 之外存储会话数据的途径;
提供一种存储大量可以跨会话存在的数据的机制。

**最初的 Web Storage 规范包含了两种对象的定义:sessionStorage 和 globalStorage。这两个对象在支持的浏览器中都是以 windows 对象属性的形式存在的,支持这两个属性的浏览器包括 IE8+、Firefox 3.5+、Chrome 4+和 Opera 10.5+。
Firefox 2 和 3 基于早期规范的内容部分实现了 Web Storage,当时只实现了globalStorage,没有实现 localStorage。**

Storage 类型

Storage 类型提供最大的存储空间(因浏览器而异)来存储名值对儿。Storage 的实例与其他对象类似,有如下方法。

clear(): 删除所有值;Firefox 中没有实现 。
getItem(name):根据指定的名字 name 获取对应的值。
key(index):获得 index 位置处的值的名字。
removeItem(name):删除由 name 指定的名值对儿。
setItem(name, value):为指定的 name 设置一个对应的值。

其中,getItem()、removeItem()和 setItem()方法可以直接调用,也可通过 Storage 对象间接调用。因为每个项目都是作为属性存储在该对象上的,所以可以通过点语法或者方括号语法访问属性来读取值,设置也一样,或者通过 delete 操作符进行删除。不过,我们还建议读者使用方法而不是属性来访问数据,以免某个键会意外重写该对象上已经存在的成员。还可以使用 length 属性来判断有多少名值对儿存放在 Storage 对象中。但无法判断对象中所有数据的大小,不过 IE8 提供了一个 remainingSpace 属性,用于获取还可以使用的存储空间的字节数。Storage 类型只能存储字符串。非字符串的数据在存储之前会被转换成字符串。

sessionStorage 对象

sessionStorage 对象存储特定于某个会话的数据,也就是该数据只保持到浏览器关闭。这个对象就像会话 cookie,也会在浏览器关闭后消失。存储在 sessionStorage 中的数据可以跨越页面刷新而存在,同时如果浏览器支持,浏览器崩溃并重启之后依然可用(Firefox 和 WebKit 都支持,IE 则不行)。因为 seesionStorage 对象绑定于某个服务器会话,所以当文件在本地运行的时候是不可用的。存储在 sessionStorage 中的数据只能由最初给对象存储数据的页面访问到,所以对多页面应用有限制。
由于 sessionStorage 对象其实是 Storage 的一个实例,所以可以使用setItem()或者直接设置新的属性来存储数据。下面是这两种方法的例子。

//使用方法存储数据
sessionStorage.setItem("name", "Nicholas");
//使用属性存储数据
sessionStorage.book = "Professional JavaScript";

不同浏览器写入数据方面略有不同。Firefox 和 WebKit 实现了同步写入,所以添加到存储空间中的数据是立刻被提交的。而 IE 的实现则是异步写入数据,所以在设置数据和将数据实际写入磁盘之间可能有一些延迟。对于少量数据而言,这个差异是可以忽略的。对于大量数据,你会发现 IE 要比其他浏览器更快地恢复执行,因为它会跳过实际的磁盘写入过程。在 IE8 中可以强制把数据写入磁盘:在设置新数据之前使用 begin()方法,并且在所有设置完成之后调用 commit()方法。看以下例子。

//只适用于 IE8
sessionStorage.begin();
sessionStorage.name = "Nicholas";
sessionStorage.book = "Professional JavaScript";
sessionStorage.commit(); 

这段代码确保了 name 和 book 的值在调用 commit()之后立刻被写入磁盘。调用 begin()是为了确保在这段代码执行的时候不会发生其他磁盘写入操作。对于少量数据而言,这个过程不是必需的;不过,对于大量数据(如文档之类的)可能就要考虑这种事务形式的方法了。
sessionStorage 中有数据时,可以使用 getItem()或者通过直接访问属性名来获取数据。两种方法的例子如下。

//使用方法读取数据
var name = sessionStorage.getItem("name");
//使用属性读取数据
var book = sessionStorage.book;

还可以通过结合 length 属性和 key()方法来迭代 sessionStorage 中的值,如下所示。

for (var i=0, len = sessionStorage.length; i < len; i++){
 var key = sessionStorage.key(i);
 var value = sessionStorage.getItem(key);
 alert(key + "=" + value);
}

它是这样遍历 sessionStorage 中的名值对儿的:首先通过 key()方法获取指定位置上的名字,然后再通过 getItem()找出对应该名字的值。还可以使用 for-in 循环来迭代 sessionStorage 中的值:

for (var key in sessionStorage){
 var value = sessionStorage.getItem(key);
 alert(key + "=" + value);
} 

每次经过循环的时候,key 被设置为 sessionStorage 中下一个名字,此时不会返回任何内置方法或 length 属性。
要从 sessionStorage 中删除数据,可以使用 delete 操作符删除对象属性,也可调用removeItem()方法。以下是这些方法的例子。

//使用 delete 删除一个值——在 WebKit 中无效
delete sessionStorage.name;
//使用方法删除一个值
sessionStorage.removeItem("book");

在撰写本书时,delete 操作符在 WebKit 中无法删除数据,removeItem()则可以在各种支持的浏览器中正确运行。
sessionStorage 对象应该主要用于仅针对会话的小段数据的存储。如果需要跨越会话存储数据,那么 globalStorage 或者 localStorage 更为合适。

localStorage 对象

由于 localStorage 是 Storage 的实例,所以可以像使用 sessionStorage 一样来使用它。下面是一些例子。

//使用方法存储数据
localStorage.setItem("name", "Nicholas");
//使用属性存储数据
localStorage.book = "Professional JavaScript";
//使用方法读取数据
var name = localStorage.getItem("name");
//使用属性读取数据
var book = localStorage.book;

存储在 localStorage 中的数据保留到通过 JavaScript 删除或者是用户清除浏览器缓存。
为了兼容只支持 globalStorage 的浏览器,可以使用以下函数。

function getLocalStorage(){
 if (typeof localStorage == "object"){
 return localStorage;
 } else if (typeof globalStorage == "object"){
 return globalStorage[location.host];
 } else {
 throw new Error("Local storage not available.");
 }
}

然后,像下面这样调用一次这个函数,就可以正常地读写数据了。
var storage = getLocalStorage();

在确定了使用哪个 Storage 对象之后,就能在所有支持 Web Storage 的浏览器中使用相同的存取规则操作数据了。

storage 事件

对 Storage 对象进行任何修改,都会在文档上触发 storage 事件。当通过属性或 setItem()方法保存数据,使用 delete 操作符或 removeItem()删除数据,或者调用 clear()方法时,都会发生该事件。这个事件的 event 对象有以下属性。

domain:发生变化的存储空间的域名。
key:设置或者删除的键名。
newValue:如果是设置值,则是新值;如果是删除键,则是 null。
oldValue:键被更改之前的值。

在这四个属性中,IE8 和 Firefox 只实现了 domain 属性。在撰写本书的时候,WebKit 尚不支持
storage 事件:
以下代码展示了如何侦听 storage 事件:

EventUtil.addHandler(document, "storage", function(event){
     alert("Storage changed for " + event.domain);
});

无论对 sessionStorage、globalStorage 还是 localStorage 进行操作,都会触发 storage事件,但不作区分。

限制

与其他客户端数据存储方案类似,Web Storage 同样也有限制。这些限制因浏览器而异。一般来说,对存储空间大小的限制都是以每个来源(协议、域和端口)为单位的。换句话说,每个来源都有固定大小的空间用于保存自己的数据。考虑到这个限制,就要注意分析和控制每个来源中有多少页面需要保存数据。
对于 localStorage 而言,大多数桌面浏览器会设置每个来源 5MB 的限制。Chrome 和 Safari 对每个来源的限制是 2.5MB。而 iOS 版 Safari 和 Android 版 WebKit 的限制也是 2.5MB。
对 sessionStorage 的限制也是因浏览器而异。有的浏览器对 sessionStorage 的大小没有限制,但 Chrome、Safari、iOS 版 Safari 和 Android 版 WebKit 都有限制,也都是 2.5MB。IE8+和 Opera 对sessionStorage 的限制是 5MB。
有关 Web Storage 的限制,请参考 http://dev-test.nemikor.com/w...

03-06 00:10