HTTP协议-缓存

  HTTP 协议中,缓存更多关心的文档资源的再利用。其目的是减少数据传输,加快相应速度等等。而对于缓存采用的是什么方案,也就是存在内存中还是硬盘中之类的问题,就属于另外的内容了。

  假设,我身在广东,但是我访问的服务器在北京,在服务器其他条件一致的时候,我访问北京的服务器所需要的速度,肯定没有访问广东的服务器快。此时如果先访问靠近我的服务器,先看看我需要的资源有没有相应的副本,有就直接返回;没有就到源服务器中请求。这样响应速度肯定会快很多,而且也减轻了源服务器的压力。当然这里还有‘缓存命中率’和‘字节命中率’之类的概念,关于这些概念可以通过阅读《HTTP权威指南》这本书,当然这本被誉为‘HTTP开发者的圣经’的书非常的厚,当我第一次拿到这本书的时候,其实内心也是崩溃的。

  下面的内容其实更像是读书笔记,外加一下个人理解、总结。

  缓存处理的步骤:

  1.接收——从网络中接收相应的请求报文

  2.解析——对请求报文进行解析,读取其请求的URL和其他首部

  3.查询——查询请求的资源本地是否有相应的‘新鲜’副本可用,如果用,就直接使用,如果没有,就去源服务器请求一份,并将其保存至本地。

  4.新鲜度检测——检查缓存的副本是否足够‘新鲜’,如果不是,就去源服务器确认是否有任何的更新

  5.创建响应——使用缓存的副本或者是从源服务器得到的新内容,加上相应的首部,构建一条响应报文

  7.发送——将响应报文发回给客户端

  8.日志——可选地创建一条日志来记录这次事务

 

  这个处理步骤针对的是缓存服务器,当然这个缓存服务器可以是服务商架设的,也可以是客户端浏览器自带的。现代的浏览器一般都会自带缓存,可以在网景系的浏览器可以在 URL 栏中输入:about:cache ,来查看本地缓存的使用情况。这里引入一个公有缓存私有缓存的概念,可以通过首部:Cache-Control 的值来规定:

  Cache-Control: public (可以向任意方提供响应的缓存,也就是公有缓存)

  Cache-Control: private (仅向特定用户返回响应,也就是私有缓存)

  但这里,我个人有一个问题:是不是私有缓存就存在浏览器缓存中,而公有缓存就存在缓存服务器中呢?很明显这个要求貌似是合理的,但是具体是否就是这样,《HTTP权威指南》中并没有明说,而在另一本书《图解HTTP》中,提及了缓存服务器可以缓存私有缓存,但只能为特定的用户提供。

  而浏览器又会缓存公有缓存,所以浏览器的缓存更像是一个私有的缓存服务器,只不过这个缓存服务器是私有的,所以是公有还是私有缓存意义就不大了。

  所以这里的公有和私有只针对响应报文本身吧。

 

  接下来是就按照处理步骤走一遍了,这里我们先假设缓存服务器中没有缓存任何的内容先,此时我的客户端向缓存服务器发送一个请求报文,因为此时缓存服务器什么都没有,所以肯定会去源服务器中请求资源,此时源服务器构建了一个响应报文,发给了缓存服务器,缓存服务器读取了相应的首部,然后进行缓存策略的调整。

  在这个过程中又引入了几个首部:

  Date:表示报文创建的时间,当然使用的时间是服务器的时间,而且一般是 GMT 时间,当然格式有很多种,在 HTTP/1.1 中使用 RFC1123 中规定的格式。这是一个通用首部,服务器和客户端都可以使用,但服务器一般要给出这个首部,因为这个首部将用来计算缓存的新鲜度。对客户端而言,这个首部是可选的,当然有会更好一些。

  下面是我打开某个网站时,其中一个响应报文的首部:

  当然,中国在东八区,所以换算成中国时间应该是,3月21日 13:15:22 。但是,当我打开这个网页的时候,已经是6月21日 15:40 左右了,也就是说这个报文是三个月前创建的,很明显,这个报文被缓存了,而且在我请求的时候,缓存服务器还判断其没有过期,还是‘新鲜的’。

  注意:一般要求代理服务器不能改写 Date 首部。


 

  Age:表示源服务器在多久之前创建了响应,单位为秒,例如上面例子中报文中:

  8043650/60/60/24/30 = 3.103....

  但是,如果创建这个响应报文的是缓存服务器,Age 指的是缓存后的响应报文再次认证,到认证完成的时间。


 

  Last-Modified:该首部试图提供这个实体最后一次被修改的相关信息。对于静态资源,例如文件、图片等,表示的是最后修改的时间;对于那些动态创建的资源,如使用脚本创建的资源,这个值可能就是创建响应的时间。

  注意:这个时间不能是未来时间,如果它比 Date 首部还要迟,HTTP 服务器就会将该时间重置。

  因为我请求的是,这是一个静态文件,所以这里的值应该是这个文件最后修改的时间。


 

  Cache-Control: max-age

  Cache-Control 这个首部主要是实现缓存控制的,它有许多的属性,我们在前面已经学习过表示私有缓存和公有缓存的属性了。这里的 max-age 属性表示资源过期的相对时间——从第一次生成文档到文档不再新鲜、无法使用为止,最大的合法生存时间,单位是秒。

  例如上面示例中的相应报文中:

  315360000/60/60/24/365 = 10 ,也就是说该资源的最大有效时间是 10 年。


 

  Cache-Control: s-maxage

  其行为和 max-age 类似,但仅适用于公有缓存。


 

  Expires:指定一个绝对过期时间,如果过了过期时间,说明文档不再‘新鲜’。其实其和 max-age 做的事是一样的,只不过 max-age 是 HTTP/1.1 中的,而 Expires 是 1.0+ 版本中的。一般而言更趋向使用max-age,因为服务器之间的时间很大可能会出现偏差。假如你希望一个文档明天就过期,而缓存服务器的时间比源服务器晚一天,那么到期时间就会变成后天,也就是说 Expires 参照的是缓存服务器的时间,而max-age 则表示在从服务器将文档传来之时起,可以认为其是新鲜的秒数,所以即使缓存服务器的时间不准也没有关系

  上面示例中的相应报文中:

  可能其和 max-age 的过期时间不同,当两者同时出现的时候,如果缓存服务器是 HTTP/1.1 版本的,则 max-age 的优先级更高,同时会忽略 Expires,而 1.0+ 就会执行 Expires 而无视 max-age ,一般源服务器为了兼容性,都会同时发生这两个首部。


 

  知道了以上首部之后,就可以知道了解到下面的内容了:在3个月前的某一天,缓存服务器还是空的,可能那个时候它还是新部署的,又或者是原来的缓存被删除了之类的。此时有一个客户端请求这个资源,缓存服务器一看,自己没有这个资源,就去源服务器请求,源服务器生成了这个响应,当时的时间是 Date 首部显示的时间。然后通过 Last-Modified 说明这个文档的修改相关信息,然后用 max-age、x-maxage、Expires 控制有效期。

  缓存服务器收到报文后,按照首部的要求缓存下来,下次有请求相同资源的,在有效期内就可以直接用缓存的资源响应了。

  那么客户端,也就是我们的浏览器是怎么样利用缓存的呢?

  首先,我们的浏览器并不知道其是和缓存服务器通信,还是和源服务器通信。最多就是通过 Via 首部得知请求经过了几层代理。而且我们浏览器也不在意是否和源服务器通信, 只有获得相应报文就行了。也就是说,当我们打开一个新的网页,或者强制刷新一个网页,其得到的都是 200 ok 响应。

  以上就是我用火狐浏览器强制刷新我的博客首页的所有请求,可以看到所有都是 200 的状态码。也就是说第一次请求的时候,都是从服务器中获取的资源。这个服务器可能是缓存服务器。

  当我第二次刷新这个页面的时候:

  却发现部分 304 的响应状态码。304 Not Modified 代表资源没改变,可以继续使用副本。

  我们看看第一次请求的时候,背景图片的响应报文是什么吧:

  其中 Last-Modified 已经解释过了,Etag 首部代表的是文件的描述符,这个东西有点类似文件的 MD5 值,每当文件改变后,其 MD5 值也会改变。所以这个标识符是用来说明文件是否有改变,当然其是否就是文件的 MD5 值我并没有研究过。总之用来表明文件是否有修改过,当然还有弱标识符的用法,在值前面加上 w/ 表示弱标识符,更多详细信息,请参考书本。

  那么,当我们普通刷新页面的时候,其请求报文是这样的:

  我们可以发现这里有两个首部:If-Modified-Since,If-None-Match,这两个是条件请求首部,表示如果其里面的条件为真,就执行相应的方法。其表示,如果当前请求的资源在指定的日期后发生了改变,条件就为真,就执行相应的 GET 之类的方法。

  仔细观察,可以发现 If-Modified-Since 和 Last-Modified 的日期就一样的。这里的意思是:在上次的响应之后,文件最后修改时间发生了变化,即文件发生了改变,就去源服务器请求新的资源。

  If-None-Match 则表示标识符和上次不符合的话,条件就为真,就请求新的资源。

  当服务器发送一个实体标签的时候,HTTP/1.1 客户端就必须使用实体验证,如果只发送 Last-Modified ,就可以使用 If-Modified-Since 验证。两者同时发送的时候,就使用两个验证,当两个验证成功(也就是两种方法判断,文件都没有修改),服务器才回送 304 响应。

  这就是新鲜度验证了,当然这里的例子说明的是浏览器缓存和服务器之间的验证,至于缓存服务器和源服务器的验证,我们是看不见的,不过原理也是差不多的。

  验证成功后返回 304 ,通常只有一个首部,比重发完整的资源节省许多;验证失败就返回 200 ,并将新资源发来,附带新的过期时间。


 

控制缓存的首部

  其实就是 Cache-Control 的各种属性:

1.Cache-Control: no-store

  服务器:当服务器发送此首部时,意味着该响应报文禁止缓存,缓存服务器会向客户端转发一条 no-store ,然后删除对象。

  客户端:当客户端发送此首部时,要求不要缓存请求报文。或者删除已缓存的资源。(当然缓存服务器是否接受该指令要看其配置如何)

  该指令暗示请求或响应报文中含有敏感或机密信息,缓存服务器不能在本地储存请求或响应的任一部分。

 

2.Cache-Control: no-cache

  服务器:当服务器发生此首部时,要求缓存服务器可以缓存此报文,但是在未经新鲜度确认之前,不能提供此缓存。如果指定了参数,例如:no-cache=Location,则表示客户端在接收到这个被指定的参数值的首部字段,例如:Location: www.xxx.com 时,就不能进行缓存,其他没有的就可以进行缓存,只能在响应报文中指定这个参数。

  客户端:当客户端发送此首部时,表示除非资源进行了再验证,否则这个客户端将不会接受已缓存的资源。这个再验证应该是收到该首部后立即进行的,其目的是为了防止返回过期的资源。

Pragma: no-cache

  是为了兼容 1.0+ 版本的首部,意思一样,有时为了兼容性会同时发生这两个首部。

 

3.Cache-Control: max-age

  服务器:控制缓存的最大有效期

  客户端:不接受已经缓存了多少秒的资源,如:max-age=600 表示不接受缓存超过1小时的资源。

4.Cache-Control: max-stale

  客户端:当没有值时,缓存服务器可以提供任意过期的资源,如果有了指定的值,例如:max-stale=600 ,表示可以接受已经过期1小时的资源。

 

5.Cache-Control: min-fresh

  客户端:必要要有指定值,表示如果在未来的多少时间内,资源仍然是新鲜的,则可以提供。例如:min-fresh=600,表示在未来的1小时之内依然没有过期的话,则提供给客户端。

 

6.Cache-Control: must-revalidate

  服务器:在没有跟原始服务器再验证的时候,不能提供此缓存的资源。缓存服务器依然可以对资源进行缓存,但是如果在进行验证的时候,源服务器不可用,则缓存服务器会返回一条 504 Gateway Timeout 错误(个人感觉和 no-cache 在功能上差别不大。)

 

7.Cache-Control: only-if-cached

  客户端:只有当缓存服务中有副本存在时,客户端才会获取一份副本。

 


 

关于 HTTP 中的缓存就先总结到这里,以后有什么错误的或者需要补充的再继续。

 

 

 

 

  

posted @ 2016-06-22 20:45  scolia  阅读(560)  评论(0编辑  收藏  举报