你应该了解的 一些web缓存相关的概念.说明:本帖主要针对各个环节的缓存代理,以及本地用户代理(浏览器)上的缓存策略. ps:应舍瓦大大要求,从我的evernote里翻找出这篇和cache相关的东西. 难免有错漏指出,欢迎指正.
代理服务器简分类:(并不太全,仅当科普, 了解代理在web中的重要作用是有必要的.这里仅仅是简单介绍下.) 缓存角度分类: (1) 缓存代理 : 根据某种约定,缓存曾经请求过的数据 (2) 常规代理 : 只转发请求的那一种.并不缓存数据的代理
(1) 反向代理:(对于原始服务器来说,反向代理即是一个客户端) a) 服务提供者主观使用,并控制. b) 相对其他客户端来说,离原始服务器最近(主观上) c) 转发请求,缓存,用于负载均衡. d) 屏蔽其他客户端与原始服务器的直接联,避免原始服务器被直接攻击 e) CDN的应用(严格来说,CDN已经并非纯粹意义上的反向代理了.) (2) 非反向代理: (非服务提供者所控制)
(1) 用户代理 a) 浏览器 b) 各类终端机 c) 搜索引擎爬虫. (2) 透明代理 (3) 拦截代理 (4) http代理 (5) socket代理(UDP,FTP…etc)
HTTP1.1协议: 继承自http1.0,在其基础上扩展或修改而产生. 比如1.0中Date标头,允许使用的RFC1036日期格式,的千年虫问题的修正.悲剧的是,有些明显的错误,无法矫正,比如一个著名的拼写错误. Referer 标头的拼写错误.一直沿用至今.因其,已经被广泛应用.在实际使用上,又无伤大雅,所以也就被继续将错就错了.
(1) 请求标头 : 在一个请求中使用的相关标头,用于添加附加信息,或对被请求服务器增加限制. (2) 响应标头 : 用于添加相关响应的附加信息. (3) 实体标头 : 提供实体相关的附加信息,如果修改时间,实体的大小等等. (4) 常规标头 : (通用标头)在请求和响应中都可以使用的一些常规标头,如HTTP1.0所定义的Date.和 Pragma(是的,这并不是一个实体标头,它仅仅是试图与客户端,包括代理,进行协商,不要使用缓存的副本.). 补充:HTTP1.1的常规标头: Date,Pragma,Cache-Control,Connection,Transfer-Encoding,Upgrade,Trailer,Via,Warning
. Expires (实体标头,HTTP 1.0+) 一个GMT时间,试图告知客户端,在此日期内,可以信任并使用对应缓存中的副本,缺点是,一但客户端日期不准确.则可能导致失效. .Pragma : no-cache(常规标头,http1.0+) 对Pragma定义的唯一的伪指令,同http1.1的Cache-Control : no-cache .Last-Modified(实体标头,HTTP1.0+) 一个GMT时间,告知,被请求实体的最后修改时间.用于客户端校验其缓存副本是否仍然可以信任.与其相关的两个条件请求标头: (1) If-Modified-Since:(此标头,仅对Get方法有意义) 如果实体在指定时间后,没有修改则返回一个304,否则返回一个常规的Get请求的响应(比如200). 另外,如果该标头的值是一个非法的值,那么也同样返回一个常规的Get请求的响应. PS:用户代理发起 If-Modified-Since尝试握手的条件,可能会有不同,比如IE系,如果该实体第一次响应头中包含Cache-Control:no-cache.则 IE不会使用If-Modified-Since请求资源.而其他浏览器则会. 但是如果使用Cache-Control:no-store.则所有用户代理的表现一致.都不使用If-Modified-Since(因为no-store的语义十分强烈.不允许任何缓存,这个在后续有专门介绍.) (2) If-Unmodified-Since: 如果实体在指定时间后,没有任何修改,那么就可以直接执行该请求使用方法的对应行为. 而如果有修改,则返回一个412 Precondition Failed状态码,并且抛弃该方法对应的行为操作(GET方法除外). . Cache-Control : (常规标头,HTTP1.1)
.Etag :(实体标头,HTTP1.1) 通过[Mog95],( http://www.research./wrl/publications/abstracts/95.4.html 遗憾的是,该地址,现在似乎访问不能.)生成一段可代表实体版本的字串.默认就是一段hash + 时间戳的形式.其实我们是可以使用自己的算法来生成Etag值.比如md5. PS:Apache的默认Etag包含Inode,Mtime,Size三部分.而且Etag有强弱之分.比如一般的弱Etag,是以W/开头的,如:W/”abcde12”,这部分不是我们关注的焦点.因为弱Etag和强Etag的区别只在于算法.比如某种弱Etag关注的时间精度,为秒.而我们在项目中,最常见的做法是使用MD5.是一种忽略时间维度的,强Etag.为的是保证精确度.以及负载均衡设备的同步.除非我们的项目有特殊需求.但是往往我们可以根据需求,来调整算法.而不是沿用一些传统的弱Etag算法. 这东西,是要和客户端的两个请求标头配合使用的: (1) If-Match: 语义:如果有匹配,或者值为”*”,才可以能去执行,请求所使用的方法,所对应的行为. If-Match,可以看做是一个过滤器,主要应用于资源多版本共存的解决方案.比如服务器端对同一实体,有多个版本.那么客户端,即可按照指定版本来获取实体. If-Match的值就是对应指定版本的Etag值.这个值可以是多选的.典型的应用场景是,客户端使用put方式请求服务器端,并带有多个If-Match的值.服务器端检查所有该实体的版本.找到匹配项,就立刻更新服务器端的对应版本.如果无一匹配,则发送一个412 Precondition Failed状态码. (2) If-None-Match: 语义:如果有任何匹配,或值是”*”,并且原始服务器存在其请求的实体,则不允许执行该请求所使用放的对应行为,如果此时,该请求使用的get,或head方法.则返回一个304状态码.以及其他一些相关的缓存控制的标头. 与If-Match相反 .但它的典型应用,也是我们要关注的部分.支持http1.1的现代浏览器,以及web server,应用If-None-Match头用于,缓存新鲜度校验.典型应用场景就是,一但原始服务器的某个响应中包含Etag时,如果浏览器本地缓存了该实体.那么在第二次的常规的get或head请求时,就会自动带上 If-None-Match头.当原始服务器上该实体的版本对应的Etag值与之匹配时,则原始服务器会返回304状态码.然后浏览器认为本地缓存是新鲜的.则继续使用缓存的实体. 但,其实Etag的本意是版本管理.而并不是缓存有效性校验.这应该是一个衍生出来的使用方式. 而这种方式相比Last-Modified校验方式的好处是,如果我们消除时间戳部分,仅使用hash作为Etag值. 就可以方便做负载均衡同步. .Age(响应标头,HTTP1.1) Age标头,对于原始服务器来说,用于指明,当前资源被生成了多久,即存活期.而对于一个缓存代理服务器来说,它表示缓存副本,被缓存了多久.缓存代理服务器,必须生成Age头.其值以秒为单位.且可能为负值. .Vary(响应标头,HTTP1.1) Vary标头,用于列出一组响应标头,用于缓存者从其缓存副本中筛选合适的变体.举个例子来说,不同的请求方法,导致对同一资源的响应有区别.这就导致缓存者有多份缓存副本.那么Vary所列出的标头项,就是选择副本时的一个重要依据. 比如Vary:Accept-Language.那么如果新的请求中的Accept-Language标头的值,而原始请求(被缓存的那个)中并未包含与之匹配的Accept-Language的标头的话.那就必须放弃该副本.而是把请求转发到原始服务器. 另一个Vary的典型应用是,Vary:Accept-Encoding.这样做的意义在于,某些用户使用的浏览器,可能不支持一些特定的压缩算法.那么当这个用户途径的某个共享的缓存代理服务器,所缓存的使用了某种压缩算法的响应,就不能直接返回给该用户.如果服务器端,并没有配置这个标头,那就可能产生悲剧.即用户的浏览器无法解压缩返回的资源.导致各种异常状况的出现.
请求标头: .Range : 1000- | -1000 | 0 - Content-Length Range头可以指定像服务器(并不一定总是原始服务器,比如一个缓存代理服务器)端获取范围内的数据,这种格式是很松散的,可以用”,”逗号分割范围的一种表达式.比如1-100,-1 这就表示要获取第一到100个字节,以及最后一个字节的部分.又或者 50-则表示50个字节之后的所有数据搭配可以很灵活.不过,当使用多个范围的Range标头时,假设范围都是合法的(不存在越界的情况,如果越界,则可能返回417 Requested Range Not Satisfiable状态码).则服务器响应时,会修改响应中的Content-Type标头为 如下格式:
好吧,以上这些不是我们关心的重点.我们关心的是和缓存有关的情况,其实对于任何客户端(包括用户代理,以及各类缓存服务器)来说.如果它明确的知道自己想要某一部分范围的数据.就可以使用Range标头. 但事实上,比如对于浏览器来说,一般情况下,只有当服务器端的响应中包含Accept-Ranges:bytes标头时,才可能会在有断点续传需求的时候,自动使用Range头去请求实体.而代只有在响应中,明确出现Accept-Ranges:none时.才会完全避免客户端(包括缓存代理服务器),使用Range标头去获取部分数据.这是要区别看待的. PS:Range头,还可以在GET方式下与If-Unmodified-Since标头配合,进行条件请求.其行为类似于If-Range头中使用日期格式做新鲜度校验. .If-Range : 实体的Etag值 | date日期值. 配合Range使用的条件请求标头,该标头的值可以是被请求实体的Etag值,又或者是其Last-Modified的日期.客户端所缓存的部分数据,是新鲜的,服务器端才会,以206方式返回这部分数据.否则,以200方式返回全部数据.
.Accept-Ranges : bytes | none 用于说明,服务器是否支持范围请求.一般,我们常常为图片等资源设置Accept-Ranges:bytes标头.以便使客户端,可以使用断点续传功能.当然,这一切的前提是,客户端之前有缓存部分数据.或者换个角度说,如果服务器端明确声明,不允许缓存某个实体.那么断点续传也就无从说起了. 所以正确的服务器端配置.是一切的基础. .Content-Range : bytes 0-xxxx/xxxx 标明当前服务器端返回数据的范围. /xxxx部分是总长度.
关于客户端缓存,我们应该关注哪些概念 1. 寿命: 即响应的寿命,指从原始服务器发出实体后所经历的时间,或者是重新验证,证明某缓存仍处于最新状态(可信赖)之后,所经历的时间. 参考响应头中的 Age,其单位是秒. 2. 过期时间: 对于一个缓存实体来说,其过期时间,是由原始服务器设定的.(参考响应头中的Expires,Cache-Control:max-age…等)一但发现过期,则缓存,必须重新验证实体,才能决定是否使用缓存中对应的实体副本,作为响应.如果原始服务器,未设定过期时间,则缓存可以自己做主,设置一个它认为合适的过期时间(就浏览器来说,IE浏览器再这里实现的与众不同,非IE的主流浏览器,不会自作主张的设置一个自认为合适的过期时间,或者说他们总是认为这样的实体.不应该被缓存下来.而IE,则会有一个会话级的缓存.实际上也不是真正有一个过期时间了.所谓会话级缓存,即浏览器不关闭的情况下,始终去读取浏览器本地的缓存.而不会去试图做任何验证的工作.) 3. 新鲜期和陈旧性:(此概念为服务器端概念) 一个HTTP响应是在服务器端某个特定时刻生成的.原始服务器决定这个响应在多长时间内,是确定新鲜的.在这个新鲜期内,相同实体的请求.可以使用原来生成的那个内容作为响应.即服务器端缓存.一但原始服务器端确认某相同实体的响应,已过了新鲜期.即处于陈旧状态.则原始服务器,需要进一步处理这个响应,比如重新生成响应,或者验证是否可以继续使用该响应.并重新设置其新鲜期. 4. 有效性: 缓存,可以与服务器联系,来验证某个缓存副本,是否是可信赖的最新状态.这种检查,被称为有效性重验证.重验证也可以针对代理服务器进行,而不一定总要和原始服务器进行验证. 5. 可缓存性(缓存能力): 缓存者, 必须决定是否存对一个响应,进行缓存.这取决于很多因素,比如原始服务器是否允许响应被缓存,是否设置了缓存过期时间.而有时候即缓存者缓存了一个响应.它仍然需要对该响应,进行验证,以确保该缓存是新鲜的.缓存能力和响应验证,是紧密相关的.
那么协议就是全部么?用户代理就那听话么? 我们理所当然的写一些东西的时候就可靠了么? 下面这段是我的evernote中翻找出来的一段和缓存有关的坑爹的例子.也算给我们自己提个醒. 用户代理可能应为某些原因,给我们造成一些困扰. 所以切记想当然. 一切应以实测为准. 同样.代理服务器也完全可以违背http协议相关的缓存策略.所以 理论上很多东西,都是不可信的:
demo:
|
|
来自: CevenCheng > 《前端优化》