HTTP缓存

504 阅读6分钟

缓存相关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>

参考