网站域名修改,旅游网站的制作,模板网点地址信息获取错误是什么意思,网站导航栏修改字体大小针对浏览器的http缓存的分析也算是老生常谈了#xff0c;每隔一段时间就会冒出一篇不错的文章#xff0c;其原理也是各大公司面试时几乎必考的问题。 之所以还写一篇这样的文章#xff0c;是因为近期都在搞新技术#xff0c;想“回归”下基础#xff0c;也希望尽量总结的更… 针对浏览器的http缓存的分析也算是老生常谈了每隔一段时间就会冒出一篇不错的文章其原理也是各大公司面试时几乎必考的问题。 之所以还写一篇这样的文章是因为近期都在搞新技术想“回归”下基础也希望尽量总结的更详尽些。 那么你是否还需要阅读本篇文章呢可以试着回答下面这个问题 我们在访问百度首页的时候会发现不管怎么刷新页面静态资源基本都是返回 200from cache 随便点开一个静态资源是酱的 哎哟有Response报头数据呢看来服务器也正常返回了etag什么鬼的应有尽有那状态200不是应该对应的非缓存状态么要from cache的话不是应该返回304才合理么 难道是度娘的服务器故障了吗 如果你知道答案那就可以忽略本文了。 http报文中与缓存相关的首部字段 我们先来瞅一眼RFC2616规定的47种http报文首部字段中与缓存相关的字段事先了解一下能让咱在心里有个底 1、通用首部字段就是请求报文和响应报文都能用上的字段 2、请求首部字段 3、响应首部字段 4、实体首部字段 后续大体也会依次介绍它们。 场景模拟 为方便模拟各种缓存效果我们建个非常简单的场景。 1、页面文件 我们建个非常简单的html页面上面只有一个本地样式文件和图片 !DOCTYPE html html head title缓存测试/title link relstylesheet hrefcss/reset.css /head body h1哥只是一个标题/h1 pimg srcimg/dog.jpg //p /body /html 2、首部字段修改 有时候一些浏览器会自行给请求首部加上一些字段如chrome使用F5会强制加上“cache-control:max-age0”,会覆盖掉一些字段比如pragma的功能另外有时候我们希望服务器能多/少返回一些响应字段。 这种情况我们就希望可以手动来修改请求或响应报文上的内容了。那么如何实现呢这里我们使用Fiddler来完成任务。 在Fiddler中我们可以通过“bpu XXX”指令来拦截指定请求然后手动修改请求内容再发给服务器、修改响应内容再发给客户端。 以我们的example为例页面文件走nginx通过 http://localhost/ 可直接访问所以我们直接执行“bpu localhost”拦截所有地址中带有该字样的请求 点击被拦截的请求可以在右栏直接修改报文内容上半区域是请求报文下半区域是响应报文点击黄色的“Break on Response”按钮可以执行下一步把请求发给服务器点击红色的按钮“Run to Completion”可以直接完成整个请求过程 通过这个方法我们可以很轻松地模拟出各种http缓存场景。 3、浏览器的强制策略 如上述当下大多数浏览器在点击刷新按钮或按F5时会自行加上“Cache-Control:max-age0”请求字段所以我们先约定成俗——后文提及的“刷新”多指的是选中url地址栏并按回车键这样不会被强行加上Cache-Control。 事实上有的浏览器还有一些更奇怪的行为在后续我们回答文章开头问题的时候会提到。 石器时代的缓存方式 在 http1.0 时代给客户端设定缓存方式可通过两个字段——“Pragma”和“Expires”来规范。虽然这两个字段早可抛弃但为了做http协议的向下兼容你还是可以看到很多网站依旧会带上这两个字段。 1、Pragma 当该字段值为“no-cache”的时候事实上现在RFC中也仅标明该可选值会知会客户端不要对该资源读缓存即每次都得向服务器发一次请求才行。 Pragma属于通用首部字段在客户端上使用时常规要求我们往html上加上这段meta元标签而且可能还得做些hack放到body后面去 meta http-equivPragma contentno-cache 它告诉浏览器每次请求页面时都不要读缓存都得往服务器发一次请求才行。 BUT!!! 事实上这种禁用缓存的形式用处很有限 1、仅有IE才能识别这段meta标签含义其它主流浏览器仅能识别“Cache-Control: no-store”的meta标签见出处。 2、在IE中识别到该meta标签含义并不一定会在请求字段加上Pragma但的确会让当前页面每次都发新请求仅限页面页面上的资源则不受影响。 做了测试后发现也的确如此这种客户端定义Pragma的形式基本没起到多少作用。 不过如果是在响应报文上加上该字段就不一样了 如上图红框部分是再次刷新页面时生成的请求这说明禁用缓存生效预计浏览器在收到服务器的Pragma字段后会对资源进行标记禁用其缓存行为进而后续每次刷新页面均能重新发出请求而不走缓存。 2、Expires 有了Pragma来禁用缓存自然也需要有个东西来启用缓存和定义缓存时间对http1.0而言Expires就是做这件事的首部字段。 Expires的值对应一个GMT格林尼治时间比如“Mon, 22 Jul 2002 11:12:01 GMT”来告诉浏览器资源缓存过期时间如果还没过该时间点则不发请求。 在客户端我们同样可以使用meta标签来知会IE也仅有IE能识别页面同样也只对页面有效对页面上的资源无效缓存时间 meta http-equivexpires contentmon, 18 apr 2016 14:30:00 GMT 如果希望在IE下页面不走缓存希望每次刷新页面都能发新请求那么可以把“content”里的值写为“-1”或“0”。 注意的是该方式仅仅作为知会IE缓存时间的标记你并不能在请求或响应报文中找到Expires字段。 如果是在服务端报头返回Expires字段则在任何浏览器中都能正确设置资源缓存的时间 在上图里缓存时间设置为一个已过期的时间点见红框则刷新页面将重新发送请求见蓝框。 那么如果Pragma和Expires一起上阵的话听谁的我们试一试就知道了 我们通过Pragma禁用缓存又给Expires定义一个还未到期的时间红框刷新页面时发现均发起了新请求蓝框这意味着Pragma字段的优先级会更高。 BUT响应报文中Expires所定义的缓存时间是相对服务器上的时间而言的如果客户端上的时间跟服务器上的时间不一致特别是用户修改了自己电脑的系统时间那缓存时间可能就没啥意义了。 Cache-Control 针对上述的“Expires时间是相对服务器而言无法保证和客户端时间统一”的问题http1.1新增了 Cache-Control 来定义缓存过期时间若报文中同时出现了 Pragma、Expires 和 Cache-Control会以 Cache-Control 为准。 Cache-Control也是一个通用首部字段这意味着它能分别在请求报文和响应报文中使用。在RFC中规范了 Cache-Control 的格式为 Cache-Control : cache-directive 作为请求首部时cache-directive 的可选值有 作为响应首部时cache-directive 的可选值有 我们依旧可以在HTML页面加上meta标签来给请求报头加上 Cache-Control 字段 另外 Cache-Control 允许自由组合可选值例如 Cache-Control: max-age3600, must-revalidate 它意味着该资源是从原服务器上取得的且其缓存新鲜度的有效时间为一小时在后续一小时内用户重新访问该资源则无须发送请求。 当然这种组合的方式也会有些限制比如 no-cache 就不能和 max-age、min-fresh、max-stale 一起搭配使用。 组合的形式还能做一些浏览器行为不一致的兼容处理。例如在IE我们可以使用 no-cache 来防止点击“后退”按钮时页面资源从缓存加载但在 Firefox 中需要使用 no-store 才能防止历史回退时浏览器不从缓存中去读取数据故我们在响应报头加上如下组合值即可做兼容处理 Cache-Control: no-cache, no-store 缓存校验字段 上述的首部字段均能让客户端决定是否向服务器发送请求比如设置的缓存时间未过期那么自然直接从本地缓存取数据即可在chrome下表现为200 from cache若缓存时间过期了或资源不该直接走缓存则会发请求到服务器去。 我们现在要说的问题是如果客户端向服务器发了请求那么是否意味着一定要读取回该资源的整个实体内容呢 我们试着这么想——客户端上某个资源保存的缓存时间过期了但这时候其实服务器并没有更新过这个资源如果这个资源数据量很大客户端要求服务器再把这个东西重新发一遍过来是否非常浪费带宽和时间呢 答案是肯定的那么是否有办法让服务器知道客户端现在存有的缓存文件其实跟自己所有的文件是一致的然后直接告诉客户端说“这东西你直接用缓存里的就可以了我这边没更新过呢就不再传一次过去了”。 为了让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存的复用率Http1.1新增了几个首部字段来做这件事情。 1、Last-Modified 服务器将资源传递给客户端时会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端。 客户端会为资源标记上该信息下次再次请求时会把该信息附带在请求报文中一并带给服务器去做检查若传递的时间值与服务器上该资源最终修改时间是一致的则说明该资源没有被修改过直接返回304状态码即可。 至于传递标记起来的最终修改时间的请求报文首部字段一共有两个 ⑴ If-Modified-Since: Last-Modified-value 示例为 If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT 该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致则直接回送304 和响应报头即可。 当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。 ⑵ If-Unmodified-Since: Last-Modified-value 告诉服务器若Last-Modified没有匹配上资源在服务端的最后更新时间改变了则应当返回412(Precondition Failed) 状态码给客户端。 当遇到下面情况时If-Unmodified-Since 字段会被忽略 1. Last-Modified值对上了资源在服务端没有新的修改 2. 服务端需返回2XX和412之外的状态码 3. 传来的指定日期不合法 Last-Modified 说好却也不是特别好因为如果在服务器上一个资源被修改了但其实际内容根本没发送改变会因为Last-Modified时间匹配不上而返回了整个实体给客户端即使客户端缓存里有个一模一样的资源。 2、ETag 为了解决上述Last-Modified可能存在的不准确的问题Http1.1还推出了 ETag 实体首部字段。 服务器会通过某种算法给资源计算得出一个唯一标志符比如md5标志在把资源响应给客户端的时候会在实体首部加上“ETag: 唯一标识符”一起返回给客户端。 客户端会保留该 ETag 字段并在下一次请求时将其一并带过去给服务器。服务器只需要比较客户端传来的ETag跟自己服务器上该资源的ETag是否一致就能很好地判断资源相对客户端而言是否被修改过了。 如果服务器发现ETag匹配不上那么直接以常规GET 200回包形式将新的资源当然也包括了新的ETag发给客户端如果ETag是一致的则直接返回304知会客户端直接使用本地缓存即可。 那么客户端是如何把标记在资源上的 ETag 传去给服务器的呢请求报文中有两个首部字段可以带上 ETag 值 ⑴ If-None-Match: ETag-value 示例为 If-None-Match: 56fcccc8-1699 告诉服务端如果 ETag 没匹配上需要重发资源数据否则直接回送304 和响应报头即可。 当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。 ⑵ If-Match: ETag-value 告诉服务器如果没有匹配到ETag或者收到了“*”值而当前并没有该资源实体则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。 If-Match 的一个应用场景是客户端走PUT方法向服务端请求上传/更替资源这时候可以通过 If-Match 传递资源的ETag。 需要注意的是如果资源是走分布式服务器比如CDN存储的情况需要这些服务器上计算ETag唯一值的算法保持一致才不会导致明明同一个文件在服务器A和服务器B上生成的ETag却不一样。 如果 Last-Modified 和 ETag 同时被使用则要求它们的验证都必须通过才会返回304若其中某个验证没通过则服务器会按常规返回资源实体及200状态码。 在较新的 nginx 上默认是同时开启了这两个功能的 上图的前三条请求是原始请求接着的三条请求是刷新页面后的新请求在发新请求之前我们修改了 reset.css 文件所以它的 Last-Modified 和 ETag 均发生了改变服务器因此返回了新的文件给客户端状态值为200。 而 dog.jpg 我们没有做修改其Last-Modified 和 ETag在服务端是保持不变的故服务器直接返回了304状态码让客户端直接使用缓存的 dog.jpg 即可没有把实体内容返回给客户端因为没必要。 缓存实践 当我们在一个项目上做http缓存的应用时我们还是会把上述提及的大多数首部字段均使用上例如使用 Expires 来兼容旧的浏览器使用 Cache-Control 来更精准地利用缓存然后开启 ETag 跟 Last-Modified 功能进一步复用缓存减少流量。 那么这里会有一个小问题——Expires 和 Cache-Control 的值应设置为多少合适呢 答案是不会有过于精准的值均需要进行按需评估。 例如页面链接的请求常规是无须做长时间缓存的从而保证回退到页面时能重新发出请求百度首页是用的 Cache-Control:private腾讯首页则是设定了60秒的缓存即 Cache-Control:max-age60。 而静态资源部分特别是图片资源通常会设定一个较长的缓存时间而且这个时间最好是可以在客户端灵活修改的。以腾讯的某张图片为例 http://i.gtimg.cn/vipstyle/vipportal/v4/img/common/logo.png?max_age2592000 客户端可以通过给图片加上“max_age”的参数来定义服务器返回的缓存时间 当然这需要有一个前提——静态资源能确保长时间不做改动。如果一个脚本文件响应给客户端并做了长时间的缓存而服务端在近期修改了该文件的话缓存了此脚本的客户端将无法及时获得新的数据。 解决该困扰的办法也简单——把服务侧ETag的那一套也搬到前端来用——页面的静态资源以版本形式发布常用的方法是在文件名或参数带上一串md5或时间标记符 https://hm.baidu.com/hm.js?e23800c454aa573c0ccb16b52665ac26 http://tb1.bdstatic.com/tb/_/tbean_safe_ajax_94e7ca2.js http://img1.gtimg.com/ninja/2/2016/04/ninja145972803357449.jpg 如果文件被修改了才更改其标记符内容这样能确保客户端能及时从服务器收取到新修改的文件。 关于开头的问题 现在回过头来看文章开头的问题可能会觉得答案很容易回答出来。 百度首页的资源在刷新后实际没有发送任何请求因为 Cache-Control 定义的缓存时间段还没到期。在Chrome中即使没发送请求但只要从本地的缓存中取都会在Network面板显示一条状态为200且注明“from cache”的伪请求其Response内容只是上一次回包留下的数据。 然而这并不是问题的全部答案我们前面提到过在Chrome中如果点击“刷新”按钮Chrome会强制给所有资源加上“Cache-Control: max-age0”的请求首部并向服务器发送验证请求的而在文章开头的动图中我们的确点击了“刷新”按钮却不见浏览器发去新请求并返回304。 关于这个问题其实在组内跟小伙伴们讨论过通过Fiddler抓包发现如果关闭Chrome的开发者面板再点击“刷新”按钮浏览器是会按预期发送验证请求且接收返回的304响应的另外这个奇怪的情况在不同的网站甚至不同的电脑下出现频率都不一致所以暂时将其归咎于浏览器的怪异反应。 那么有这么一个问题——是否有办法在浏览器点击“刷新”按钮的时候不让浏览器去发新的验证请求呢 办法还是有的就是不怎么实用——在页面加载完毕后通过脚本动态地添加资源 $(window).load(function() { var bghttp://img.infinitynewtab.com/wallpaper/100.jpg; setTimeout(function() { //setTimeout是必须的 $(#bgOut).css(background-image, url(bg)); },0); }); 出处来自知乎更具体的解释可以去看看。 其它相关的首部字段 事实上较常用和重要的缓存相关字段我们都介绍完了这里顺带讲讲几个跟缓存有关系但没那么主要的响应首部字段。 1、Vary “vary”本身是“变化”的意思而在http报文中更趋于是“vary from”与。。。不同的含义它表示服务端会以什么基准字段来区分、筛选缓存版本。 我们先考虑这么一个问题——在服务端有着这么一个地址如果是IE用户则返回针对IE开发的内容否则返回另一个主流浏览器版本的内容。这很简单服务端获取到请求的 User-Agent 字段做处理即可。但是用户请求的是代理服务器而非原服务器且代理服务器如果直接把缓存的IE版本资源发给了非IE的客户端这就出问题了。 因此 Vary 便是着手处理该问题的首部字段我们可以在响应报文加上 Vary: User-Agent 便能知会代理服务器需要以 User-Agent 这个请求首部字段来区别缓存版本防止传递给客户端的缓存不正确。 Vary 也接受条件组合的形式 Vary: User-Agent, Accept-Encoding 这意味着服务器应以 User-Agent 和 Accept-Encoding 两个请求首部字段来区分缓存版本。 2、Date 和 Age HTTP并没有提供某种方法来帮用户区分其收到的资源是否命中了代理服务器的缓存但在客户端我们可以通过计算响应报文中的 Date 和 Age 字段来得到答案。 Date 理所当然是原服务器发送该资源响应报文的时间GMT格式如果你发现 Date 的时间与“当前时间”差别较大或者连续F5刷新发现 Date 的值都没变化则说明你当前请求是命中了代理服务器的缓存。 上述的“当前时间”自然是相对于原服务器而言的时间那么如何获悉原服务器的当前时间呢 常规从页面地址请求的响应报文中可获得以博客园首页为例 每次你刷新页面浏览器都会重新发出这条url的请求你会发现其 Date 值是不断变化的这说明该链接没有命中缓存都是从原服务器返回过来的数据。 因此我们可以拿页面上其它静态资源请求回包中的 Date 与其进行对比若静态资源的 Date 早于原服务端时间则说明命中了代理服务器缓存。 通常还满足这么个条件 静态资源Age 静态资源Date 原服务端Date 这里的 Age 也是响应报文中的首部字段它表示该文件在代理服务器中存在的时间秒如文件被修改或替换Age会重新由0开始累计。 我们在上面那张博客园首页报文截图的同个场景下看看某个文件jQuery.js命中代理服务器缓存的回包数据 会发现它满足我们上述的规则 //return true new Date(Mon, 04 Apr 2016 07:03:17 GMT)/1000 new Date(Sat, 19 Dec 2015 01:29:14 GMT)/1000 9264843 不过这条规则也不一定准确特别是当原服务器经常修改系统时间的情况下。 关于http缓存原理的知识就整理到这希望能让你有所收获共勉~ 作者VaJoy / 蓝邦珏 链接http://www.cnblogs.com/vajoy/p/5341664.html 转载于:https://www.cnblogs.com/iifranky7/p/5467672.html