浏览器缓存
因为服务器上的资源不是一直固定不变的,大多数情况下它会更新,这个时候如果我们还访问本地缓存,那么对用户来说,那就相当于资源没有更新,用户看到的还是旧的资源;所以我们希望服务器上的资源更新了浏览器就请求新的资源,没有更新就使用本地的缓存,以最大程度的减少因网络请求而产生的资源浪费。
缓存分为两种:强缓存和协商缓存,根据响应的 header 内容来决定。
获取资源形式 | 状态码 | 发送请求到服务器 | |
---|---|---|---|
强缓存 | 从缓存取 | 200(from cache) | 否,直接从缓存取 |
协商缓存 | 从缓存取 | 304(not modified) | 是,通过服务器来告知缓存是否可用 |
1. 强缓存
直接从本地副本比对读取,不去请求服务器,返回的状态码是 200。
强缓存相关字段有 expires
,cache-control
。如果 cache-control
与 expires
同时存在的话,cache-control
的优先级高于expires
。
1.1 expires
expires
是 HTTP1.0
中定义的缓存字段。当我们请求一个资源,服务器返回时,可以在 Response Headers
中增加 expires
字段表示资源的过期时间。
expires: Thu, 03 Jan 2019 11:43:04 GMT
它是一个时间戳(准确点应该叫格林尼治时间),当客户端再次请求该资源的时候,会把客户端时间与该时间戳进行对比,如果大于该时间戳则已过期,否则直接使用该缓存资源。
但是,有个大问题,发送请求时是使用的客户端时间去对比。一是客户端和服务端时间可能快慢不一致,另一方面是客户端的时间是可以自行修改的(比如浏览器是跟随系统时间的,修改系统时间会影响到),所以不一定满足预期。
1.2 cache-control
正由于上面说的可能存在的问题,HTTP1.1
新增了 cache-control
字段来解决该问题,所以当 cache-control
和 expires
都存在时,cache-control
优先级更高。该字段是一个时间长度,单位秒,表示该资源过了多少秒后失效。当客户端请求资源的时候,发现该资源还在有效时间内则使用该缓存,它不依赖客户端时间。cache-control
主要有 max-age
和 s-maxage
、public
和 private
、no-cache
和 no-store
等值。
cache-control: public, max-age=3600, s-maxage=3600
max-age
和s-maxage
两者是cache-control
的主要字段,它们是一个数字,表示资源过了多少秒之后变为无效。在浏览器中,max-age
和s-maxage
都起作用,而且s-maxage
的优先级高于max-age
。在代理服务器中,只有s-maxage
起作用。 可以通过设置max-age
为 0 表示立马过期来向服务器请求资源。public
和private
public
表示该资源可以被所有客户端和代理服务器缓存,而private
表示该资源仅能客户端缓存。默认值是private
,当设置了s-maxage
的时候表示允许代理服务器缓存,相当于public
。no-cache
和no-store
no-cache
表示的是不直接询问浏览器缓存情况,而是去向服务器验证当前资源是否更新(即协商缓存)。no-store
则更狠,完全不使用缓存策略,不缓存请求或响应的任何内容,直接向服务器请求最新。由于两者都不考虑缓存情况而是直接与服务器交互,所以当no-cache
和no-store
存在时会直接忽略max-age
等。
1.3 pragma
既然讲到了 no-cache
和 no-store
,就顺便把 pragma
也讲了。他的值有 no-cache
和 no-store
,表示意思同 cache-control
,优先级高于 cache-control
和 expires
,即三者同时出现时,先看 pragma
-> cache-control
-> expires
。
pragma: no-cache
2. 协商缓存
会去服务器比对,若没改变才直接读取本地缓存,返回的状态码是 304。
协商缓存相关字段有 Last-Modified/If-Modified-Since
,Etag/If-None-Match
上面的 expires
和 cache-control
都会访问本地缓存直接验证看是否过期,如果没过期直接使用本地缓存,并返回 200。但如果设置了 no-cache
和 no-store
则本地缓存会被忽略,会去请求服务器验证资源是否更新,如果没更新才继续使用本地缓存,此时返回的是 304,这就是协商缓存。协商缓存主要包括 last-modified
和 etag
。
2.1 last-modified
last-modified
记录资源最后修改的时间。启用后,请求资源之后的响应头会增加一个 last-modified
字段,如下:
last-modified: Thu, 20 Dec 2018 11:36:00 GMT
当再次请求该资源时,请求头中会带有 if-modified-since
字段,值是之前返回的 last-modified
的值,如:if-modified-since:Thu, 20 Dec 2018 11:36:00 GMT
。服务端会对比该字段和资源的最后修改时间,若一致则证明没有被修改,告知浏览器可直接使用缓存并返回 304;若不一致则直接返回修改后的资源,并修改 last-modified
为新的值。
但 last-modified
有以下两个缺点:
- 只要编辑了,不管内容是否真的有改变,都会以这最后修改的时间作为判断依据,当成新资源返回,从而导致了没必要的请求响应,而这正是缓存本来的作用即避免没必要的请求。
- 时间的精确度只能到秒,如果在一秒内的修改是检测不到更新的,仍会告知浏览器使用旧的缓存。
2.2 etag
为了解决 last-modified
上述问题,有了 etag
。 etag
会基于资源的内容编码生成一串唯一的标识字符串,只要内容不同,就会生成不同的 etag
。启用 etag
之后,请求资源后的响应返回会增加一个 etag
字段,如下:
etag: "FllOiaIvA1f-ftHGziLgMIMVkVw_"
当再次请求该资源时,请求头会带有 if-none-match
字段,值是之前返回的 etag
值,如:if-none-match:"FllOiaIvA1f-ftHGziLgMIMVkVw_"
。服务端会根据该资源当前的内容生成对应的标识字符串和该字段进行对比,若一致则代表未改变可直接使用本地缓存并返回 304;若不一致则返回新的资源(状态码200)并修改返回的 etag
字段为新的值。
可以看出 etag
比 last-modified
更加精准地感知了变化,所以 etag
优先级也更高。不过从上面也可以看出 etag
存在的问题,就是每次生成标识字符串会增加服务器的开销。所以要如何使用 last-modified
和 etag
还需要根据具体需求进行权衡。