写点什么

缓存在哪 -HTTP 缓存原理介绍

用户头像
hasWhere
关注
发布于: 4 小时前


上图中有三个角色,浏览器、Web 代理和服务器,如图所示 Http 缓存存在于浏览器和 Web 代理中。当然在服务器内部,也存在着各种缓存,但这已经不是本文要讨论的 Http 缓存了。所谓的 Http 缓存控制,就是一种约定,通过设置不同的响应头 Cache-Control 来控制浏览器和 Web 代理对缓存的使用策略,通过设置请求头 If-None-Match 和响应头 ETag,来对缓存的有效性进行验证。


非 HTTP 协议定义的缓存机制


浏览器缓存机制,其实主要就是 HTTP 协议定义的缓存机制(如: Expires; Cache-control 等)。但是也有非 HTTP 协议定义的缓存机制,如使用 HTML Meta 标签,Web 开发者可以在 HTML 页面的<head>节点中加入<meta>标签,代码如下:


<META HTTP-EQUIV="Pragma" CONTENT="no-cache">


上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。使用上很简单,但只有部分浏览器可以支持,而且所有缓存代理服务器都不支持,因为代理不解析 HTML 内容本身。下面主要介绍 HTTP 协议定义的缓存机制。


大话浏览器缓存


浏览器缓存一直是一个让人又爱又恨的存在,一方面极大地提升了用户体验,而另一方面有时会因为读取了缓存而展示了“错误”的东西,而在开发过程中千方百计地想把缓存禁掉。如果没听说过浏览器缓存或者不知道浏览器缓存的用处,可以先浏览一下这篇文章->Web 缓存的作用与类型 。


那么浏览器缓存机制到底是如何工作的呢?核心就是把缓存的内容保存在了本地,而不用每次都向服务端发送相同的请求,设想下每次都打开相同的页面,而在第一次打开的同时,将下载的 js、css、图片等“保存”在了本地,而之后的请求每次都在本地读取,效率是不是高了很多?真正的浏览器工作的时候并不是将完整的内容保存在本地,各种浏览器都有不同的方式,譬如 firefox 是一种类似 innodb 的方式存储的 key value 的模式,在地址栏中输入 about:cache 可以看见缓存的文件,chrome 会把缓存的文件保存在一个叫 User Data 的文件夹下。但是如果每次都读取缓存也会存在一定的问题,如果服务端的文件更新了呢?这时服务端就会和客户端约定一个有效期,譬如说服务端告诉客户端 1 天内我服务端的文件不会更新,你就放心地读取缓存吧,于是在这一天里每次遇到相同的请求客户端都开心地可以读取缓存里的文件。但是如果一天过去了,客户端又要读取该文件了,发现和服务端约定的有效期过了,于是就会向服务端发送请求,试图下载一个新的文件,但是很有可能服务端的文件其实并没有更新,其实还是可以读取缓存的。这时该怎么判断服务端的文件有没有更新呢?有两种方式,第一种在上一次服务端告诉客户端约定的有效期的同时,告诉客户端该文件最后修改的时间,当再次试图从服务端下载该文件的时候,check 下该文件有没有更新(对比最后修改时间),如果没有,则读取缓存;第二种方式是在上一次服务端告诉客户端约定有效期的同时,同时告诉客户端该文件的版本号,当服务端文件更新的时候,改变版本号,再次发送请求的时候 check 一下版本号是否一致就行了,如一致,则可直接读取缓存。


而事实上真正的浏览器缓存机制大抵也是如此,接下来就可以分别对号入座了。


需要注意的是,浏览器会在第一次请求完服务器后得到响应,我们可以在服务器中设置这些响应,从而达到在以后的请求中尽量减少甚至不从服务器获取资源的目的。浏览器是依靠请求和响应中的的头信息来控制缓存的。


Expires 与 Cache-Control


Expires 和 Cache-Control 就是服务端用来约定和客户端的有效时间的。



比如如上一个响应头,Expires 规定了缓存失效时间(Date 为当前时间),而 Cache-Control 的 max-age 规定了缓存有效时间(2552s),理论上这两个值计算出的有效时间应该是相同的(上图好像不一致)。Expires 是 HTTP1.0 的东西,而 Cache-Control 是 HTTP1.1 的,规定如果 max-age 和 Expires 同时存在,前者优先级高于后者。Cache-Control 的参数可以设置很多值,譬如(参考浏览器缓存机制):



Last-Modified/If-Modified-Since


而 Last-Modified/If-Modified-Since 就是上面说的当有效期过后,check 服务端文件是否更新的第一种方式,要配合 Cache-Control 使用。比如第一次访问我的主页 simplify the life,会请求一个 jquery 文件,响应头返回如下信息:



然后我在主页按下 ctrl+r 刷新,因为 ctrl+r 会默认跳过 max-age 和 Expires 的检验直接去向服务器发送请求(下文再探讨各种刷新后如何读取缓存),我们看看请求截图:



请求头中包含了 If-Modified-Since 项,而它的值和上次请求响应头中的 Last-Modified 一致,我们发现这个日期是在遥远的 2013 年,也就是说这个 jquery 文件自从 2013 年的那个日期后就没有再被修改过了。将 If-Modified-Since 的日期和服务端该文件的最后修改日期对比,如果相同,则响应 HTTP304,从缓存读数据;如果不相同文件更新了,HTTP200,返回数据,同时通过响应头更新 last-Modified 的值(以备下次对比)。


ETag/If-None-Match


而 ETag/If-None-Match 则是上文大话中说的第二种 check 服务端文件是否更新的方式,也要配合 Cache-Control 使用。实际上 ETag 并不是文件的版本号,而是一串可以代表该文件唯一的字符串(Apache 中,ETag 的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行 Hash 后得到的。),当客户端发现和服务器约定的直接读取缓存的时间过了,就在请求中发送 If-None-Match 选项,值即为上次请求后响应头的 ETag 值,该值在服务端和服务端代表该文件唯一的字符串对比(如果服务端该文件改变了,该值就会变),如果相同,则相应 HTTP304,客户端直接读取缓存,如果不相同,HTTP200,下载正确的数据,更新 ETag 值。



看如上截图,与服务器约定的直接读取本地缓存的时间过了,就会向服务器发送新的请求,请求头中带 If-None-Match 项,该字符串值会在服务端进行匹配,很显然,并没有什么变化(看响应头的 ETag 值),于是响应 HTTP304,直接读取缓存。或许你会发送该请求也有 If-Modified-Since 项,如果两者同时存在,If-None-Match 优先,忽略 If-Modified-Since。或许你会问为什么它优先?两者功能相似甚至相同,为什么要同时存在?HTTP1.1 中 ETag 的出现主要是为了解决几个 Last-Modified 比较难解决的问题:


  1. Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

  2. 如果某些文件会被定期生成,但有时内容并没有任何变化(仅仅改变了时间),但 Last-Modified 却改变了,导致文件没法使用缓存

  3. 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形


不能缓存的请求


当然并不是所有请求都能被缓存。


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


  1. HTTP 信息头中包含 Cache-Control:no-cache,pragma:no-cache(HTTP1.0),或 Cache-Control:max-age=0 等告诉浏览器不用缓存的请求

  2. 需要根据 Cookie,认证信息等决定输入内容的动态请求是不能被缓存的

  3. 经过 HTTPS 安全加密的请求(有人也经过测试发现,ie 其实在头部加入 Cache-Control:max-age 信息,firefox 在头部加入 Cache-Control:Public 之后,能够对 HTTPS 的资源进行缓存,参考《HTTPS 的七个误解》)

  4. POST 请求无法被缓存

  5. HTTP 响应头中不包含 Last-Modified/Etag,也不包含 Cache-Control/Expires 的请求无法被缓存


用户行为与缓存


浏览器缓存过程还和用户行为有关,譬如上面提到的,打开我的主页 simplify the life,有个 jquery 的请求,如果直接在地址栏按回车,响应 HTTP200(from cache),因为有效期还没过直接读取的缓存;如果 ctrl+r 进行刷新,则会相应 HTTP304(Not Modified),虽然还是读取的本地缓存,但是多了一次服务端的请求;而如果是 ctrl+shift+r 强刷,则会直接从服务器下载新的文件,响应 HTTP200。



通过上表我们可以看到,当用户在按 F5 进行刷新的时候,会忽略 Expires/Cache-Control 的设置,会再次发送请求去服务器请求,而 Last-Modified/Etag 还是有效的,服务器会根据情况判断返回 304 还是 200;而当用户使用 Ctrl+F5 进行强制刷新的时候,只是所有的缓存机制都将失效,重新从服务器拉去资源。


总结


盗图浏览器缓存机制,两张图很清晰



参考

  1. 浏览器缓存机制浅析

    HTTP 缓存原理介绍

发布于: 4 小时前阅读数: 4
用户头像

hasWhere

关注

间歇性努力的学习渣 2018.04.20 加入

通过博客来提高下对自己的要求

评论

发布
暂无评论
缓存在哪-HTTP缓存原理介绍