HTTP缓存

1、HTTP缓存的作用

Web缓存是指存在于客户端和服务器之间的资源副本,资源可以是HTML,图片,js,css等等。如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过网站的网页,当再次访问这个URL地址的时候,如果网页没有更新,就不会再次下载网页,而是直接使用本地的缓存。

使用缓存可以一定程度上降低服务器端的压力,与此同时,也减少了网络带宽的消耗。当然最重要的是,也能提高用户体验,就是你的再次访问时间变小了。

2、缓存类型

1、服务器端Web应用缓存

分Web服务器本身,代理服务器和CDN缓存。

1、Web应用服务器缓存

Django提供了比较完善的缓存框架,它可以保存动态页面这样避免对于每次请求都重新计算,从而用于缓解服务器的压力。
此外,Django的缓存系统可以完美滴配合其他缓存类型,比如基于浏览器的缓存等等。 比如你在视图函数中设置cache_control,那么就可以指定在客户端本地缓存的资源。
Django的缓存可以保存在内存中,本地文件系统中,也可以保存在数据库中。各有各的优缺点,详细的可以看Django的官方文档。 》》》 Django缓存框架

这里面要提一下,在DJango官网里提到了一个Vary头,我在wireshark抓包的时候也看到了。它的作用就是定义了缓存机制在构建其缓存键时应考虑哪些请求头。在下面的图中会看到,响应报文有Vary:Cookie。

2、代理服务器缓存

代理服务器是浏览器和源服务器之间的中间服务器,浏览器先向这个中间服务器发起Web请求,经过处理后(比如权限验证,缓存匹配等),再将请求转发到源服务器。代理服务器缓存的运作原理跟浏览器的运作原理差不多,只是规模更大。可以把它理解为一个共享缓存,不只为一个用户服务,一般为大量用户提供服务,因此在减少相应时间和带宽使用方面很有效,同一个副本会被重用多次。常见代理服务器缓存解决方案有Squid等,这里不再详述。Nginx,Apache等均有缓存的作用。

Nginx缓存文章: Nginx缓存

3、 CDN缓存

CDN(Content delivery networks)缓存,也叫网关缓存、反向代理缓存。CDN缓存一般是由网站管理员自己部署,为了让他们的网站更容易扩展并获得更好的性能。浏览器先向CDN网关发起Web请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态将请求转发到合适的源服务器上。虽然这种架构负载均衡源服务器之间的缓存没法共享,但却拥有更好的处扩展性。从浏览器角度来看,整个CDN就是一个源服务器,从这个层面来说,本文讨论浏览器和服务器之间的缓存机制,在这种架构下同样适用。

4、浏览器端缓存

浏览器缓存根据一套与服务器约定的规则进行工作,在同一个会话过程中会检查一次并确定缓存的副本足够新。如果你浏览过程中,比如前进或后退,访问到同一个图片,这些图片可以从浏览器缓存中调出而即时显现。

3、浏览器缓存相关的头信息

If-Modified-Since & If-None-Match

If-Modified-Since,和 Last-Modified 一样都是用于记录页面最后修改时间的 HTTP 头信息,只是 Last-Modified 是由服务器往客户端发送的 HTTP 头,而 If-Modified-Since 则是由客户端往服务器发送的头,可以看到,再次请求本地存在的 cache 页面时,客户端会通过 If-Modified-Since 头将先前服务器端发过来的 Last-Modified 最后修改时间戳发送回去,这是为了让服务器端进行验证,通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回新的内容,如果是最新的,则 返回 304 告诉客户端其本地 cache 的页面是最新的,于是客户端就可以直接从本地加载页面了。

If-None-Match,它和ETags(HTTP协议规格说明定义ETag为“被请求变量的实体值”,或者是一个可以与Web资源关联的记号)常用来判断当前请求资源是否改变。类似于Last-Modified和HTTP-IF-MODIFIED-SINCE。但是有所不同的是Last-Modified和HTTP-IF-MODIFIED-SINCE只判断资源的最后修改时间,而ETags和If-None-Match可以是资源任何的任何属性,不如资源的MD5等。

ETags和If-None-Match的工作原理是在HTTP Response中添加ETags信息。当客户端再次请求该资源时,将在HTTP Request中加入If-None-Match信息(ETags的值)。如果服务器验证资源的ETags没有改变(该资源没有改变),将返回一个304状态;否则,服务器将返回200状态,并返回该资源和新的ETags。

ETag如何帮助提升性能? 聪明的服务器开发者会把ETags和GET请求的“If-None-Match”头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生ETag,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。

其过程如下:

  1. 客户端请求一个页面(A)。

  2. 服务器返回页面A,并在给A加上一个ETag。

  3. 客户端展现该页面,并将页面连同ETag一起缓存。

  4. 客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。

    这里面的If-None-Match值就是之前服务器发过来的ETAG值。

5.服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。

Last-Modified/ETag与Cache-Control/Expires比较

配置Last-Modified/ETag的情况下,浏览器再次访问统一URI的资源,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器会只发送一个304回给浏览器,告诉浏览器直接从自己本地的缓存取数据;如果修改过那就整个数据重新发给浏览器;

Cache-Control/Expires则不同,如果检测到本地的缓存还是有效的时间范围内,浏览器直接使用本地副本,不会发送任何请求(除了刷新操作)。两者一起使用时,Cache-Control/Expires的优先级要高于Last-Modified/ETag。即当本地副本根据Cache-Control/Expires发现还在有效期内时,则不会再次发送请求去服务器询问修改时间(Last-Modified)或实体标识(Etag)了。

上图只配置了Cache-Control,该头是我在Django的视图函数中设置的:

        @cache_control(public=True,max_age=3600)
        def dashboard(request):
            pass

一般情况下,使用Cache-Control/Expires会配合Last-Modified/ETag一起使用,因为即使服务器设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时Last-Modified/ETag将能够很好利用304,从而减少响应开销。

上图是重新输入URL后浏览。看HTTP响应,由于之前的响应都在有效期,因此大多数都是使用本地资源了,不再像服务器请求。然后再看刷新页面之后的响应情况。

上图是刷新页面之后,客户端又重新向服务器发一次请求,该页面的HTML文档就不是从本地获取的了,而是服务器又响应了一次。
再看其他的静态文件,是利用头信息的Last-Modified信息来验证是否修改,不修改就还可以使用本地资源。这点来讲,的确很有用。

上图是浏览器响应,里面有ETAG字段。此外,看所有的304的响应,Connection都为close,即关闭了当前的TCP连接。

哪些请求不能被缓存?

无法被浏览器缓存的请求:

HTTP信息头中包含Cache-Control:no-cache,pragma:no-cache,或Cache-Control:max-age=0等告诉浏览器不用缓存的请求
需要根据Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
经过HTTPS安全加密的请求(有人也经过测试发现,ie其实在头部加入Cache-Control:max-age信息,firefox在头部加入Cache-Control:Public之后,能够对HTTPS的资源进行缓存,参考《HTTPS的七个误解》)
POST请求无法被缓存
HTTP响应头中不包含Last-Modified/Etag,也不包含Cache-Control/Expires的请求无法被缓存

RFC2616中很多头部信息,真的很难都万丈烂熟于心,只能在开发,实践中不停地去应用。

--------EOF---------
本文微信分享/扫码阅读