前言
HTTP 缓存是当需要请求一个文档时,如果本地有已缓存的副本,就可以从本地存储设备中提取,而不用从服务器中提取。
缓存的好处:
* 减少了重复、冗余的数据传输
* 降低服务器的要求,服务器可以更快的响应,避免过载的情况
* 缓解了客户端网络瓶颈,不需要更多的带宽就能更快的加载页面
一个栗子
使用 Chrome 浏览器打开 taobao.com,并打开控制台查看 Network 信息如下:
可以发现有三种状态码,分别是307、304和200。
307 状态码是重定向新的地址,如下:
304 状态码表示服务器接收到了请求,但资源没有被修改,重定向到本地缓存资源
200 状态码有两种:200 from cache 和 200 from server
* 200 from memory cache 不访问服务器,直接读缓存,从内存中读取缓存资源,当kill进程后,数据将不存在
* 200 from disk cache 不访问服务器,直接读缓存,从磁盘中读取缓存资源,当kill进程时,数据还是存在
* 200 from server 访问服务器,服务器响应资源
强缓存和协商缓存
从上面的例子可以看出,从缓存读取资源有两种情况,一种是 200,一种是 304
200 的结果就是强缓存,无须访问服务器验证,直接读取本地缓存。
304 的结果是协商缓存,需要访问服务器,服务器验证资源有没有更新,没有更新则使用本地已有的缓存,如果文件更新,则响应新的资源。
强缓存
对强缓存来说,响应头中有两个字段 Expires/Cache-Control 来表明规则。
Expires
expires: Fri, 01 Feb 2019 08:06:05 GMT
expires 是 HTTP/1.0 的标准,指缓存过期的时间,超过这个时间点的资源是过期的,需要重新从服务器获取新的资源。
缺陷是需要服务器的时间和客户端的时间同步,所以更倾向于用 Cache-Control
Cache-Control
Cache-Control 是 HTTP/1.1 中的标准,可以由多个字段组合,如下是常用的取值
max-age:资源被缓存的时间,从请求开始到过期时间之间的秒数(单位是秒)
no-cache:不管缓存资源是否过期,都从服务器获取响应资源
no-store:资源不被缓存(就是想从缓存中读也不可能了),也是从每次服务器上获取响应
public:响应可以被用于任何缓存区域缓存
private: 表明响应只能被单个用户缓存,是非共享的,不能被代理服务器缓存
must-revalidate:缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源
如果 Expires 和 Cache-Control 同时使用的时候,Cache-Control 会覆盖 Expires 规则。
协商缓存
缓存时间到期了,也并不意味着有资源被修改,所以需要验证资源是否被修改,HTTP 头部有两组字段来验证:Last-modified/If-Modified-Since 和 Etag/If-None-Match
Last-modified/If-Modified-Since
Last-modified:服务器端资源的最后修改时间,响应头部会带上这个标识;
If-Modified-Since:浏览器记录上一次服务器响应的 Last-modified 时间,请求头部会带上这个标识;
第一次请求之后,浏览器记录 Last-modified 这个时间,再次请求时,请求头部带上 If-Modified-Since(即为之前记录下的时间)。服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比。若修改过就返回最新资源,状态码 200,若没有修改过则返回 304。
Etag/If-None-Match
ETag:由服务器端上生成的一段 hash 字符串,响应头带上 ETag;
If-None-Match:浏览器记录上一次服务器响应的 ETag ;
之后的请求中带上 If-None-Match 服务器检查 ETag 是否匹配,返回 304 或 200。
注:ETag 有的时候标识以 “w/” 开头,说明这个 hash 是弱的(weak),没有经过更严密的算法进行计算。
ETag 和 Last-modified 很像,区别在于 Last-modified 有 1s 的精确度问题,ETag 像指纹一样更准确。
总结
1、HTTP 缓存对于网站的加载优化是很有必要的,有三个很直观的好处;
2、HTTP/1.1 比 HTTP/1.0 一个区别就是在缓存控制上面;
3、HTTP 缓存配置是在后台的,例如 express 框架 中默认 ETag 的配置如下:
/**
* Initialize application configuration.
* @private
*/
app.defaultConfiguration = function defaultConfiguration() {
var env = process.env.NODE_ENV || 'development';
// default settings
this.enable('x-powered-by');
this.set('etag', 'weak'); // 默认 'weak'
this.set('env', env);
this.set('query parser', 'extended');
this.set('subdomain offset', 2);
this.set('trust proxy', false);
......
}