内容没变时服务器只回一个状态码,条件请求靠哪个标识判断
内容没变的时候服务器可以只回一个状态码,条件请求靠哪个标识判断,答案是资源自带的那一枚版本标识。浏览器初次拿到资源时,响应头里会附带一个 ETag,即标识某一版资源的响应头;等本地缓存到期,浏览器并不急着重新下载整份内容,而是发起条件请求,用 If-None-Match 这个请求头把上次拿到的旧值带回去。服务器把带回的值和当前版本的值比对,一致就返回 304,响应里没有任何正文,只告诉客户端:你手上那一版仍然可用。
缓存过期就等于重新下载整份内容吗
缓存的工作节奏分两段。新鲜期内资源直接读本地副本,连请求都不必发;过期之后资源也没有立刻作废,而是先问服务器一句:这一版还作数吗。这一问就是条件请求。没有版本标识,过期就只能重新下载全部内容,哪怕一个字都没改,整份文本或图片被反复搬运显然是浪费;有了标识,判断就降级为比对两个字符串,命中一致时连正文都不必发送。
这枚标识代表什么
标识的规则只有一条:同一地址上的资源内容发生变化,就必须生成新的标识值;内容不变,值就保持不变。它不描述内容长什么样,也不编码内容有多长,只回答一个问题:现在这一版,还是你上次那一版吗。
它与按最后修改时间判断的方式是两套各自独立的校验。时间方式比的是文件上次被动过是什么时候,版本方式比的是这是不是同一版内容。时间粒度较粗,同一秒内的多次改动可能分辨不出;文件被重新保存而内容未变时,时间戳更新还会造成误判。版本标识对内容变化的判断更直接,两者可以共存,客户端带哪个条件头,服务器就按对应的那套逻辑来判。
回访时怎么做比对
流程串起来是这样:服务器在响应里给出标识值,客户端连同内容一起存下;缓存过期后客户端发出新请求,并在 If-None-Match 里附上旧值;服务器计算当前版本的标识再比对。一致就返回 304,客户端继续使用本地副本;不一致就返回 200 和完整新内容,附带新值,客户端替换旧缓存。
生成标识的关键,是把内容真的变了和只是重新渲染了一遍区分开。如果输入里掺了每次请求都波动的成分,比如渲染时刻、随机数,每次响应都会算出新值,比对永远失败,校验就退化成每次都全量下载,等于没有缓存。内容以数据库渲染为主的站点更容易踩到这一点:正文存在库里,配套资源本身稳定,标识更应只取决于真正代表内容的输入。集群各节点的计算输入也要一致,否则同一版内容算出不同值,校验会莫名落空。
强校验与弱校验有什么差别
标识值前面出现 W/ 前缀,表示这是弱校验标识;没有前缀则是强校验标识。弱标识易于生成,但不适合逐字节比较:两边相等只能说明内容在语义层面大致相当,不能保证每个字节都相同。
更实际的影响在分段请求上。浏览器有时用 Range 头只请求文件的一段:弱标识会让这类范围请求无法命中缓存,强标识下范围请求仍可缓存。因为范围请求要求拿到的那一段与整份文件严格对应,弱标识既然不承诺逐字节一致,这段内容是否还是原来那段就无从确认。依赖分段拉取的资源场景,更适合使用强标识。
内容频繁改动的站点怎么用
文章改一次就是一版,标识跟着换值,回访者自然会拿到新内容。需要斟酌的是易变部分:页面里若嵌了实时统计、个性化问候这类每次都在变的内容,整页标识就会每次都变,回访校验全部落空。常见的处理是把易变部分拆出去单独请求,让主体内容保持稳定版本,标识才有恒定的可能。对静态资源,标识照常生成,新鲜期放长一些,过期后的校验大多能命中 304。此外要留意,服务器或框架大版本升级可能改变标识的计算方式,让旧值一夜之间全部对不上,宜按预期内的发布影响来安排。
常见问题
问:这套标识和按修改时间判断要同时配吗? 答:两套是各自独立的校验方式,可以共存。带标识旧值就按版本比对,带时间条件就按时间比对,两者互补而非替代。
问:收到不带正文的 304 是不是请求失败了? 答:不是。304 是校验成功的正常结果,含义是缓存里那一版仍然可用,客户端随即使用本地副本。没有正文,恰恰是省下重复传输的表现。
问:为什么我们的站点从来见不到不带正文的响应? 答:通常是标识每次都在变。检查生成输入里是否混入了渲染时刻之类的波动,以及集群各节点的计算方式是否一致。输入稳定后,内容未变的回访才会命中。
问:改了内容但客户端迟迟没拿到新版,和这套机制有关吗? 答:有可能。内容变了却没有生成新的标识值,回访比对仍会一致,客户端会继续使用旧缓存。同一地址的资源内容发生变化时必须换值,这一步不能省。