Http1.0的时候,客户端通过Pragma和Expires来设定缓存,当Pragma值为no-cache的时候会告诉客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。Expires的值对应一个GMT(格林尼治时间),比如Mon, 22 Jul 2002 11:12:01,GMT来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求,该时间是相对服务器上的时间。如果Pragma头部和Expires头部同时存在,则起作用的会是Pragma。
针对“Expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,http1.1新增了 Cache-Control 来定义缓存过期时间,若报文中同时出现了 Expires 和 Cache-Control,则以 Cache-Control 为准,也就是说优先级从高到低分别是 Pragma -> Cache-Control -> Expires 。
Cache-Control是通用首部字段,作为请求首部时,可选值
no-cache:告知服务器不直接使用缓存,要求向原服务器发起请求。
no-store:所有内容都不会被缓存,
max-age=*:告知服务器客户端希望接收一个存在时间不大于*秒的资源;
作为响应首部,可选值
public:表明任何情况下都得缓存该资源,
no-cache:不直接使用缓存,要求向服务器发起新鲜度校验请求,no-store:所有内容都不会被缓存,
must-revalidate:当前资源是向原服务器发去验证请求的,若请求失败会返回504,
max-age=*:告知客户端该资源在*秒内是新鲜的(相对于上次访问服务器的时间),无需向服务器发请求。
如果设置的缓存时间过期了,但服务器并没有更新这个资源,是否一定要再发送一遍呢?Http1.1新增了几个字段来验证缓存文件是否更新。服务器将资源传递给客户端时会将资源最后更改的时间以 Last-Modified:*(GMT时间) 的形式加在实体首部返回给客户端。客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码,内容为空,一个304响应比一个静态资源通常小得多,这样就节省了传输数据量 。如果两个时间不一致,则服务器会发回该资源并返回200状态码,和第一次请求时类似。至于传递标记起来的最终修改时间的请求报文首部字段一共有两个 If-Modified-Since: Last-Modified-value,该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致,则直接回送304 和响应报头即可,当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。另一个字段是If-Unmodified-Since: Last-Modified-value,该值告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。
Last-Modified 存在一定问题,如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源),这时候ETag实体首部字段就发挥作用了,服务器会通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”一起返回给客户端。客户端会保留该 ETag 字段,并在下一次请求时将其一并带过去给服务器。服务器只需要比较客户端传来的ETag跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。那么客户端是如何把标记在资源上的 ETag 传回给服务器的呢?请求报文中有两个首部字段可以带上 ETag 值:If-None-Match: ETag-value告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。 当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。If-Match: ETag-value告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。ETag可以更加精确的判断资源是否被修改,可以识别一秒内多次修改的情况。
在URI输入栏中输入然后回车,浏览器发现该资源已经缓存了而且没有过期(通过Expires头部或者Cache-Control头部),没有跟服务器确认,而是直接使用了浏览器缓存的内容。
F5的作用和直接在URI输入栏中输入然后回车是不一样的,F5会让浏览器无论如何都发一个HTTP Request给Server,即使先前的响应中有Expires头部。这时候请求头部的Cache-Control: max-age=0是Chrome强制加上的,而If-Modified-Since是因为获取该资源的时候包含了Last-Modified头部,浏览器会使用If-Modified-Since头部信息重新发送该时间以确认资源是否需要重新发送。如果初次获得的Response中包含ETag,F5引发的Http Request中也是会包含If-None-Match的。
Ctrl+F5要的是彻底的从Server拿一份新的资源过来,所以不光要发送HTTP request给Server,而且这个请求里面连If-Modified-Since/If-None-Match都没有,这样就逼着Server不能返回304,而是把整个资源原原本本地返回一份。实际上,为了保证拿到的是从Server(不是中间代理的Cache)上最新的,Ctrl+F5不只是去掉了If-Modified-Since/If-None-Match,Chrome 51 中会包含两个头部信息Cache-Control: no-cache和Pragma: no-cache。
对于所有可缓存资源,指定一个Expires或Cache-Control max-age以及一个Last-Modified或ETag至关重要。同时使用前者和后者可以很好的相互适应。
前者不需要每次都发起一次请求来校验资源时效性,后者保证当资源未出现修改的时候不需要重新发送该资源。
Cache-Control常见的取值有private、no-cache、max-age、must-revalidate等,默认为private。其作用根据不同的重新浏览方式分为以下几种情况:
(1) 打开新窗口
如果指定cache-control的值为private、no-cache、must-revalidate,那么打开新窗口访问时都会重新访问服务器。而如果指定了max-age值,那么在此值内的时间里就不会重新访问服务器,例如:
Cache-control: max-age=5
表示当访问此网页后的5秒内再次访问不会去服务器
(2) 在地址栏回车
如果值为private或must-revalidate(和网上说的不一样),则只有第一次访问时会访问服务器,以后就不再访问。如果值为no-cache,那么每次都会访问。如果值为max-age,则在过期之前不会重复访问。
(3) 按后退按扭
如果值为private、must-revalidate、max-age,则不会重访问,而如果为no-cache,则每次都重复访问
(4) 按刷新按扭
无论为何值,都会重复访问
Expires和Cache-Control都有一个问题就是服务端作的修改,如果还在缓存时效里,那么客户端是不会去请求服务端资源的(非刷新),这就存在一个资源版本不符的问题,我们可以在发布不同版本的时候在请求URI后面以参数的形式加上版本信息,这种文件覆盖的方式会有个问题,由于多个文件是先后发布到线上环境,在各个文件发布的间隔会出现旧文件引用新文件或者新文件引用旧文件的问题,最优方案是基于文件内容的hash版本冗余机制,即本地文件名不变,发布后的文件名是根据文件内容进行hash运算得到的,只有文件内容发生变化了才会有所更改。这项工作可以借助工具完成。
- 需要兼容HTTP1.0的时候需要使用Expires,不然可以考虑直接使用Cache-Control
- 需要处理一秒内多次修改的情况,或者其他Last-Modified处理不了的情况,才使用ETag,否则使用Last-Modified。
- 对于所有可缓存资源,需要指定一个Expires或Cache-Control,同时指定Last-Modified或者Etag。
- 可以通过标识文件版本名、加长缓存时间的方式来减少304响应。
Http Header里的Content-Type一般有这三种:
- application/x-www-form-urlencoded:数据被编码为名称/值对。这是标准的编码格式。多用于action为get形式的请求
- multipart/form-data: 数据被编码为一条消息,页上的每个控件对应消息中的一个部分。主要用于表单
- text/plain: 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符。postman软件里标的是RAW。action为post,内容为json等纯文本时可用taxt/plain编码方式
当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串追加到url后面,用?分割,加载这个新的url。
当action为post时候,浏览器把form数据封装到http body中,然后发送到server。 如果没有type=file的控件,用默认的application/x-www-form-urlencoded就可以了。 但是如果有type=file的话,就要用到multipart/form-data了。
当action为post且Content-Type类型是multipart/form-data,浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)。