本文整理在,我的github上。欢迎Star。
关于3xx系列的状态码
- 300 多资源选择
- 301 永久重定向
- 302 临时重定向
-
303 告诉用户,使用另一个url获取资源
主要目的是允许POST请求的响应将客户端定位到某个资源上。比如说,在文件上传完成后让客户端自动重定向到一个上传成功的结果页面。
- 307 通知用户临时从其他URL响应,重定向的页面展示给用户,让用户去点重定向URL链接。
-
304 服务端没有变更
通知浏览器,可以使用缓存。这篇博文要描述的重点
浏览器(http)缓存机制
类型
说到浏览器缓存,通常会遇到一下两种。
- 200(from cache)
- 304
那么,这两种http状态,对应什么样的机制,流程呢?
缓存机制
综合以上描述,浏览器在请求一个资源时,使用缓存的流程大概如下。
- 首先浏览器会判断,这个资源是否有缓存。没有的话,正常请求。
-
如果有缓存的话,浏览会判断缓存是否过期。如果缓存没有过期,则直接使用。此时就是 200(from cache)。
通过上次缓存留下的:cache-control max-age 和 Expires。
注意:cache-control的优先级高于Expires。 -
如果浏览器的缓存过期了,它会请求服务器。服务器会校验缓存的数据是否真的发生了更改。如果服务器端发现数据没有变。就会返回一个304告诉浏览器,你请求的数据 “Not Modified”,可以继续用缓存。同时,浏览器会更新缓存首部的过期时间等信息。
这里浏览器发起请求时,会用到上次缓存首部的Last-Modified/E-tag。
具体做法是:
取出上次缓存的Last-Modified的值,放在本次请求header的 If-Modified-Since 中。
取出上次缓存的E-tag的值,放在本次请求header中的 If-None-Match 中。
服务器会据此判断资源是否发生过修改,浏览器中的缓存是否依然可用。 如果服务器端修改了上次缓存的内容,则直接返回200,并携带新的内容。
相关http头
上文中提到了几个http头,这里做一下详细的解释。
Cache-Control
- public: 所有内容都将被缓存
- private: 内容只缓存到似有缓存中
- no-cache: 所有内容都不会被缓存
- no-store: 所有内容都不会被缓存到缓存或者internet临时文件中
- must-revalidation/proxy-revalidation: 如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证(主要用于控制浏览器的前进、后退)
- max-age=xxx( xxx is numeric ): 缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高
Expires
头部字段提供一个日期和时间。
(cache-control max-age 和 s-maxage 将覆盖 Expires 头部。)
Last-Modified/E-tag
若服务器在响应一个资源时添加了Last-Modified字段,那么当下一次浏览器再一次向服务器请求该资源时(前提是浏览器中上一次的资源被缓存过了),会在请求header中包含If-Modified-Since字段,且值与服务器第一次响应给浏览器的Last-Modified字段一致。
若服务器在响应一个资源时添加了ETag字段,那么当下一次浏览器再一次向服务器请求该资源时(前提是浏览器中上一次的资源被缓存过了),会在请求header中包含If-None-Match字段,且值与服务器第一次响应给浏览器的ETag字段一致
那么上述是遵循了Http协议的浏览器会自动实现的,而要实现304的功能,就需要服务器(比如Apache对于静态资源会自动实现这两个字段的响应)或者我们手动在服务器端编写响应的逻辑来实现。
写到这,整个浏览器及http缓存机制应该彻底介绍清楚了。
那么,就只剩下如何应用了。
原理与应用
静态资源
所谓静态资源,就是很少会修改的内容,包括前端常年打交道的css、js及图片文件等等。
通常情况下,静态资源的http头是这样配置的。
Cache-Control: public, max-age=31536000
Expires: (一年后的今天)
ETag: (基于内容生成)
Last-Modified: (过去某个时间)
Vary: Accept-Encoding
到这里,可以据此给站点中全部的静态资源配置缓存头。这里我有两个建议。
- 建议给每个静态资源路径添加指纹信息。可以使用gulp或者webpack等构建工具。通过不同版本的指纹信息控制更新缓存。
- 项目中引用的依赖,建议使用CDN资源。依据缓存原理,浏览器只要访问过相同CDN的资源,就会留下缓存,哪怕其他站点。
动态资源
基础配置
对于非私密性和经常性变动的资源,我们可以这样做。
Cache-Control: public, max-age=0
Expires: (当前时间)
ETag: (基于内容生成)
Last-Modified: (过去某个时间)
Vary: Accept-Encoding
这样配置的意义在于,内容可以被浏览器缓存起来。但是因为过期时间的问题,每次都会到服务端判断是否过期。未过期的话,就会收到304,进而继续使用缓存。
控制返回、前进
你如果需要更严格的控制,需要告知浏览器即使当用户点击了「返回/前进」按钮,也需要重新检查这些资源文件,那么可以使用:
Cache-Control: public, no-cache, no-store
私密性缓存
对于私密或者针对用户的内容,需要把 public 替换为 private 以避免内容被代理缓存。
Cache-Control: private, …
关于ETag
建议避免使用 ETag。尤其是使用Apache,又配置了负载均衡的情况下。
针对生成的 ETag,默认的Apache方法需要把文件的索引节(inode),大小(size)和最后修改时间作为输入求值得到。这会导致在负载均衡的环境中,生成的 ETag 值变得毫无用处,因为每个服务器都会针对相同的文件生成一个不同的 Etag 值。这个可能就是唯一的问题导致很多人完全禁用 ETag,其实只要精确地针对一个匹配的文件生成一个独一无二的 ETag 值,就没有必要禁用 ETag 了。
Vary: Accept-Encoding
指定“Vary: Accept-Encoding”标头,用一句话来说明它的意义,就是“告诉代理服务器缓存两种版本的资源:压缩和非压缩,这有助于避免一些公共代理不能正确地检测Content-Encoding标头的问题。
还有一个现实的原因:IE 浏览器不缓存任何带有 Vary 头但值不为 Accept-Encoding 和 User-Agent 的资源。所以通过这种方式添加这个头,才能确保这些资源在 IE 下被缓存。
总的来说,加这么一句肯定没错,相当于“多喝热水”。
参考文献
- [1] http缓存
- [2] 浏览器缓存和304小结
- [3] 浏览器缓存机制
- [4] 你所知道的3xx状态码
- [5] 标头“Vary:Accept-Encoding”指定方法及其重要性分析