2

之前对http的缓存知识一知半解,只能说出个大概。和同事交流这块内容时稍一深入探讨就捉襟见肘,自惭形秽。故痛定思痛,花了一两天时间去研究了下这块内容,写下这篇笔记方便以后的查询与修正。

首先介绍下和缓存相关的http头字段参数。

http的头字段包括通用头字段(general-header)、请求头字段(request-header)、响应头字段(response-header)以及实体头字段(entity-header)。

我个人对实体头字段这个说法有点陌生,所以查阅了相关资料:根据RFC-2616(7.1 Entity Header Fields)中的解释是:实体头字段(entity header)定义了实体主体(entity-body)的元信息,如果实体主体(entity-body)不存在,则表示http请求所标识的资源的元信息。

Entity-header fields define metainformation about the entity-body or,
if no body is present, about the resource identified by the request.

general header


Cache-Control: 整个请求响应链的缓存机制必须遵循的特别指令。这个指令是单向的,因为请求中该指令的效果和响应中的效果并不一样。其值包括max-age,no-cache等。

The Cache-Control general-header field is used to specify directives that MUST be obeyed by all caching mechanisms along the request/response chain.

Cache directives are unidirectional in that the presence of a directive in a request does not imply that the same directive is to be given in the response.

Date: 请求或响应的时间和日期。(注:这里的日期格式使用的是GMT格式,即格林威治时间,要换算成北京时间的话要加上8小时)

Pragma:可被应用到整个请求响应链中的自定义设置,例如:Pragma: no-cache。

The Pragma general-header field is used to include implementation-specific directives that might apply to any recipient along the request/response chain.

request header


If-Modified-Since: 如果从该参数指定的时间开始,请求并没有被修改的话,服务器会返回304(not modified)状态码,而不是一个实体。

if the requested variant has not been modified since the time specified in this field, an entity will not be returned from the server; instead, a 304 (not modified) response will be returned without any message-body.

If-Unmodified-Since: 如果从该参数指定的时间开始,请求并没有被修改的话,服务器应该执行请求的操作。如果被修改过的话,服务器不执行操作病返回412(Precondition Failed)状态码

If the requested resource has not been modified since the time specified in this field, the server SHOULD perform the requested operation as if the If-Unmodified-Since header were not present.

If the requested variant has been modified since the specified time, the server MUST NOT perform the requested operation, and MUST return a 412 (Precondition Failed).

If-None-Match: 允许在对应的内容未被修改的情况下返回304未修改( 304 Not Modified ),判断依据为客户端该参数存储的Etag值与服务器上存储的Etag是否相匹配。(原文太长,暂时拿wiki百科来填坑)

If-Match: 仅当客户端提供的实体与服务器上对应的实体相匹配时,才进行对应的操作。主要用作像 PUT 这样的方法中,仅当从用户上次更新某个资源以来,该资源未被修改的情况下,才更新该资源。如果不匹配,则返回412(Precondition Failed)状态码,该参数也会被忽略。(原文太长,暂时拿wiki百科来填坑)

response header


ETag: 指定请求变量的当前实体Tag。该字段用来比较来自同一资源的变化的实体。(只是直译,后面会有详细解释和用法)

The ETag response-header field provides the current value of the entity tag for the requested variant.

Entity Header


Expires: 指定过期时间。

The Expires entity-header field gives the date/time after which the response is considered stale.

Last-Modified: 指定了服务器认为资源最近一次修改的时间。

The Last-Modified entity-header field indicates the date and time at which the origin server believes the variant was last modified.

字段参数可能会有翻译或者理解错误,请各位大大指出。

了解了参数的含义后,接下来就是看具体的缓存机制了。

HTTP 1.0时代 (基于Pragma&Expires的缓存实现)

Pragma在RFC1945文档中只提供了“no-cache”值,当"no-cache"出现在请求消息中时,应用程序应当向原始服务器推送此请求,即使它已经在上次请求时已经缓存了一份拷贝。这样将保证客户端能接收到最权威的回应。它也用来在客户端发现其缓存中拷贝不可用或过期时,对拷贝进行强制刷新。
(HTTP1.1会把请求中的pragma="no-cache"视为发送了Cache-Control: no-cache)

有Pragma用来禁用缓存的话,那就有Expires来设置缓存。
就如上文介绍所说,Expires是用来指定过期时间的,时间格式为GMT,如果客户端时间没有超过该时间点的话,则不发送请求。但是值得注意的是,Expires所定义的缓存时间是相对服务器上的时间而言的,如果客户端的时间和服务器的时间不一致的话(例如用户修改了系统时间),那么这个资源失效时间也没有意义了。

HTTP 1.1时代

http1.1新增了 Cache-Control 来定义缓存过期时间,若报文中同时出现了Expires 和 Cache-Control,会以 Cache-Control 为准。
如上文介绍所说,Cache-Control是通用头部参数,格式为


"Cache-Control" ":" cache-directive

作为请求头部时,cache-directive的可选值为:(图片来自网络)

作为响应头部时,cache-directive的可选值为:(图片来自网络)

cache-control设置缓存的使用方法为:Cache-Control:max-age=31536000,缓存单位为秒。

如果客户端请求的资源未超过缓存时间,则直接取本地缓存,状态码为200(from memory cache),
但是如果超过缓存时间或者直接不走缓存的话,那我们就需要和其他的字段配合来校验缓存了。

1. Last-Modified

服务端传递资源给客户端的时候会在响应报文中带上这个信息,而客户端接受到资源后会将该信息的值设置为请求头中If-Modified-Since参数的值(If-Modified-Since: Last-Modified value)。在下次发送请求的时候(max-age过期之后),会将请求头中的If-Modified-Since参数的值与服务端Last-Modified的值去比较,如果仍是一致的,那么说明该资源未被修改,直接返回304状态码即可,如果不一致,则返回状态码200,以及新的Last-Modified。

但是Last-Modified会有不准确的时候,比如服务端的资源做了无实际变化的修改(加一个空格再删掉),这样也会使服务端Last-Modified的时间更新,导致客户端请求中If-Modified-Since的值与其无法匹配,就会返回整个实体。(即使返回的实体内容与客户端的缓存内容没有区别)

2. Etag

Etag是一种比Last-Modified更为精确的校验方式。服务器会通过某种算法,给资源计算得出一个唯一标志符。其判断依据和Last-Modified类似,只是服务端在传递资源给客户端的相应报文中带上Etag后,客户端会将其设置为请求头中If-None-Match参数的值(If-None-Match: Etag-value),然后在传递资源时进行比较。

如果同时使用Last-Modified和Etag进行验证的话,那么两者中有一个通过校验(即传递的值可以匹配),则可以认为资源并没有被修改。

基本的缓存机制就是由这些参数形成的。

最后我们来看下,不同的页面打开方式对缓存机制的影响

主要有以下两点要注意:

1.手动刷新页面(F5刷新),浏览器会直接认为缓存已经过期,即使缓存并没有过期,在请求中加上字段:Cache-Control:max-age=0,发包向服务器查询是否有文件是否有更新。

2.强制刷新页面(ctrl+F5刷新),浏览器会直接忽略本地缓存内容,即使本地有缓存可用,在请求中加上字段:Cache-Control:no-cache(或 Pragma:no-cache),发包向服务器重新请求文件。
其他的直接上图(图片来自网络):

此篇文章的很多想法是借鉴网上高手的文章,我会在底部标明参考出处。如果此文章中有什么问题的话,烦请一定要指出,谢谢!

参考资料
RFC2616
H5 缓存机制浅析 移动端 Web 加载性能优化
浅谈浏览器http的缓存机制


magichuang
101 声望8 粉丝