前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浏览器缓存机制浅析--HTTP缓存

浏览器缓存机制浅析--HTTP缓存

作者头像
Clearlove
发布2019-08-29 14:29:32
8840
发布2019-08-29 14:29:32
举报
文章被收录于专栏:前端客栈前端客栈

非HTTP协议定义缓存

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

    <meta http-equiv="Pragma" content="no-cache"> 

上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。使用上很简单,但事实上这种禁用缓存的形式用处很有限:

  • 仅有IE才能识别这段meta标签含义,其它主流浏览器仅能识别“Cache-Control: no-store”的meta标签
  • 在IE中识别到该meta标签含义,并不一定会在请求字段加上Pragma,但的确会让当前页面每次都发新请求(仅限页面,页面上的资源则不受影响)
  • 而且所有缓存代理服务器都不支持,因为代理不解析HTML内容本身

HTTP协议定义缓存机制

1. Expires

Expires是HTTP1.0的产物,这个字段早可抛弃,但为了做http协议的向下兼容,你还是可以看到很多网站依旧会带上这个字段。 Expires的值对应一个GMT(格林尼治时间),比如“Mon, 22 Jul 2002 11:12:01 GMT”来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。

2. Cache-Control

Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires。 Cache-Control也是一个通用首部字段,这意味着它能分别在请求报文和响应报文中使用。在RFC中规范了 Cache-Control 的格式为:

    "Cache-Control" ":" cache-directive

作为请求首部时,cache-directive 的可选值有:

字段名称

说明

no-cache

告知(代理)服务器不直接使用缓存,要求向原服务器发起请求。

no-store

所有内容都不会保存到缓存或者Internet临时文件中。

max-age=delta-seconds

告知 (代理)服务器,客户端希望接收一个存在时间不大于detal-seconds秒的资源。

max-stale[=delta-seconds]

告知 (代理)服务器,客户端愿意接收一个超过缓存时间的资源,若有定义delta-seconds则为delta-seconds,若没有则为任意超出的时间。

min-fresh=delta-seconds

告知 (代理)服务器,客户端希望接收一个在小于delta-seconds秒内被更新过的资源。

no-transform

告知 (代理)服务器,客户端希望获取实体数据没有被转换(比如压缩)过的资源。

only-if-cached

告知 (代理)服务器,客户端希望获取换成的内容(若有),而不用向原来服务器请求。

cache-extension

自定义扩展值,若服务器不识别改值则会被忽略。

作为响应首部时,cache-directive的可选值有:

字段名称

说明

public

表明任何情况下都得缓存该资源(即使是需要HTTP认证的资源)

Private[="field-name"]

表明返回报文中全部或者部分(若指定field-name,则为field-name的字段数据)仅开放给某些用户(服务器指定的share-user,如代理服务器)做缓存使用,其他用户则不能缓存这些数据。

no-cache

不直接使用缓存,要向服务器发起(新鲜度校验)请求

no-store

所有内容都不会保存到缓存或者Internet临时文件中。

no-transform

告知客户端缓存文件时不得对实体数据做任何改变。

only-if-cached

告知(代理)服务器,客户端希望获取缓存的内容(如果有),而不向原来服务器发起请求。

must-revalidate

当前资源一定是向原服务器发起验证请求的,若请求失败会返回504(而非代理服务器上的缓存)

proxy-revalidate

与must-revalidate类似,但仅能应用于共享缓存(如代理)

max-age=delta-seconds

告知客户端,该资源在delta-seconds秒内是新鲜的,无需向 服务器发起请求。

s-max-age=delta-seconds

同max-age,但仅用于共享缓存(如代理)

cache-extension

自定义扩展值,若服务器不识别改值则会被忽略。

如图所示:

当然这种组合的方式也会有些限制,比如 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状态码即可。

至于传递标记起来的最终修改时间的请求报文首部字段一共有两个: 1.If-Modified-Since: Last-Modified-value

    If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT

该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致,则直接回送304 和响应报头即可。 当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。

2. If-Unmodified-Since: Last-Modified-value 告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。 当遇到下面情况时,If-Unmodified-Since 字段会被忽略:

  • Last-Modified值对上了(资源在服务端没有新的修改);
  • 服务端需返回2XX和412之外的状态码;
  • 传来的指定日期不合法
2. ETag

服务器会通过某种算法,给资源计算得出一个唯一标志符(Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”一起返回给客户端。

客户端会保留该 ETag 字段,并在下一次请求时将其一并带过去给服务器。服务器只需要比较客户端传来的ETag跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。

如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。

那么客户端是如何把标记在资源上的 ETag 传去给服务器的呢?请求报文中有两个首部字段可以带上 ETag 值: 1. If-None-Match: ETag-value

    If-None-Match: "56fcccc8-1699"

告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。 当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。

2. If-Match: ETag-value 告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。 If-Match 的一个应用场景是,客户端走PUT方法向服务端请求上传/更替资源,这时候可以通过 If-Match 传递资源的ETag。 如果 Last-Modified 和 ETag 同时被使用,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,则要求它们的验证都必须通过才会返回304,若其中某个验证没通过,则服务器会按常规返回资源实体及200状态码。 或许你会问为什么它优先?两者功能相似甚至相同,为什么要同时存在?HTTP1.1中ETag的出现主要是为了解决几个Last-Modified比较难解决的问题:

  • Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
  • 如果某些文件会被定期生成,但有时内容并没有任何变化(仅仅改变了时间),但Last-Modified却改变了,导致文件没法使用缓存
  • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

不能缓存的请求

当然并不是所有请求都能被缓存。 无法被浏览器缓存的请求:

  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的请求无法被缓存

用户行为与缓存

浏览器缓存行为还有用户的行为有关!!!

用户操作

Expires/Cache-Control

Last-Modified/Etag

地址栏回车

有效

有效

页面链接跳转

有效

有效

新窗口打开

有效

有效

前进、后退

有效

有效

F5刷新

无效

有效

Ctrl+F5

无效

无效

总结

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 非HTTP协议定义缓存
  • HTTP协议定义缓存机制
    • 1. Expires
      • 2. Cache-Control
      • 缓存校验字段
        • 1. Last-Modified
          • 2. ETag
          • 不能缓存的请求
          • 用户行为与缓存
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档