客户端存储技术总结

客户端存储方式

 

  1. COOKIE

由NetScape公司创造,指保存在用户本地终端内存或磁盘上的一小块数据,只能保存字符串类型,所有的cookie信息都会随着浏览器的请求而发送。尽管几乎所有浏览器都支持cookie,但容量小是它最致命的缺点,单域cookie数量不应超过20个,总长度不应超过4K,对于现在web应用本地存储数据量的需求是远远不够的,而且由于它是明文传输,禁止在cookie中保存如电话号码,账号密码等敏感信息。

 

1.1 Cookie机制

Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。

 

1.2 什么是Cookie

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。

 

1.3 Cookie的不可跨域名性

Cookie具有不可跨域名性。根据Cookie规范,浏览器访问Google只会携带Google的Cookie,而不会携带Baidu的Cookie。Google也只能操作Google的Cookie,而不能操作Baidu的Cookie。Cookie在客户端是由浏览器来管理的。浏览器能够保证Google只会操作Google的Cookie而不会操作Baidu的Cookie,从而保证用户的隐私安全。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。Google与Baidu的域名不一样,因此Google不能操作Baidu的Cookie。

 

1.4 Unicode编码:保存中文

中文与英文字符不同,中文属于Unicode字符,在内存中占4个字符,而英文属于ASCII字符,内存中只占2个字节。Cookie中使用Unicode字符时需要对Unicode字符进行编码,否则会乱码。Cookie中保存中文只能编码。一般使用UTF-8编码即可。不推荐使用GBK等中文编码,因为浏览器不一定支持,而且JavaScript也不支持GBK编码。

 

1.5 设置Cookie的所有属性

 

 

 

 

 

属  性  名

 

描    述

 

String name

 

该Cookie的名称。Cookie一旦创建,名称便不可更改

 

Object value

 

该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码

 

Int maxAge

该Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为–1

 

Boolean secure

该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false

 

String path

 

该Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”

 

String domain

可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”

 

String comment

该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明

 

int version

 

该Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范

 

1.6 Cookie的修改、删除

Cookie并不提供修改、删除操作。如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。如果要删除某个Cookie,只需要新建一个同名的Cookie,并将maxAge设置为0,并添加到response中覆盖原来的Cookie。注意是0而不是负数。负数代表其他的意义。读者可以通过上例的程序进行验证,设置不同的属性。修改、删除Cookie时,新建的Cookie除value、maxAge之外的所有属性,例如name、path、domain等,都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。

 

1.7 Cookie的域名

Cookie是不可跨域名的。域名www.google.com颁发的Cookie不会被提交到域名www.baidu.com去。这是由Cookie的隐私安全机制决定的。隐私安全机制能够禁止网站非法获取其他网站的Cookie。正常情况下,同一个一级域名下的两个二级域名如www.helloweenvsfei.com和images.helloweenvsfei.com也不能交互使用Cookie,因为二者的域名并不严格相同。如果想所有helloweenvsfei.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数,例如:

Cookie cookie = new Cookie("time","20080808"); // 新建Cookie

cookie.setDomain(".helloweenvsfei.com");           // 设置域名

cookie.setPath("/");                              // 设置路径

cookie.setMaxAge(Integer.MAX_VALUE);               // 设置有效期

response.addCookie(cookie);                       // 输出到客户端

domain参数必须以点(".")开始。另外,name相同但domain不同的两个Cookie是两个不同的Cookie。如果想要两个域名完全不同的网站共有Cookie,可以生成两个Cookie,domain属性分别为两个域名,输出到客户端。

 

1.8 Cookie的路径

domain属性决定运行访问Cookie的域名,而path属性决定允许访问Cookie的路径(ContextPath)。例如,如果只允许/sessionWeb/下的程序使用Cookie,可以这么写:

Cookie cookie = new Cookie("time","20080808");     // 新建Cookie

cookie.setPath("/session/");                          // 设置路径

response.addCookie(cookie);                           // 输出到客户端

设置为“/”时允许所有路径使用Cookie。path属性需要使用符号“/”结尾。name相同但domain相同的两个Cookie也是两个不同的Cookie。

页面只能获取它属于的Path的Cookie。例如/session/test/a.jsp不能获取到路径为/session/abc/的Cookie。使用时一定要注意。

 

1.9 Cookie的安全属性

HTTP协议不仅是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。下面的代码设置secure属性为true:

Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie

cookie.setSecure(true);                           // 设置安全属性

response.addCookie(cookie);                        // 输出到客户端

secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。

 

1.10 JavaScript操作Cookie

Cookie是保存在浏览器端的,因此浏览器具有操作Cookie的先决条件。浏览器可以使用脚本程序如JavaScript或者VBScript等操作Cookie。这里以JavaScript为例介绍常用的Cookie操作。例如下面的代码会输出本页面所有的Cookie。

<script>document.write(document.cookie);</script>

由于JavaScript能够任意地读写Cookie,有些好事者便想使用JavaScript程序去窥探用户在其他网站的Cookie。不过这是徒劳的,W3C组织早就意识到JavaScript对Cookie的读写所带来的安全隐患并加以防备了,W3C标准的浏览器会阻止JavaScript读写任何不属于自己网站的Cookie。换句话说,A网站的JavaScript程序读写B网站的Cookie不会有任何结果。

 

  1. UserData

由Microsoft公司在IE5.0中引入,指保存在用户本地终端的一块持久化字符串数据,除非手动删除或设置过期时间,否则数据将一直保存,只有IE5.0-IE9.0支持。它借助DHTML的behaviour属性来存储本地数据,允许每个页面最多存储64K数据,每个站点最多存储640K数据。它的缺点也是致命的,它并非Web标准,只有IE5.0-9.0支持,无法有效解决棘手的浏览器兼容问题。

userData的存储机制:将要保存的数据以“健值对(健作为属性,值为属性的值)”的形式保存在XML文档中。userData行为提供了一个比Cookie更具有动态性和更大容量的数据结构。每页的UserData存储区数据大小可以达到64 Kb,每个域名可以达到640 Kb。

userData行为通过sessions为每个对象分配UserData存储区。使用save和load方法将UserData存储区数据保存在缓存(cache)中。一旦UserData存储区保存以后,即使IE浏览器关闭或者刷新了,下一次进入该页面,数据也能够重新载入而不会丢失,除非你人为删除或者用脚本设置了该数据的失效期。

2.1 userData使用前必须在行内或者文档的HEAD部分宣告如下样式:

<STYLE>
      .userData {behavior:url(#default#userdata);}

 </STYLE>

或者使用脚本绑定:
object.style.behavior = "url('#default#userData')"
object.addBehavior ("#default#userData")

2.2 成员

Expires

设置或取得使用userData行为保存数据的失效日期。脚本语法:对象ID.expires = 参数参数是一个使用UTC(Universal Time Coordinate,世界调整时间)格式表示失效日期的字符串。该属性可以读写,没有默认值。浏览器会对比这个日期和当前日期,如果到期,该数据就自动失效。
getAttribute(key)取得指定的属性值。
load(存储区名)从UserData存储区载入存储的对象数据。
removeAttribute(key)从对象中删除指定的属性值。
save(存储区名) 将对象数据存入一个UserData存储区。
setAttribute(key, value)设置指定的属性值。
XMLDocument 取得存储该对象数据的XML DOM引用。

例:

o.setAttribute("code", "hello world!");
o.save("baidu");

实际上就是一个XML文件,通过文件名->属性的方式保存字符串。

执行后,UserData文件夹中会生成一个baidu[1].xml文件,其中的内容是:
<ROOTSTUB code="hello,world!"/>

在一个文件中可以有多个属性,也就是可以存储多种不同的数据。

 

 

 

  1. Flash SharedObject

它允许你在本地客户端的硬盘或是服务器上存储所有flash支持的数据(Number, String, Array, Boolean, Object, XML等),数据会永久性保存,没有过期时间,可以通过设置管理器或调用clear()方法清除。按存放位置可以分为本地共享对象和远程共享对象。默认存储大小为100KB,用户可以手动设置,最大为10M。它的问题同样致命,作为flash,它拥有flash拥有的安全、稳定性低,耗系统资源等所有缺点。

 

  1. Google Gears

Google于07年发布的一个开源浏览器插件,内置了一个基于SQLite的嵌入式SQL数据库,并提供了统一的API访问数据库。在取得用户授权之后,每个站点可以在数据库中存储大小不限的数据。但是Google早在chrome 12.0 中就已经放弃了对它的支持。

 

  1. WebStorage

相对于上述本地存储方案,之后提出的HTML5本地存储中的WebStorage本地存储得到了最广泛的支持。浏览器兼容版本有IE8+/Chrome4+/FireFox3.5+/Safari4+/Opera10.5+。它包括localStorage和sessionStorage两种存储方式,均只保存字符串类型。Firefox3.5+/Opera10.5+/IE8+都支持最大存储5M的数据,而Chrome4+/Safari4+支持最大存储2.5M的数据。

localStorage用于持久化的本地存储,浏览器窗口关闭后,localStorage存储的数据仍然可以被访问。所有浏览器窗口可以共享localStorage的数据,保存的数据永远不会过期,只能手动删除。

而sessionStorage用于本地存储一个会话中的数据,它不是一种持久化的本地存储。这些数据只有在同一个会话中的页面才能访问,当前页面不可以访问新开页面的数据,并且会话结束后数据也随之销毁而无法使用。

Name

Description

length

获取存储的键值对的数量

remainingSpace

获取存储空间剩余空间的大小(非标准,仅IE8.0+支持)

setItem(key, value)

将value值存储到本地的key字段

removeItem(key)

删除指定key本地存储的值

getItem(key)

获取指定key本地存储的值

clear()

删除localStorage中存储的所有数据

key(index)

根据索引获取一个指定位置的键名

WebStorage容量大、易用、原生支持等优点都使它成为首选的本地存储方案,当然它的安全性也较差,不能用它来保存敏感信息。

 

  1. Indexed Database API

Indexed Database 是Oracle于2009年提出的,简称IndexedDB,是一种能让你在用户的浏览器中持久地存储结构化数据的数据库,为web应用提供了丰富的查询能力。它使用对象来保存数据,按域名分配独立空间,一个独立域名下可以创建多个数据库,每个数据库可以创建多个对象存储空间,一个对象存储空间相当于一个数据库表,可以存储多个对象数据。目前仅Chrome11+/Firefox4+/IE10支持。Firefox4+支持最大存储50MB的数据(移动端5MB),chrome11+支持最大存储5MB的数据。

IndexedDB的基本工作模式是:
1) 打开数据库并开始一个事务
2) 创建一个对象存储空间作为一种交互操作对象(object store)
3) 构建请求来执行数据库操作,如增加或查询数据
4) 通过监听DOM事件来等待操作完成
5) 最后处理“请求”结果(可以在request对象中找到)

window.indexedDB=window.indexedDB||window.webkitIndexedDB||window.msIndexedDB||window.mozIndexedDB;   //浏览器兼容

var request = indexedDB.open("MyTestDB");

request.onsuccess = function(event){

  var db = request.result;

}

request.onerror = function(event){

  console.log("open error and errorCode is"+ request.errorCode );

}

 

上面代码首先调用IndexedDB对象唯一的方法open()方法打开名为”MyTestDB”的数据库,如果不存在,则会被创建。open()方法不会立刻打开数据库或者开始一个事务,它会返回一个我们可以作为事件来处理的包含result或者错误值的IDBOpenDBRequest对象。该对象有success和error事件属性。如果成功打开,会触发success事件,我们可以在事件处理程序里保存request.result这个IDBDatabase实例以供后面使用,如果发生错误,比如用户不允许你的web应用访问以创建一个数据库,则error事件会在request上触发。

var appData = [{appId:"9100", appName:"生活", appVersion:1},

{appId:"8920", appName:"娱乐", appVersion:2}];

request.onupgraddeneeded = function(event){

  var db = event.target.result;

  var objectStore = db.createObjectStore("Apps", {keyPath: "appId"});

  objectSotre.createIndex("appName","appName", {unique:false});

  for(var i in appData){

    objectStore.add(appData[i]);

  }

}

上述代码调用creatObjectStore()方法创建了一个名为Apps的对象存储空间,并且定义了一个使得存储空间中每个对象都是唯一的属性作为key path,这个属性就是appId。存储空间中的所有对象都必须拥有appId属性。此外,createIndex()方法创建了一个索引来通过appName查找app,可能会有重名的,所以不能使用unique索引。最后,调用存储空间对象的add()方法往里面添加数据。

接下来就是向我们之前创建的数据库中增加数据了。但是,在可以操作数据库之前,我们需要开始一个事务。事务来自于你创建的数据库对象,而且必须给它指定它需要跨越的对象存储空间。

var transaction = db.transaction(["Apps"], "readwrite");

transaction.oncomplete = function(event){

  //当数据库添加数据完成时执行的操作

};

transaction.onerror = function(event){

  //添加数据发生错误时的错误处理

};

var objectStore = transaction.objectStore("Apps");

for(var i in appData){

  var request = objectStore.add(appData[i]);

}

var request2 = objectStore.delete("9100");

request2.onsuccess = function(event){

  //删除数据成功后执行的操作

};

上面代码中的transaction()方法接收三个参数并返回一个事务对象。第一个参数是事务希望跨越的对象存储空间的列表,包含所有需要跨越的对象存储空间名,这里就是我们之前创建的对象存储空间Apps。后面两个参数可选,如果第二个参数未指定,默认表示得到只读事务。如果想进行写操作,用上面的"readwrite"指定。拥有了事务对象后,你就可以通过transaction.objectStore("Apps")从它那拿到在你创建事务前已经指定过的对象存储空间Apps,再调用add()方法增加你需要的数据和delete方法从数据库中删除数据。

 

  1. FileSystem

只是目前来说只有 Chrome浏览器对FileSystem API支持的比较好,所以只能运行在Chrome浏览器中。

FileSystem提供了文件夹和文件的创建、移动、删除等操作,大大方便了数据的本地处理, 而且所有的数据都是在沙盒(sandboxed)中,不同的web程序不能互相访问,这就保证了数据 的完整和安全。

 

7.1申请空间

为了进行数据的存储,必须要向浏览器进行申请,如果是永久存储还会向用户进行询问,只有 同意后才会继续执行。

window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; window.resolveLocalFileSystemURL=window.resolveLocalFileSystemURL||window.webkitResolveLocalFileSystemURL; 

得到系统的权限后就可以向浏览器申请空间

window.requestFileSystem(window.PERSISTENT, //persistent(永久) or temporary(临时) 
1024*1024, //1M 
onInitFs, //成功后的回调函数 
errorHandler); //错误后的回调函数

 

7.2判断是否申请过空间

window.webkitStorageInfo.queryUsageAndQuota(webkitStorageInfo.PERSISTENT, 
function(used, remaining){ 
if(remaining == ""){ 
console.log("未申请空间。"); 
}else{ 
console.log("已使用空间"+used); 
console.log("全部空间"+remaining); 

}, 
errorHandler);

 

7.3读取本机存储的数据

window.resolveLocalFileSystemURL(url,function(fileEntry){ 
console.log(fileEntry); 
var dirReader = fileEntry.createReader(); 
var readEntries = function(){ 
dirReader.readEntries(function(results){ 
if(!results.length){ 
create_file_title("默认文件", ""); 
console.log("没有文件!"); 
}else{ 
console.log("读取到" + results.length + "个文件"); 
for(var i = 0; i < results.length; i++){ 
console.log(results[i].name); 
getFileContentByName(fileEntry, results[i].name); 


},errorHandler); 
}; 
readEntries(); 
},errorHandler);

posted @ 2015-07-30 10:02  freewalker  阅读(251)  评论(0编辑  收藏  举报