Cookie
定义
Cookie(复数形态Cookies),中文名称为“小型文本文件”或“小甜饼”[1],指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。由网景公司的前雇员卢·蒙特利在1993年3月发明[2]。最初定义于RFC 2109。目前使用最广泛的 Cookie标准却不是RFC中定义的任何一个,而是在网景公司制定的标准上进行扩展后的产物。
分类
Cookie总是保存在客户端中,按在客户端中的存储位置,可分为内存Cookie和硬盘Cookie。
内存Cookie由浏览器维护,保存在内存中,浏览器关闭后就消失,硬盘Cookie保存在硬盘里,有一个过期时间,除非用户手工清理或到了过期时间,硬盘Cookie不会被删除,其存在时间是长期的。
用途
因为HTTP协议是无状态的,即服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现。
比如购物网站, 由于http是无状态的, 所以如果不采用其他手段. 服务器根本不知道用户到底买了什么, 购物车里到底保存了什么,
在刚才的购物场景中,当用户选购了第一项商品,服务器在向用户发送网页的同时,还发送了一段Cookie,记录着那项商品的信息。当用户访问另一个页面,浏览器会把Cookie发送给服务器,于是服务器知道他之前选购了什么。用户继续选购饮料,服务器就在原来那段Cookie里追加新的商品信息。结帐时,服务器读取发送来的Cookie就行了。
特点
- 服务器通过 Set-Cookie 响应头设置 Cookie
- 浏览器得到 Cookie 之后, 每次请求都要带上 Cookie
- 服务器读取 Cookie 就知道登陆用户的信息
- Cookie 可以在开发者工具的 application 中修改
- Cookie 有效期默认20分钟左右
- 后端可以强制设置有效期
过程图
我们以登陆界面为例
session
- 将 SessionID(随机数)通过 Cookie 发送给客户端
- 客户端访问服务器时, 服务器读取 SessionID
- 服务器有一块内存(哈希表)保存了所有 session
- 通过 SessionID 我们可以得到对应用户的隐私信息, 如 id 等
- 这块内存(哈希表)就是服务器上的所有 session
localStorage
html5提供的一个API, 在浏览器建立一个哈希表用来存储内容, 只接受字符串
使用
localStorage干什么用?
我们都知道, 假设我们在js里面声明了一个变量, 并给变量赋值, 当我们关闭或者刷新页面后, 之前声明的变量就销毁了, 没有办法永久保存, 当我们想要在关闭或刷新页面后, 持久化保留某个变量和它的值的时候, 就可以利用localStorage来保存它。
假设我们有以下场景
- 第一次登陆网页提示网站改版
- 之后再登陆不提示
我们就可以用 localStorage 实现
let already = localStorage.getItem('alreadyAlert')
if(!already) {
alert('你好, 我们网站改版了, 有了这些新功能.....')
localStorage.setItem('alreadyAlert', true)
}
特点
- localStorage 跟 HTTP 无关
- HTTP 不会带上 localStorage 的值
- 只有相同域名的页面才能互相读取 localStorage
- 每个域名 localStorage 最大存储量为 5Mb 左右
- 常用场景: 记录有没有提示过用户等功能(没有用的信息, 不能记录密码)
- localStorage 永久有效, 除非用户清理缓存
SessionStorage
1、2、3、4同上
- SessionStorage 在用户关闭页面后就失效
- 用法也同localStorage
Cache-Control(缓存控制)
缓存
缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当 web 缓存发现请求的资源已经在本地存储,它就会拦截请求,返回保存的资源,而不会去源服务器重新下载。
缓存可以缓解服务器端压力,提升性能(获取资源的耗时更短了)。对于网站来说,缓存是达到高性能的重要手段之一。缓存需要合理配置,因为并不是所有资源都是永久不变的:重要的是对一个资源的缓存应截止到其下一次发生改变(即不能缓存过期的资源)。
缓存种类
缓存的种类有很多,其大致可归为两类:私有缓存与共享缓存。
共享缓存存储的响应能够被多个用户使用。私有缓存只能用于单独用户。
我们主要介绍浏览器与代理缓存
[站外图片上传中...(image-612554-1524639617439)]
(私有)浏览器缓存
私有缓存只能用于单独用户。你可能已经见过浏览器设置中的“缓存”选项。浏览器缓存拥有用户通过 HTTP 下载的所有文档。这些缓存为浏览过的文档提供向后/向前导航,保存网页,查看源码等功能,可以避免再次向服务器发起多余的请求。它同样可以提供缓存内容的离线浏览。
(共享)代理缓存
共享缓存可以被多个用户使用。例如,ISP 或你所在的公司可能会架设一个 web 代理来作为本地网络基础的一部分提供给用户。这样热门的资源就会被重复使用,减少网络拥堵与延迟。
缓存控制
Cache-control 头
HTTP/1.1定义的 Cache-Control
头用来区分对缓存机制的支持情况, 请求头和响应头都支持这个属性。通过它提供的不同的值来定义缓存策略。
禁止进行缓存
缓存中不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容。
Cache-Control: no-store
Cache-Control: no-cache, no-store, must-revalidate
强制确认缓存
如下头部定义,此方式下,每次有请求发出时,缓存会将此请求发到服务器(该请求应该会带有与本地缓存相关的验证字段),服务器端会验证请求中所描述的缓存是否过期,若未过期(实际就是返回304),则缓存才使用本地缓存副本。
Cache-Control: no-cache
私有缓存和公共缓存
"public" 指令表示该响应可以被任何中间人(比如中间代理、CDN等)缓存。若指定了"public",则一些通常不被中间人缓存的页面(因为默认是private)(比如 带有HTTP验证信息(帐号密码)的页面 或 某些特定影响状态码的页面),将会被其缓存。
而 "private" 则表示该响应是专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中。
Cache-Control: private
Cache-Control: public
缓存过期机制
过期机制中,最重要的指令是 "max-age=<seconds>
",表示资源能够被缓存(保持新鲜)的最大时间。相对Expires而言,max-age是距离请求发起的时间的秒数。针对应用中那些不会改变的文件,通常可以手动设置一定的时长以保证缓存有效,例如图片、css、js等静态资源。
Cache-Control: max-age=31536000
缓存验证确认
当使用了 "must-revalidate
" 指令,那就意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用。详情看下文关于缓存校验的内容。
Cache-Control: must-revalidate
新鲜度
理论上来讲,当一个资源被缓存存储后,该资源应该可以被永久存储在缓存中。由于缓存只有有限的空间用于存储资源副本,所以缓存会定期地将一些副本删除,这个过程叫做缓存驱逐。另一方面,当服务器上面的资源进行了更新,那么缓存中的对应资源也应该被更新,由于HTTP是C/S模式的协议,服务器更新一个资源时,不可能直接通知客户端及其缓存,所以双方必须为该资源约定一个过期时间,在该过期时间之前,该资源(缓存副本)就是新鲜的,当过了过期时间后,该资源(缓存副本)则变为陈旧的。驱逐算法用于将陈旧的资源(缓存副本)替换为新鲜的,注意,一个陈旧的资源(缓存副本)是不会直接被清除或忽略的,当客户端发起一个请求时,缓存检索到已有一个对应的陈旧资源(缓存副本),则缓存会先将此请求附加一个If-None-Match头,然后发给目标服务器,以此来检查该资源副本是否是依然还是算新鲜的,若服务器返回了 304 (Not Modified)(该响应不会有带有实体信息),则表示此资源副本是新鲜的,这样一来,可以节省一些带宽。若服务器通过 If-None-Match
或 If-Modified-Since
判断后发现已过期,那么会带有该资源的实体内容返回。
下面是上述缓存处理过程的一个图示:
[站外图片上传中...(image-ea0fc1-1524639617440)]
对于含有特定头信息的请求,会去计算缓存寿命。比如Cache-control: max-age=N的请求头,相应的缓存的寿命就是N。通常情况下,对于不含这个属性的请求则会去查看是否包含Expires属性,通过比较Expires的值和头里面Date属性的值来判断是否缓存还有效。如果max-age和expires属性都没有,找找头里的Last-Modified信息。如果有,缓存的寿命就等于头里面Date的值减去Last-Modified的值除以10(注:根据rfc2626其实也就是乘以10%)。
缓存失效时间计算公式如下:
expirationTime = responseTime + freshnessLifetime - currentAge
上式中,responseTime 表示浏览器接收到此响应的那个时间点。
缓存更新
通常我们对于css文件和js文件的max-age
都设置1年以上, 但是如果这期间文件更新了怎么办呢?
只有url相同才会调用缓存, 所以当有文件更新时候, 对文件名加入随机数或者MD5值使文件路由地址不一样就可以了, 比如我们刚开始的文件
<link rel="stylesheet" href="/style.css">
若文件更新了, 我们让文件名变成
<link rel="stylesheet" href="/style125445.css">
这样引用文件的路由就变了, 这样就不会使用之前的缓存了
下面的截图是百度的请求, css请求和js请求后面的那些不规则数字就是利用我们这种方法实现缓存更新的
Expires(不常用)
在Cache-Control出来之前我们用Expires, 现在用的不多了, 这里就大概提一下, 比如在node中, 我们跟设置其他响应头一样, 在响应头中设置Expire时间, 到了那个时间缓存就会过期, 这个时间需要用GMT时间, 但是这有个缺点, 这个时间是根据机器的本地时间来判断的
response.setHeader('Expires', 'Sun, 04 Feb 2018 14:3:05 GMT')
Etag
MD5
信息摘要算法, 用于校验文件内容是否相同, 相同内容的文件MD5值相等, 内容差异越小, MD5值差异越大。
通常我们在下载电影或者安装包等文件的时候经常会用到MD5校验, 用来校验我们下载的文件是不是跟目标文件一样。
所以, 对于js文件或者css文件, 我们同样可以用MD5来判断文件是否相同, 假设我们在nodejs中已经引用了MD5包, 并算出了js文件的MD5值, 我们设置Etag并赋值为js文件的MD5值
response.setHeader('ETag', fileMD5)
当我们在响应头中设置ETag后, 在下次请求过来时, 请求头就会带有一个if-none-match
, 它的值就是上次响应头中的ETag
值, 这样我们就可以在后端检验这个值, 如果这个值跟这次响应中的ETag值不相等, 就更新文件
response.setHeader('ETag', fileMD5)
if(request.headers['if-none-match'] === fileMD5) {
response.statusCode = 304
//不用返回响应体
}else {
// 返回新版js文件
response.write(newJsFile)
response.end()
}