RFC 7234:阅读与思考

2020-03-11

介绍

RFC全称就是Request for Comments,是由IETF(Internet Enginerring Task Force)为在互联网上运行的各种类型的通信分别制定标准和规范,开发者要实现互联网应用程序就按照这个标准来,那么实现出来的软件就能和其他同样遵循RFC的软件进行交互和通信了.比方说HTTP协议中的消息语法和路由这部分工作内容的标准和规范由RFC 7230进行描述,Nginx是遵循这个标准的,Chrome也是遵循这个标准的,那么Chrome就可以向在任何一台服务器上运行着的Nginx(通过HTTP协议)请求网页.

简单来说就是标准,就是规范,大家都遵循这个东西,制造出来的东西就能进行组合和协同工作.更通俗一点,一个手机没说具体哪家厂商生产出来的充电线可以用,但是肯定会有一个标准,比如说一些型号的安卓机,如果充电线满足Type-C标准,就可以拿来给这个手机充电了,这里的Type-C就是一个标准,或者说是一个规范,是一个生产出来的组件能否与其他组件相互工作的准绳和门槛,那么就互联网上的应用来说,RFC也是同样的道理.

RFC 7234是关于什么的?

关于缓存的,网页浏览器和网页服务器之间互相发送请求(Request)和响应(Response),而有的浏览器为了减少不必要的重复请求,会把之前获取到的响应在本地按照键值对的方式存着,比方说有一个请求$x=$https://tools.ietf.org/html/rfc7234,而响应记做$y$,浏览器实现的本地的缓存就会在收到响应后把$(x,y)$这样的键值对记在小本本上,下次再遇到一个请求,这个请求等于$x$,或者说匹配$x$,那么浏览器就把$y$取出来,就不用再去向服务器发送请求并且等着响应了,节省了时间,增加了效率,这个『小本本儿』就叫做缓存.

RFC 7234规定浏览器该怎么把$(x,y)$往小本本儿里面记,怎么样读,怎么样判断小本本里面记着的$(x,y)$能不能用,等等,这都得有个规范的不是?

阅读记录

我已经浏览过了一遍RFC 7234,然后再重读一遍,一边读,一边把思考和总结记在这里,必要时配上RFC的原话.还有就是别把我写的这个拿来当做参考,原文地址我放在文章后尾的参考文献那里了.

第一章 引言

我们这里指的缓存其实也叫HTTP缓存,是响应消息(后面简称响应)在本地的存储,控制这个存储的工作方式的子系统,负责取出和删除缓存中的消息.共享缓存(Shared Cache)可以被多个用户所用,而专用缓存(Private Cache)则必须为不同的用户单独设立.

HTTP/1.1协议中的缓存的主要目的是提高性能,怎么提高呢?通过复用以前(Prior)的响应来满足当前的请求(Current Request).4.2小节定义了一个存着的响应是否是新鲜的(Fresh),新鲜的意思就是说可以不加验证就拿来用,拿来满足当前的请求,而验证通常只是去联络服务器,看这个缓存过期了没有.因为如果一个缓存是新鲜的就意味着不加验证就能拿来用,所以说缓存还是能减少延迟和网络请求时间的.

另外呢,即使一个缓存不再新鲜了,还是能够通过验证来重新变得新鲜,也就是重新向服务器确定当前的超过保质期的缓存有没有真的过期,如果服务器说缓存还没有过期,那这个缓存的保质期是可以被重置的,也就重新变得新鲜了.缓存在这方面有点类似于商店里卖的可乐,除了过期的可乐不能再重新变得新鲜.

第一章第一节 一致性和错误处理

主要是定义了MUST, MUST NOT, REQUIRE, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, OPTIONAL这几个词究竟什么意思,感兴趣的可以看RFC 2119,这里咱们按照字面意思来理解就行了.

第一章第二节 语法记号

用的是Augmented Backus-Naur Form来描述语法,简称ABNF,增强巴斯范式.

第一章第二节第一小节 时间间隔的的表示

用非负整数,用秒来表示时间间隔.

第二章 缓存的运作方式总览

工作正常的缓存会保持HTTP协议语义上的完整性,同时减少已经被缓存了的信息的传输.同时,尽管呢缓存只是HTTP协议中的一项可选(OPTIONAL,大写表示强调)的功能,还是能假设缓存的复用是有意义的,是值得的,如果说没有本地配置明确禁止缓存的话.所以说,这个HTTP缓存规范,它的关注点,实际上是在关注如何防止存储一些不可复用的响应,或者是放在不规范地使用存储了的响应,而不是强制HTTP协议的实现也要实现缓存.说白了就是实现缓存要避免什么,RFC 7234更多是更多的关注这个.

每一个缓存条目(Entry)由一个缓存键(Cache Key)和一个或者多个对应于这个缓存键的HTTP响应组成,缓存条目最常见的型式是一次成功的请求的资源表示符号作为键,响应作为值.

下面特殊情形的缓存处理,404响应,206响应也是能缓存的,不过没必要关注了.

缓存条目的主键一般来说就是请求方法,比如说GET,外加一个请求URL,比如说https://tools.ietf.org/html/rfc7234,很多缓存其实只支持GET.

如果要请求的东西(Request Target)收到内容协商的约束,内容协商其实就是浏览器告诉服务器”我想要什么形式的内容“,比如说手机浏览器会希望服务器返回适合手机屏幕显示的网页,而英语国家的用户可能会希望浏览器请求的是英文内容的网页,例如Content-Type,Content-Language这样的HTTP请求头,其实就是内容协商的一部分,表明了浏览器希望服务器返回什么,如果服务器有这样的资源,那就会优先满足浏览器发来的内容协商,这就是内容协商.接着说,请求里面如果有内容协商的话,对同样的请求,缓存的实现应该存储多份内容,什么意思呢?

举个例子,一个用户它既通英文又通中文,对https://www.gooogle.com/这个URI,它可能会设置浏览器显示英文,这样浏览器可能就会在请求头中加上Content-Language: en-US,而用户可能会设置浏览器显示中文,这样请求头里面的Content-Language就会变成Content-Language: zh-CN,那缓存的实现在存储响应的时候,同样的地址https://www.google.com/,它就得存储两份响应内容,一份英文的,一份中文的,只因为请求头里面出现了Content-Language这样的内容协商,否则你想象一下用户下一次请求的是中文的,但是中文内容的缓存被英文内容的缓存覆盖了,所以可能浏览器还是会拿英文版本的缓存出来应付中文版本的请求,那这肯定是用户不希望看到的.

所以说,有内容协商的请求,哪怕请求方法和URI一样,也得对每一种不同的内容协商分别存储缓存,下一次再由同样的请求的时候,从这么多缓存中,找到匹配内容协商的,拿出来满足用户.

第三章 在缓存中存储响应

一个缓存的实现一定不能缓存任何请求,除非什么呢:

  • 除非请求的方法,比如说GET,是可以被缓存理解的,缓存能识别HTTP请求头中的请求方法;
  • 并且,响应的状态码(Status Code, 2xx, 3xx之类的),能被缓存所理解,缓存要知道状态码的实际意义,不同的状态码对应着不同的服务器想表达的内容;
  • 并且,Cache-Contol字段中没有private指令;
  • 并且,没有Authorization字段出现在请求头中;
  • 并且,响应满足下列条件之一:
    • 响应的头部包含Expire字段(说明这个内容什么时候过期)
    • 响应的缓存控制字段Cache-Control包含max-age指令(最大保质期)
    • 响应的缓存控制字段Cache-Control包含s-maxage指令(s就是shared的意思,意味共享缓存)
    • 响应的缓存控制字段Cache-Control有允许这样的内容被缓存(一般是针对普通条件下不能被缓存的内容,特事特办的感觉)的缓存控制扩展(Cache Control Extnsion,缓存可选地实现普通缓存协议以外的功能)
    • 响应的状态码,按照定义,指出这种响应默认情况下是可缓存的;
    • 响应的缓存控制字段包括一个public指令,表示这个响应内容是面向公众的,大家看的内容都一样,所以当然可被缓存啦.

第三章第一节 非完全响应的存储

不看.咱们不是做流媒体网站的.

第三章第二节 需要验证的请求的响应的存储

不看.暂时还没有这么奇葩的需求.咱们这篇文章只涵盖基本的常见就够了.

第三章第三节 把部分内容组合起来

你逗我?

第四章 从缓存中构建响应——重点!!

说是一个缓存啊,当它面对一个请求时,它一定不能复用之前存着的响应,除非(以下条件全部满足!):

  • 这个请求明确出现了URI,并且这个请求的URI和存着的那个缓存条目中的URI对得上:比方说请求是GET https://www.google.com/,那我缓存条目也得是GET https://www.google.com/才能拿来用,如果我这时只有一个GET https://www.baidu.com/的缓存条目,我能拿来用吗?肯定不能对吧.
  • 这个请求的方法啊,比如说GET,HEAD,POST之类的,这叫请求方法,允许它的响应内容被存起来当做缓存用,比方说,最基本的,GET请求得到的响应内容,一般都是能拿来用的,除非Cache-Control字段里面或者别的什么地方另有说明,那是另外一回事.
  • 要拿来用的那个缓存,它的头部字段得匹配当前请求的头部字段,就是说一个HTTP请求和一个HTTP响应它事实上都是分别有一个头部的(Header),头部主要是字段和字段的值,你缓存着的这个响应,得跟现在的这个请求的头部(Header),要能匹配得上.
  • 这个请求呢它不能有no-cache在Pragma字段里面,这在旧版的HTTP(也就是HTTP/1.0)意思是千万不要缓存,就得真的发一个请求到服务器,要最新的内容回来.
  • 缓存着的这个响应也不能包含no-cache这样的指令,除非它被成功地验证过证明它还没过期
  • 缓存着的这个响应它要么是新鲜(Fresh)的,要么被明确地允许拿过期(Stale)的来充当新鲜货,要么是被成功地验证过真的还是新鲜的.

注意啦,以上任何一条要求都可以通过缓存控制扩展(Cache-Control Extension)的机制来忽略,比方说啊,Cache-Control: stale-while-error就是这样的一条缓存控制扩展,它允许服务器不可用的时候,缓存拿着过期的缓存来满足请求,这样在用户看来服务器是永远在线的.

当一个存储着的响应被拿来响应当前的请求时,这个缓存的实现它必须为这份响应内容生成一个Age头部字段,什么意思呢?一般来说,一个响应的头部都会有一个Date字段,类似于可乐上边标着的生产日期,诶,现在你缓存如果要拿这个响应来满足当前请求,你还得算一算现在距离这罐可乐,哦不,这个响应的生产的日期已经过了多久了,就是现在离这个Date过了多少秒,来当做这个Age,然后同时可能还会有这么一个Cache-Control: max-age=600指示这灌可乐的保质期是10分钟,那我算出来的这个Age,如果它超过600秒,我就说这罐可乐过期了,不能当缓存用了,不能拿来满足当前的请求了,得必须重新向服务器验证当前缓存的新鲜性或者干脆直接向服务器请求一份全新的内容,就是这么个道理.

遇到不安全的请求方法,缓存的实现不能直接拿缓存出来应付请求,还得把请求转到服务器去,因为不安全的请求方法(POST, PUT, PATCH, DELETE)是会影响服务器内部状态地,真正要响应什么内容那还得看做这么一个不安全请求之后服务器是什么状态,所以这种不安全的请求不能直接拿缓存来应付,也应付不了.

如果有多份存着的响应可以拿来当缓存用,选最新的那个.

如果实现缓存的环境没有表看不了时间的话,不能不经验证就拿缓存来复用,就是说它得每次反复确认这个缓存有没有过期,因为它不知道时间啊,所以就不能根据时间来判断这个响应是否过期,一罐可乐上面写着保质期6个月,但是不不知道现在的时间,哪怕我知道它的生产日期,我怎么知道它过没过期呢?

第四章第一节 面对Vary该怎么处理

遇到了这种情况:缓存中有缓存条目的响应头有Vary字段,并且看来是可以用来满足这个请求的,那么,除非Vary字段里面指定的所有选择字段都匹配得上请求头里面的对应字段,否者不能拿来满足这个请求.举个例子,比如说请求有Content-Language: en,并且缓存条目的头部有Vary: Content-Language,那么这个缓存条目还要满足Content-Language: en才能拿来用.

到底这个头部带Vary的缓存着的响应怎么样才算和这个请求是匹配的,也有相应的规定,是比较繁琐,参考原文吧.

Vary字段有通配符单独出现,比如说Vary: *,那肯定是匹配不上了,就是说这个缓存条目的缓存肯定不能拿来满足请求.Vary里边出现了什么它就得检查什么,比如说Vary: Accept, Content-Language,那就得检查请求头里面的Accept和Content-Language是否和这个响应的Accept和Content-Language都满足,要都满足才能拿来用,所以Vary里面出现的字段越多肯定是越难匹配上,更别说通配符了,那得是所有的字段啊,包括响应里边根本没有的字段.

第四章第二节 新鲜度

一个新鲜的响应它的年龄必须(Age)不能超过它的保质期(Freshness Lifetime),相反的,超过了就算不新鲜了(Stale).

一个响应的保质期其实就是它的过期时间(Expiration Time)到它的生产日期(Its Generation)的时间间隔.一个明确指定的过期时间(通常就是Expire字段)是说服务器计划在这个时间过后更新这个内容.要是响应里边没有给出过期时间,那一般用启发式的方法来估算过期时间.

一个响应的Age就是距离它被生成的那一刻已经过了多久,一般是传输时间加上当前时间减去Date.

重要:如果一个响应被认为是新鲜的,那他就可以用来满足相应的请求而无需联系原服务器,这于是就提供了效率.就是啊,如果一罐可乐显示还没过期,我何必花时间去验证它是否真的没过期呢?

用来决定一个响应的新鲜度的主要机制其实是依赖于原服务器提供一个明确的过期时间,要么是通过Expires字段,要么是通过Cache-Control字段里面的max-age或者s-maxage指令.总的来说,原服务器会给响应头附上明确的未来过期的时间,是基于服务器相信这份响应会在那之后过期.

如果原服务器想强制缓存验证每一次请求(得来的响应是不是新鲜的),它可以给响应内容附上一个过期的过期时间,比如说3月10号出产的可乐可以在它上面附上过期时间:3月9号,用户一收到这个可乐就肯定不会拿来缓存了,下回还会再往服务器取最新内容的.

计算一个响应是否新鲜:

$$ responseIsFresh = freshnessLifeTime > currentAge $$

其中$currentAge$就是这份响应上次被验证或者产生到现在的时间,而$freshnessLifeTime$就是保质期了.主要看保质期和出产时间到现在过了多久,两个时间一比对就知道了.

第四章第二节第一小节 保质期的计算

首先看有没有s-maxage,如果没有看max-age,如果没有看Expires,如果也没有就用启发式的计算方法.

第四章第二节第二小节 保质期的启发式计算方法

不看.

第四章第二节第三小节 计算内容存活时间

存活时间Age是指距离这个内容的生成或者上一次被验证到现在过了多久.就这么简单.具体计算的时候还要加上延迟,不过估算的话可以忽略,毕竟我们设置max-age是以秒为单位的,延迟超过一秒那肯定是验证的性能问题了,而延迟不超过一秒,由于都只是加减法的计算,也不会有太大的误差.

第四章第二节第四小节 拿过期内容以次充好

依据计算内容存活时间和新鲜度的方法,得出的结果指出这个内容过期了,那它就是过期了(Stale.有些情况下不能用过期的响应来应付,比如说请求里边有no-store, no-cache之类的缓存控制指令.如果和服务器失联了,那是可以使用过期缓存的.要生成1xx警告,还要有Warning字段在响应头里边.

第四章第三节 验证

现在假设缓存都全部过期了,不能拿来满足请求,那怎么办?向服务器发送条件请.条件请求就是条件放在请求头里边,服务器收到请求后,看一看请求头里的条件,看一看请求的资源,再看看自身的情况,再决定返回什么样的响应,这就是条件请.发出条件请求要么是为了重新让当前缓存了的内容变得可用,要么是拿全新的响应内容来更新缓存.

第四章第三节第一小节 发出验证请求

当要发送出一个条件请求用于缓存的有效性的验证的时候,缓存的实现可用在发出的请求里面添上一个或者更多个条件头字段(Precondition Header Fields)包含着用来验证当前缓存有效性的验证子(也就是当前缓存的一些信息,服务器收到后就知道客户端的这个缓存内容有没有过期了).

一般来说If-Modified-Since是用得比较多的验证子,怎么说?举个例子,现在假设本地有个缓存内容,它显示Expires: Thu, 31 May 2007 20:35:00 GMT,但是假如说请求发出时已经是Thu, 1 Jun 2007 20:35:00 GMT了,这时缓存的实现往请求里加这么一行If-Modified-Since: Thu, 1 Jun 2007 20:35:00 GMT,就是说服务器如果再这个时间之后,内容实际上还没更新,那内容就没过期,服务器就返回304,就不包括响应内容,这样加快了速度,如果过期了,服务器返回完整的响应内容,然后一般是200,然后缓存的实现拿着这个200来更新缓存,这也是正常的.

第四章第三节第二小节 处理收到的验证请求

这是对服务器来说的,缓存的实现会发来条件请求:如果缓存没过期你就发回个304,如果缓存过期了你发200外加完整内容我好更新缓存,现在是关于服务器这边如何具体处理这个用于验证缓存有效性的条件请求.

客户端发来的用于验证缓存有效性的条件请求里面包含验证子,验证子可能是Etag, If-Not-Match, If-Modified-Since, 如果有Etag和If-Not-Match那服务器就检查当前内容和发来这个条件请求中的Etag是否Match,当不Match的时候才返回200加完整内容,否则就是302告诉缓存实现这个缓存还能用,如果是Expire或者Date+maxage加上If-Modified-Since,那么服务器就看当前资源是不是在If-Modified-Since这个时间之后更改了,如果更改了,那就返回200加完整内容,如果没有修改,就说明缓存还有效,发304就可以了.

第四章第三节第三小节 处理缓存验证响应

刚才先是说了本地缓存过期后,缓存实现会向服务器发条件请求,服务器收到条件请求,如果没过期就发回304,如果过期了就发200加完整内容,现在不管服务器发304还是200,缓存实现都收到了,该怎么处理呢?

很简单,如果是304,那缓存就还没过期,那缓存就重新变得有效,至少可以重置个生存时间和延长保质期之类的.如果是200,那就更新缓存.如果是5xx,那有可能把5xx原样告诉客户端,或者拿旧的缓存来应付.

第四章第三节第四小节 根据验证结果更新缓存

如果说缓存实现这边它收到的验证结果(就是刚才发的这个条件请求的响应)是304的,并且呢同样的缓存键可能会有多个缓存条目(比如说如果响应头里面有Vary字段,会分别存储的),那么缓存实现需要确定到底要更新具体哪一个缓存条目的信息(应该是更新Age之类的字段).

如果说服务器返回的响应头里面包含强验证子(Strong Validator)(Etag算是一个强验证子,它相当于资源的一张『身份证』,唯一地标识资源自身的状态),那么要用这个强验证子来选择到底是更新本地的哪一个缓存条目,如果说本地的缓存条目都没有包含强验证子字段(比如说它们的响应头里边都没有Etag这个字段也没有其他的强验证子字段),那么缓存就千万不能更新任何一条本地存储着的响应,也就是不能更新缓存条目的响应内容.

如果返回来的响应头包含的是若验证子(时间啊之类的),那缓存实现就选最近的,匹配到这个响应头的缓存条目进行更新.

如果返回来的响应不包含任何验证子(别说Etag,连时间都没有),而且本地只有一份存储着的响应也缺少验证子,那就更新这一份.

如果按照刚才说的,根据服务器返回来的响应确实是由本地存储着的响应要被更新了,那缓存必须怎么样呢:首先要删除本地存储着的响应的头部的Warning字段如果这个Warning字段是有1xx这样的值的话,本地的响应里边,Warning字段是2xx的都留着,使用除了这个304响应的其他头部字段去更新本地存储着的响应.

这样就完成验证到缓存的更新了.

第四章第三节第五小节 通过HEAD请求更新响应

HEAD请求和GET请求原本是差不多的,只不过GET请求还会得到请求体(就是请求的内容),而HEAD请求得到的响应中是只有请求头,而没有请求体的.

只有当更有效的GET条件请求不可用(比如说因为本地存储着的响应里边都没有验证子,没有时间啊,没有Etag之类的),那么才用HEAD请求来更新响应,如果HEAD请求收到的返回状态码是200,那就说明本地存储着的响应过期了,要被更新或者要作废不能拿来当缓存用了.

要是本地存储着的响应的头部和HEAD请求的响应的头部匹配得上,并且HEAD请求得到的响应的头部有Content-Length字段,而这个字段还和本地的Content-Length字段对应得上,那缓存实现是可以按一定步骤来更新本地缓存条目的有效期的.

第四章第四节 缓存的作废

首先这里的作废(Invalidation)指的是删除所有存储着的响应,或者呢对这些存储着的响应做标记,标记它们过期了,比如说,使他们不能够用于满足响应的请求.而「非错误响应」指响应的头部的状态码是类似于2xx或者3xx这样的状态码,2xx一般表示成功,3xx一般表示重定向,但都没有发生错误.

因为非安全的请求,比如说像PUT, POST, DELETE这样的非安全请求,会有潜在的可能改变服务器自身的内部状态,所有呢,中间的缓存是可以用这些非安全请求来保证他们自身的内容是最新的.

如果说一个非安全请求返回着非错误响应,大概就是一个POST这样的请求得到2xx,或者3xx这样的响应码儿,它缓存的实现就得把相应的URL对应的缓存条目作废,因为都POST成功了,说明服务器状态还是被改变了,那相应的本地存储着的响应肯定就过期了,这很好理解的.

如果一个响应从Location(原请求Path)或者Content-Location(重定向后的Path)构造出来的URI的host部分和原请求的URI的host部分不一样,那不可用使缓存作废.

如果对一个有效URI发起了一个请求方法的安全性是未知的请求,并且返回的是2xx的状态码,那缓存的实现一定要作废缓存.不确定这种请求方法是否安全,那就默认它不安全,由于默认它不安全,服务器返回了2xx,就认为服务器的状态被改变了,于是缓存肯定要作废.

第五章 头部字段的定义

这一章定义一些和缓存有关的Header里边的字段.

第五章第一节 Age

Age字段的值就是内容的产生到现在的时间,或者是内容的有效性得到验证到现在的时间.

第五章第二节 Cache-Control

Cache-Control是用来对请求发起者到服务器中间的缓存链发起指令的,Cache-Control里面包含着directive.并且这些directives是不分方向的,缓存的实现和服务器都要参考的.

第五章第二节第一小节 请求的缓存控制指令

第五章第二节第一小节第一小小节 max-age

一般是max-age=x,这里的x是个非负整数,表示多少秒,是说客户端不希望收到距离内容产生的时间超过x秒的内容,比如说响应内容是10分钟前产生的,而max-age=500,10分钟相当于于600秒,那客户端不希望收到生存了已经超过500秒的内容,于是600秒的就不能做缓存了,就过期了,作废了.

第五章第二节第一小节第二小小节 max-stale

max-stale=x,x是一个非负整数,也是表示多少秒,是说客户端可以收到过期的内容,但是这个过期的时间,就是超过保质期的时间,不能超过max-stale设定的x.max-stale=100表示最多能过期100秒,如果说内容过期了(就是内容是生存时间超过了max-age),但是只超过了90秒,那还是可以拿来当缓存用,如果已经过期了超过101秒或者更多,那肯定就不能拿来当缓存用了,就算真的过期了.

第五章第二节第一小节第三小小节 min-fresh

内容的存活时间和过期时间的差值叫做新鲜时间,比如说当前这个内容已经存活100秒了,而它的保质期是120秒,那它的新鲜时间就是20秒,也就是它还有20秒是新鲜的.

min-fresh=x秒是说这个新鲜时间必须不能小于这个值,比如说min-fresh=10,虽然说你有120秒的保质期,但是实际上在生存时间超过110的时候就要过期处理了,因为min-fresh=10至少要留着10秒的新鲜时间.

第五章第二节第一小节第四小小节 no-cache

如果Cache-Control字段中出现了no-cache指令,那么缓存的实现将不能不经源服务器的验证就将存着的响应拿来满足当前的请求.

第五章第二节第一小节第五小小节 no-store

缓存的实现不能把响应存起来,no-cache没说不能把响应存着,只是说不能不经验证就拿来用,这两个还是有差别的.

第五章第二节第一小节第六小小节 no-transform

中间的节点不能变换请求体或者响应体的内容,因为是有些代理会这样做,是为了节省流量和内存占用,但是如果Cache-Control里面有这个no-transform,这样做就是不允许的,也就是不允许中间经手这个请求和响应的服务器对内容做变换.

第五章第二节第一小节第七小小节 only-if-cached

表明客户端只希望收到的是缓存,如果是中间服务器没有这个缓存,那它不能往服务器去取,只能返回一个504.感觉这个应该主要是用在诊断和调试这方面.

第五章第二节第二小节 响应的缓存控制指令

第五章第二节第二小节第一小小节 must-revalidate

缓存的实现在缓存过期后不能在把这过期的缓存拿来用,除非得到服务器的验证,验证这个缓存仍然是有效的新鲜的.

第五章第二节第二小节第二小小节 no-cache

服务器发往客户端的响应头里边的Cache-Control字段也可以有no-cache指令,主要是用来表示服务器希望客户端的缓存的实现收到这份响应后,下一次再遇到同样的请求时,必须要再和服务器验证这份本地缓存的有效性才能拿来用,就是说不能直接拿来用.

第五章第二节第二小节第三小小节 no-store

服务器不希望客户端的缓存实现把响应内容存起来当缓存用.

第五章第二节第二小节第四小小节 no-transform

中间服务器不能变换响应内容,主要是响应体的内容,是不能变换的.

第五章第二节第二小节第五小小节 public

指示任何缓存实现都可以把响应存起来当做缓存用,即使这个响应正常情况下是不可缓存的或者只有在私有缓存可缓存的.

第五章第二节第二小节第六小小节 private

表示这个响应内容是对于单个用户的,或者单个组织的,可以由私有缓存存着,但是不能存在公共缓存里面,什么意思呢?

比如说一个页面https://examplepage.com/它对于不同的组织,返回不一样的内容,具体是根据请求头里边的X-Community字段(加了个X你知道这个是非标准字段,是自定的)的值不同,返回不同的页面,比如说X-Community: company1就返回company1想要的页面,X-Community: company2就返回company2想要的页面,并且还知道company1的页面也好还是company2的页面也好,都是可以缓存的,所以这个时候就需要在请求方法,请求网址之外再加一个X-Community键,company1的请求缓存在company1的私有缓存,company2的请求缓存在company2的私有缓存,这样X-Community: compnay1的请求不会收到compnay2的内容,反之也是一样,并且company1的页面更新了,也只是更新company1的私有缓存就好了,没必要影响到company2的.要实现以上需求,其实只要在Cache-Control里面加上private=X-Community就可以了,然后缓存的实现就会分别对company1company2甚至X-Community的其他取值分别设置单独的缓存,这就叫私有缓存.

第五章第二节第二小节第七小小节 proxy-revalidate

和must-revalidate的意思是一样的,只不过不会对私有缓存产生效果.

第五章第二节第二小节第八小小节 max-age

响应内容的保质期也就是,本地缓存会看生存时间Age有没有大过这个max-age,如果大过了就是过期了,就要重新验证,如果没大过就当做缓存用,什么缓存也不发.

第五章第二节第二小节第九小小节 s-maxage

s-maxage会覆盖max-age指令,还是不知道这个有什么用.

第五章第二节第三小节 缓存控制扩展

具体就是在Cache-Control里面添加RFC 7234包括的标准缓存控制指令之外的控制指令,比如说stale-while-revalidate告诉缓存实现可以以后台更新的方式更新缓存,而stale-while-error告诉缓存实现可以返回过期的内容如果原服务器失联了或者5xx了.

具体得看缓存的实现支持什么,如果缓存的实现不认得这些指令,它完全可以忽略,比如说stale-while-revalidate就被Cloudflare忽略却被Fastly支持.

第五章第三节 Expires

是响应头里面的Expires字段,是一个日期时间,表示服务器计划在这个时间之后更新这份内容,客户端的缓存实现可以简单的比较Expires和请求发出时的时间判断用不用向服务器重新验证缓存的有效性,如果不用,直接拿来满足当前请求.

第五章第四节 Pragma

为了兼容只实现了HTTP/1.0协议的缓存,可以包括no-cache,或者一些扩展指令(取决于具体的实现是否支持),no-cache和前面的no-cache是一样的.其实就是旧版的Cache-Control.

第五章第五节 Warning

包含发送者希望在表达的响应码之外的信息,用来提醒接收者,可以包含一个三位十进制数的警告码,外加一些文字的警告信息.

总结

讲的大概涉及到缓存的实现在收到什么信息的时候该怎么做,服务器在收到相应的信息时怎么做,在HTTP消息头部添加日期和缓存控制相关的信息该遵循什么规范,什么样的缓存算是新鲜的,什么算是不新鲜的,怎样判断是新鲜的,如果没有给生产日期怎么办,怎么样的时候不能缓存,怎么样的时候不能储存,等等,还是比较细的,特别繁琐,建议跳着看,看懂大概意思就行了.

参考文献

[1] RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing

[2] RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content

[3] RFC 7234 - Hypertext Transfer Protocol (HTTP/1.1): Caching

概念普及rfcthink

证明任意$n$个连续的正整数的乘积都能被$n!$整除

为你的网站添加Cache-Control