研究 Web 浏览器本地存储方式
提出四个问题:
- 什么样的数据适合放在 cookie 中?
- cookie 是怎么设置的?
- cookie 为什么会自动加到 request header 中?
- cookie 怎么增删查改?
带着问题来阅读。
cookie
cookie 的来源
Cookie 的本职工作并非本地存储,而是“维持状态”。
因为HTTP协议是无状态的,HTTP协议自身不对请求和响应之间的通信状态进行保存,通俗来说,服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现。在典型的网上购物场景中,用户浏览了几个页面,买了一盒饼干和两瓶饮料。最后结帐时,由于HTTP的无状态性,不通过额外的手段,服务器并不知道用户到底买了什么,于是就诞生了Cookie。它就是用来绕开HTTP的无状态性的“额外手段”之一。服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。
我们可以把Cookie 理解为一个存储在浏览器里的一个小小的文本文件,它附着在 HTTP 请求上,在浏览器和服务器之间“飞来飞去”。它可以携带用户信息,当服务器检查 Cookie 的时候,便可以获取到客户端的状态。
在刚才的购物场景中,当用户选购了第一项商品,服务器在向用户发送网页的同时,还发送了一段Cookie,记录着那项商品的信息。当用户访问另一个页面,浏览器会把Cookie发送给服务器,于是服务器知道他之前选购了什么。用户继续选购饮料,服务器就在原来那段Cookie里追加新的商品信息。结帐时,服务器读取发送来的Cookie就行了。
cookie 是怎么工作的?
首先必须明确一点,存储cookie
是浏览器提供的功能。cookie
其实是存储在浏览器中的纯文本,浏览器的安装目录下会专门有一个 cookie
文件夹来存放各个域下设置的cookie。
当网页要发http
请求时,浏览器会先检查是否有相应的cookie
,有则自动添加在request header
中的cookie
字段中。这些是浏览器自动帮我们做的,而且每一次http
请求浏览器都会自动帮我们做。这个特点很重要,因为这关系到“什么样的数据适合存储在cookie中”。
存储在cookie
中的数据,每次都会被浏览器自动放在http请求中,如果这些数据并不是每个请求都需要发给服务端的数据,浏览器这设置自动处理无疑增加了网络开销;但如果这些数据是每个请求都需要发给服务端的数据(比如身份认证信息),浏览器这设置自动处理就大大免去了重复添加操作。所以对于那设置“每次请求都要携带的信息(最典型的就是身份认证信息)”就特别适合放在cookie中,其他类型的数据就不适合了。
但在 localStorage
出现之前,cookie
被滥用当做了存储工具。什么数据都放在cookie
中,即使这些数据只在页面中使用而不需要随请求传送到服务端。当然cookie
标准还是做了一些限制的:每个域名下的cookie 的大小最大为4KB,每个域名下的cookie数量最多为20个(但很多浏览器厂商在具体实现时支持大于20个)。
cookie 的格式
document.cookie
JS 原生的 API提供了获取 cookie
的方法:document.cookie
,注意,这个方法只能获取非 HttpOnly 类型的 cookie
。
cookie
是一个字符串类型,因为 cookie
本身就是存储在浏览器中的字符串。但这个字符串是有格式的,由键值对 key=value
构成,键值对之间由一个分号
和一个空格
隔开。
cookie 的属性选项
每个 cookie 都有一定的属性,如什么时候失效,要发送到哪个域名,哪个路径等等。这些属性是通过cookie选项来设置的,cookie选项包括:expires、domain、path、secure、HttpOnly
。在设置任一个cookie时都可以设置相关的这些属性,当然也可以不设置,这时会使用这些属性的默认值。在设置这些属性时,属性之间由一个分号和一个空格隔开。代码示例如下:
"key=name; expires=Thu, 25 Feb 2016 04:18:00 GMT; domain=ppsc.sankuai.com; path=/; secure; HttpOnly"
expires
expires选项用来设置“cookie 什么时间内有效”。expires其实是cookie失效日期,expires必须是 GMT 格式的时间(可以通过 new Date().toGMTString()
或者 new Date().toUTCString()
来获得)。
如expires=Thu, 25 Feb 2016 04:18:00 GMT
表示cookie讲在2016年2月25日4:18分之后失效,对于失效的cookie浏览器会清空。如果没有设置该选项,则默认有效期为session,即会话cookie。这种cookie在浏览器关闭后就没有了。
expires 是 http/1.0 协议中的选项,在新的 http/1.1 协议中 expires 已经由 max-age 选项代替,两者的作用都是限制cookie 的有效时间。expires的值是一个时间点(cookie失效时刻= expires),而max-age 的值是一个以秒为单位时间段(cookie失效时刻= 创建时刻+ max-age)。
另外,max-age 的默认值是 -1(即有效期为 session );若max-age有三种可能值:负数、0、正数。负数:有效期session;0:删除cookie;正数:有效期为创建时刻+ max-age
domain 和 path
domain
是域名,path
是路径,两者加起来就构成了 URL,domain和path一起来限制 cookie 能被哪些 URL 访问。
一句话概括:
Domain 标识指定了哪些域名可以接受Cookie。如果没有设置domain,就会自动绑定到执行语句的当前域。如果设置为”.baidu.com”,则所有以”baidu.com”结尾的域名都可以访问该Cookie。
path对于指定域中的那个路径,应该向服务器发送 cookie。 例如,你可以指定 cookie 只有从http://www.wrox.com/books/ 中才能访问,那么 www.wrox.com 的页面就不会发送 cookie 信息,即使请求都是来自同一个域的。
特别说明1:
发生跨域 xhr 请求时,即使请求URL的域名和路径都满足 cookie 的 domain 和 path,默认情况下 cookie 也不会自动被添加到请求头部中。
特别说明2:
domain是可以设置为页面本身的域名(本域),或页面本身域名的父域,但不能是公共后缀 public suffix。举例说明下:如果页面域名为 www.baidu.com, domain可以设置为“www.baidu.com”,也可以设置为“baidu.com”,但不能设置为“.com”或“com”。
secure 与 http-only 以及 cookie 安全问题
secure 选项用来设置cookie只在确保安全的请求中才会发送。当请求是HTTPS或者其他安全协议时,包含 secure 选项的 cookie 才能被发送至服务器。
默认情况下,cookie不会带secure选项(即为空)。所以默认情况下,不管是HTTPS协议还是HTTP协议的请求,cookie 都会被发送至服务端。但要注意一点,secure选项只是限定了在安全情况下才可以传输给服务端,但并不代表你不能看到这个 cookie。
下面我们设置一个 secure类型的 cookie:
document.cookie = "name=huang; secure";
这里有个坑需要注意下:
如果想在客户端即网页中通过 js 去设置secure类型的 cookie,必须保证网页是https协议的。在http协议的网页中是无法设置secure类型cookie的。
httpOnly 这个选项用来设置cookie是否能通过 js 去访问。默认情况下,cookie不会带httpOnly选项(即为空),所以默认情况下,客户端是可以通过js代码去访问(包括读取、修改、删除等)这个cookie的。当cookie带httpOnly选项时,客户端则无法通过js代码去访问(包括读取、修改、删除等)这个cookie。
在客户端是不能通过js代码去设置一个httpOnly类型的cookie的,这种类型的cookie只能通过服务端来设置。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
——httpOnly与安全
从上面介绍中,大家是否会有这样的疑问:为什么我们要限制客户端去访问cookie?其实这样做是为了保障安全。
试想:如果任何 cookie 都能被客户端通过document.cookie获取会发生什么可怕的事情。当我们的网页遭受了 XSS 攻击,有一段恶意的script脚本插到了网页中。这段script脚本做的事情是:通过document.cookie读取了用户身份验证相关的 cookie,并将这些 cookie 发送到了攻击者的服务器。攻击者轻而易举就拿到了用户身份验证信息,于是就可以摇摇大摆地冒充此用户访问你的服务器了(因为攻击者有合法的用户身份验证信息,所以会通过你服务器的验证)。
如何设置 cookie?
知道了cookie的格式,cookie的属性选项,接下来我们就可以设置cookie了。首先得明确一点:cookie既可以由服务端来设置,也可以由客户端来设置。
服务端设置 cookie
不管你是请求一个资源文件(如 html/js/css/图片),还是发送一个 ajax 请求,服务端都会返回 response 。而 response header 中有一项叫 set-cookie ,是服务端专门用来设置 cookie 的。如下图所示,服务端返回的 response header 中有5个 set-cookie 字段,每个字段对应一个 cookie (注意不能将多个 cookie 放在一个 set-cookie 字段中),set-cookie 字段的值就是普通的字符串,每个 cookie 还设置了相关属性选项。
注意:
- 一个 set-Cookie 字段只能设置一个 cookie,当你要想设置多个 cookie,需要添加同样多的 set-Cookie 字段。
- 服务端可以设置 cookie 的所有选项:expires、domain、path、secure、HttpOnly
客户端设置 cookie
在网页即客户端中我们也可以通过 js 代码来设置 cookie,如:
document.cookie="age=12; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/";
注意:
客户端可以设置 cookie 的下列选项:expires、domain、path、secure(有条件:只有在https协议的网页中,客户端设置secure类型的 cookie 才能成功),但无法设置HttpOnly选项。
跨域请求中 cookie
默认情况下,在发生跨域时,cookie 作为一种 credential 信息是不会被传送到服务端的。必须要进行额外设置才可以。详见:你真的会使用XMLHttpRequest吗?
cookie 的缺陷
Cookie 不够大
Cookie的大小限制在4KB左右,对于复杂的存储需求来说是不够用的。当 Cookie 超过 4KB 时,它将面临被裁切的命运。这样看来,Cookie 只能用来存取少量的信息。此外很多浏览器对一个站点的cookie个数也是有限制的。
这里需注意:各浏览器的cookie每一个name=value的value值大概在4k,所以4k并不是一个域名下所有的cookie共享的,而是一个name的大小。
过多的 Cookie 会带来巨大的性能浪费
Cookie 是紧跟域名的。同一个域名下的所有请求,都会携带 Cookie。大家试想,如果我们此刻仅仅是请求一张图片或者一个 CSS 文件,我们也要携带一个 Cookie 跑来跑去(关键是 Cookie 里存储的信息并不需要),这是一件多么劳民伤财的事情。Cookie 虽然小,请求却可以有很多,随着请求的叠加,这样的不必要的 Cookie 带来的开销将是无法想象的。
cookie是用来维护用户信息的,而域名(domain)下所有请求都会携带cookie,但对于静态文件的请求,携带cookie信息根本没有用,此时可以通过cdn(存储静态文件的)的域名和主站的域名分开来解决。
Cookie 未加密
由于在 HTTP 请求中的 Cookie 是明文传递的,所以安全性成问题,除非用HTTPS。
cookie 与 session
本片开头已说过 HTTP 是一个不保存状态的协议。session 是服务器端使用的一种记录客户端状态的机制,不同的是 cookie 保存在客户端浏览器中,而 session 保存在服务器上。
客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是 session。客户端浏览器再次访问时只需要从该 session 中查找该客户的状态就可以了。如果说 cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 session 机制就是通过检查服务器上的“客户档案”来确认客户身份。session 相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
session对浏览器的要求
虽然 session 保存在服务器中,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为 session 需要使用 cookie 作为识别标志。HTTP 协议是无状态的,session 不能依据 HTTP 连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为 JSESSIONID 的 cookie,它的值为该 session 的 id。session 依据该cookie 来识别是否为同一用户。该 cookie 为服务器自动生成的,它的 MaxAge 属性一般为–1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。
禁用 cookie 时用 sesson 记录用户状态
URL 地址重写是对客户端不支持 cookie 的解决方案。URL 地址重写的原理是将该用户 session 的 id 信息重写到 URL 地址中。服务器能够解析重写后的 URL 获取 session 的 id。这样即使客户端不支持 cookie,也可以使用 session 来记录用户状态。
cookie session 区别
- cookie 数据存放在客户的浏览器上,session数据放在服务器上
- cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗考虑到安全应当使用 session
- session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用 COOKIE
鉴于上述区别建议:
- 将登陆信息等重要信息存放为 SESSION
- 其他信息如果需要保留,可以放在 COOKIE 中
LocalStorage
1.LocalStorage 的特点
- 保存的数据长期存在,下一次访问该网站的时候,网页可以直接读取以前保存的数据。
- 大小为5M左右
- 仅在客户端使用,不和服务端进行通信
- 接口封装较好
基于上面的特点,LocalStorage可以作为浏览器本地缓存方案,用来提升网页首屏渲染速度(根据第一请求返回时,将一些不变信息直接存储在本地)。
2.存入/读取数据
localStorage 保存的数据,以“键值对”的形式存在。也就是说,每一项数据都有一个键名和对应的值。所有的数据都是以文本格式保存。
存入数据使用setItem方法。它接受两个参数,第一个是键名,第二个是保存的数据。
localStorage.setItem("key","value");
读取数据使用 getItem 方法。它只有一个参数,就是键名。
var valueLocal = localStorage.getItem("key");
具体步骤,请看下面的例子:
<script> if(window.localStorage){ localStorage.setItem('name','world') localStorage.setItem(“gender','female') } </script> <body> <div></div> <div></div> <script> var name=localStorage.getItem('name') var gender=localStorage.getItem('gender') document.getElementById('name').innerHTML=name document.getElementById('gender').innerHTML=gender </script> </body>
3.使用场景
LocalStorage在存储方面没有什么特别的限制,理论上 Cookie 无法胜任的、可以用简单的键值对来存取的数据存储任务,都可以交给 LocalStorage 来做。
这里给大家举个例子,考虑到 LocalStorage 的特点之一是持久,有时我们更倾向于用它来存储一些内容稳定的资源。比如图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串。
sessionStorage
sessionStorage保存的数据用于浏览器的一次会话,当会话结束(通常是该窗口关闭),数据被清空;sessionStorage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 sessionStorage 内容便无法共享;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。除了保存期限的长短不同,SessionStorage的属性和方法与LocalStorage完全一样。
1.sessionStorage 的特点
- 会话级别的浏览器存储
- 大小为5M左右
- 仅在客户端使用,不和服务端进行通信
- 接口封装较好
基于上面的特点,sessionStorage 可以有效对表单信息进行维护,比如刷新时,表单信息不丢失。
2.使用场景
sessionStorage 更适合用来存储生命周期和它同步的会话级别的信息。这些信息只适用于当前会话,当你开启新的会话时,它也需要相应的更新或释放。比如微博的 sessionStorage 就主要是存储你本次会话的浏览足迹。
lasturl 对应的就是你上一次访问的 URL 地址,这个地址是即时的。当你切换 URL 时,它随之更新,当你关闭页面时,留着它也确实没有什么意义了,干脆释放吧。这样的数据用 sessionStorage 来处理再合适不过。
3.sessionStorage 、localStorage 和 cookie 之间的区别
- 共同点:都是保存在浏览器端,且都遵循同源策略。
- 不同点:在于生命周期与作用域的不同。
作用域
localStorage只要在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份localStorage数据。sessionStorage比localStorage更严苛一点,除了协议、主机名、端口外,还要求在同一窗口(也就是浏览器的标签页)下。
生命周期:
localStorage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 sessionStorage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。
Web Storage 是一个从定义到使用都非常简单的东西。它使用键值对的形式进行存储,这种模式有点类似于对象,却甚至连对象都不是——它只能存储字符串,要想得到对象,我们还需要先对字符串进行一轮解析。
说到底,Web Storage 是对 Cookie 的拓展,它只能用于存储少量的简单数据。当遇到大规模的、结构复杂的数据时,得用到IndexedDB了。
indexedDB
IndexedDB 是一种低级API,用于客户端存储大量结构化数据(包括文件和blobs)。该API使用索引来实现对该数据的高性能搜索。IndexedDB 是一个运行在浏览器上的非关系型数据库。既然是数据库了,那就不是 5M、10M 这样小打小闹级别了。理论上来说,IndexedDB 是没有存储上限的(一般来说不会小于 250M)。它不仅可以存储字符串,还可以存储二进制数据。
1.IndexedDB 的特点
键值对储存
IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
异步
IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
支持事务
IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
同源限制
IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
储存空间大
IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
支持二进制储存
IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
2.IndexedDB 的常见操作
在IndexedDB大部分操作并不是我们常用的调用方法,返回结果的模式,而是请求——响应的模式。
建立打开 IndexedDB
window.indexedDB.open("testDB")
这条指令并不会返回一个DB对象的句柄,我们得到的是一个IDBOpenDBRequest对象,而我们希望得到的DB对象在其result属性中
除了 result,IDBOpenDBRequest 接口定义了几个重要属性:
- onerror: 请求失败的回调函数句柄
- onsuccess:请求成功的回调函数句柄
- onupgradeneeded:请求数据库版本变化句柄
<script> function openDB(name){ var request=window.indexedDB.open(name)//建立打开IndexedDB request.onerror=function (e){ console.log('open indexdb error') } request.onsuccess=function (e){ myDB.db=e.target.result//这是一个 IDBDatabase对象,这就是IndexedDB对象 console.log(myDB.db)//此处就可以获取到db实例 } } var myDB={ name:'testDB', version:'1', db:null } openDB(myDB.name) </script>
控制台得到一个 IDBDatabase对象,这就是IndexedDB对象。
关闭 IndexedDB
indexdb.close()
function closeDB(db){ db.close(); }
删除 IndexedDB
window.indexedDB.deleteDatabase(indexdb)
function deleteDB(name) { indexedDB.deleteDatabase(name) }
3.WebStorage、cookie 和 IndexedDB之间的区别
Cookie 已经不建议用于存储。如果没有大量数据存储需求的话,可以使用 localStorage 和 sessionStorage 。对于不怎么改变的数据尽量使用 localStorage 存储,否则可以用 sessionStorage 存储。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论