缓存相关header
- Expires:响应头(服务端返回给客户端),代表该资源过期时间。
- Cache-Control:响应头,缓存控制字段,含有很多值,比如max-age(相对过期时间)、no-cache(不做缓存检测)等,能精确控制缓存策略
- Last-Modified:响应头,资源最近修改时间,有服务器告知浏览器
- If-Modified-Since:请求头(浏览器告知服务器),资源最近修改时间,值等于上次请求资源时服务端返回的Last-Modified
- ETag:响应头,资源标识,由服务器告知浏览器
- If-Node-Match:请求头,缓存资源标识,由浏览器告知服务器,值等于上次请求时服务器返回的ETag。
浏览器的缓存机制
如果没有缓存机制,浏览器每次请求资源,服务器都得读取磁盘文件,返回给客户端。
这样既浪费用户流量,又浪费服务器资源,而且浏览器必须等待资源文件下载完才能渲染页面,影响用户体验。
所以浏览器增加了缓存机制,第二次请求不会再向服务器发起请求,而是直接从浏览器缓存读取。
但是当服务器上的资源更新时,浏览器无法感知,从而无法拿到最新的资源。
因此增加了各种HTTP头部来解决缓存的相关问题。
服务器与浏览器约定资源过期时间:Expires
服务器和浏览器约定文件过期时间,用 Expires 字段来控制,时间是 GMT 格式的标准时间,如 Fri, 01 Jan 1990 00:00:00 GMT。
- 浏览器发起请求资源,服务器返回资源并且增加了Expires响应头
- 在Expires指定的时间之前,浏览器不再像服务器请求资源,直接使用缓存的文件
- 缓存时间过了之后,浏览器再请求服务器,服务器返回资源,并返回新的过期时间
缺点:
- 只能在缓存时间过了之后才能请求服务器,如果在缓存过期之前服务器上的文件更新了呢
- 缓存时间到了之后,服务器不管文件有无更新,都会直接返回给浏览器,浪费流量
服务器告知浏览器上次修改时间:Last-Modified
为了解决上面的问题,引入了Last-Modified(上次修改的时间,GMT标准格式)
- 在缓存时间(Expires)之后,浏览器再次发起请求,此时需要携带if-Modified-Since(等于上次请求服务端返回的Last-Modified)请求服务器
- 服务器比较if-Modified-Since与资源文件上次修改的时间,如果一致,则返回304(没有响应实体,因为浏览器已有该资源,无需重复发送)。如果不一致,服务器则从磁盘读取文件返回给浏览器,同时返回Last-Modified(最近修改的时间)和Expires(缓存过期时间)
缺点:
- Last-Modified只能精确到秒,如果同一时刻服务端的资源更新了,此时比较时间之后,仍判定为资源未修改
- Expires 过期控制不稳定,因为浏览器端可以随意修改时间,导致缓存使用不精准
- 如果服务器的资源文件被修改了,但是实际内容却并未改变
服务器告知浏览器增加相对时间的控制,Cache-Control
服务器除了告知浏览器过期时间,同时还告诉浏览器一个相对时间 Cache-Control:max-age=10秒。意思是在10秒以内,使用缓存到浏览器的资源。
浏览器先检查 Cache-Control,如果有,则以 Cache-Control 为准,忽略 Expires。如果没有 Cache-Control,则以 Expires 为准。
Cache-Control 对缓存的控制粒度更细,包括缓存代理服务器的缓存控制:
- public:资源允许被中间服务器缓存(浏览器请求服务器时,如果缓存时间没到,中间服务器直接返回给浏览器内容,而不必请求源服务器。)
- private:资源不允许被中间代理服务器缓存(浏览器请求服务器时,中间服务器都要把浏览器的请求透传给服务器。)
- no-cache:浏览器不做缓存检查(由服务器来告诉浏览器是否继续使用缓存-- 返回304,而不是浏览器自己判断)
- no-store:浏览器和中间代理服务器都不能缓存资源。
- must-revalidate:可以缓存,但是使用之前必须先向源服务器确认。
- proxy-revalidat:要求缓存服务器针对缓存资源向源服务器进行确认
为了兼容,一般会同时写Expires和Cache-Control
因为 Expires 是 HTTP 1.0 定义的字段,而且Expires需要服务器和浏览器的时钟严格同步。
而 Cache-Control 是 HTTP 1.1 的字段,万一客户端只支持 HTTP 1.0,
那么 Cache-Control 有可能就会不工作,所以一般为了兼容会都写上。
优先解析Cache-Control
增加文件内容对比,ETag
为了解决文件修改时间Last-Modified只能精确到秒带来的问题,增加了ETag响应头,当资源文件的内容变了,ETag才变,否则不变,可以理解为文件内容的id。同时对应的请求头If-Node-Match。
- 浏览器发起请求
- 服务器返回资源文件,Expires(缓存过期时间)+Cache-Control:max-age=10(相对时间)+Last-Modified(上次修改资源的时间)+ETag
- 在10s内浏览器再次请求资源时,不再请求服务器,直接使用本地缓存
- 10s之后,浏览器再次请求服务器,带上If-Modified-Since(上次修改资源的时间)+ If-None-Match(响应头ETag的值)
- 服务端发现有If-None-Match,则忽略If-Modified-Since的比较,去比较 If-None-Match 和资源文件的 Etag 值
- 如果没有变化,则服务器返回304
一致的缺点及解决方案
Expires和Cache-Control,他们都是控制缓存的过期时间,在缓存过期之前,浏览器无法得知服务器上的资源是否变化,只有当浏览器过期时,才会发起请求。
最终方案:不让HTML文件缓存,每次访问HTML都会去请求服务器,对于引用的资源比如a.js,加一个版本号,当内容更新时,再修改
<script src="http://test.com/a.js?version=0.0.1"></script>