13 HTTP中的缓存
HTTP典型应用于能通过采用缓存技术而提高性能的分布式信息系统。HTTP/1.1协议包括的许多使缓存尽可能的工作的元素。因为这些元素与协议的其他方面有着千丝万缕的联系,而且他们相互作用、影响,因此有必要单独的来介绍基本的缓存设计。
如果缓存不能改善性能,他将一无用处。HTTP/1.1中缓存的目的是为了在很多情况下减少发送请求,同时在许多情况下可以不需要发送完整响应。前 者减少了网络回路(译注:一个请求会返回一个响应,请求响应这个过程就是一个回路)的数量;我们利用一个“过期(expiration)”机制来为此目的 (见13.2节)。后者减少了网络应用的带宽;我们用“验证(validation)”机制来为此目的。
对行为,可行性,和关闭的操作的要求放松了语义透明性的目的。HTTP/1.1协议允许服务器,缓存,和客户端能显示地降低透明性当在必要的时候。 然而,因为不透明的操作能混淆非专业的用户,同时可能和某个服务器应用程序不兼容(例如订购商品),因此此协议透明性在下面情况下才能被放松要求:
-- 只有在一个显示的协议层的请求时,透明性才能被客户端或源服务器放松
-- 当出现一个对终端用户的显示的警告时,透明性才能被缓存或被客户端放松
因此,HTTP/1.1协议提供这些重要的元素:
1.提供完整语义透明的协议特征,当这些特征被通信的所有方需要时
2. 允许源服务器或用户代理显示的请求和控制不透明操作的协议特征
3. 允许缓存给这样的响应绑定警告信息的协议特征,这些响应不能保留请求的语义透明的近似
一个基本的原则是客户端必须能够发现语义透明性的潜在的放松。
注意:服务器,缓存,或者客户端的实现者可能会面对设计上的判断,而这些判断没有显示地在此规范里讨论。如果一个判断可能会影响语义透明性,那么实现者应该能维持语义透明性,除非一个仔细的完整的分析能说明打破透明性的好处。
13.1.1缓存正确性(Cache Correctness)
一个正确的缓存必须能用最新的响应来响应请求,此响应当在下面的条件满足时才适合此请求(见13.2.5,13.2.6,和13.12)
此缓存响应已被检测与假设通过源服务器重验证后源服务器返回的响应相等价。
此缓存响应是足够保鲜(fresh)的(见13.2节)。缺省的情况下,这意味着此响应必须满足客户端,源服务器,和缓存的最严格的保鲜要求(见14.9节);如果源服务器指定了保鲜寿命,这说明它是源服务器自己的保鲜要求。
由于客户端和源服务器的最严格的保鲜要求,如果一个缓存的响应不是足够保鲜的,那么在仔细考虑的情况下,缓存可能仍然返回此缓存的响应通过合适的 Warning头域(见13.1.5和14.46节),除非此响应被阻止(例如:通过”no-store” cache-directive ,或者通过一个”no-cache”cache-request-directive;见14.9节)。
此缓存响应是一个304(没有被改变),305(代理重定向),或 错误(4xx或者5xx)响应消息。
如果缓存不能同源服务器通信,那么一个正确的缓存应该用它缓存的响应去响应请求,如果此缓存的响应能正确的服务于请求;如果不能服务器于此请求,那么缓存必须能返回一个错误或警告指示存在通信失败。
如果缓存从服务器端接收到一个响应(或者是一个完整的响应,或者一个304(没有被改变)的响应),此响应应该是正常情况下要发送到请求的客户端 的,并且此响应并不是新鲜的,那么此缓存应该把此响应转发给请求客户端不需要添加一个新的Warning头域(但是没有移去已经存在的Warning头 域)。缓存并不是简单的因为传输中响应变得陈旧而尝试去重验证响应;这可能会导致一个无限的循环。用户代理接收一个陈旧的没有Warning头域的响应应 该提示用户一个警告信息。
13.1.2警告信息(Warnings)
无论何时,缓存返回一个响应,此响应既不是第一手的(first-hand)也不是足够保鲜(在 13.1.1节的条件2),那么缓存必须利用一个Warning常用头域来警告产生的效果。Warning头域和当前定义的警告在14.46节里描述。这 些警告允许客户端去采取合适的动作。
警告可能被用于其他的目的,和缓存相关的目的和其他的目的。警告和错误状态码的区别在于是否是真正的失败。
警告被赋予三位数字warn-codes。第一个数字指明:警告是否必须或不必须从缓存项里删除,在一个成功的重验证之后。
1xx 警告描述了响应的保鲜或重验证状态信息,并且这些信息应该在一个成功的重验证之后删除。1xx警告码可能是由缓存产生的,当缓存验证一个缓存项时。此警告码不能被客户端产生。
2xx 警告描述了实体主体或实体头域的某些方面的信息,这些信息不能被重验证修改,并且这些信息不能在一个成功重验证之后被删除。
见14.46节关于警告码的定义。
HTTP/1.0缓存将缓存所有响应中的警告,并且不会删除第一类警告。穿过HTTP/1.0缓存响应中的警告会携带一个额外的warning-date域,这是为了防止将来的HTTP/1.1接收端信任一个错误的缓存警告。
警告同样携带一个警告文本。此文本可能是任何合适的自然语义(可能基于客户端请求的Accept头域),同时包含一个可选择的关于何种字符集被使用的声明。
多个警告可能会绑定一个响应(或者被源服务器或者被一个缓存发送的),这包括多个警告可以共用一个警告码。例如,服务器可能会以英语和法语提供相同的警告。
当多个警告绑定一个响应时,有时候不可能把所有的警告都展示给客户。HTTP版本不能指定一个严格的优先值去决定警告的优先和顺序显示,但是可以探索一些方法。
13.1.3缓存控制机制 (Cache-control Mechanism)
HTTP/1.1基本的缓存机制(服务器指定过期时间和验证器)对缓存是隐含的指令。某些情况下,服务器或客户端可能需要给HTTP缓存提供显示的指令。我们利用Cache-Control头域为此目的。
Cache-Control头域允许客户端或服务器在请求或响应里传输多个指令。这些指令常常覆盖缺省的缓存算法。作为一个常用规则,如果头域值中 存在一个明显的冲突,那么最具严格解释的头域值会被应用(也就是说,能保留语义透明性的值)。然而,一些情况下,cache-control指令被显示地 指定用来削弱语义透明性的相似性(例如,”max-stale” 或者 “public”)。
Cache-control指令在14.9节被描述。
13.1.4显示的用户代理警告(Explicit User Agent Warnings)
很多用户代理允许用户覆盖基本缓存机制。例 如,用户代理可能允许用户指定缓存实体(即使实体明显是陈旧的)从来不要被验证。或者,用户代理可能习惯于给任何请求加上“Cache- Control:max-stale=3600”。用户代理不能缺省的执行不透明行为,或者不能缺省的执行导致非正常的无效率的缓存行为,但是可能被显示 地设置去这样做通过一个显示的用户动作。
如果用户已经覆盖基本的缓存机制,那么用户代理应该给用户指示何时显示不能满足服务器透明要求的信息(特别地,如果显示的实体被认为是陈旧的)。因 为此协议通常允许用户代理去判定响应是否是陈旧或不是陈旧的,所以此指示只需要当实际发生时显示。此指示不必是对话框;它应该是一个图标(例如,一个正在 腐烂的鱼)或者一些其他的指示器。
如果用户以一种方式已经覆盖了缓存机制,这种方式可能不正常地减少缓存效率,那么用户代理应该继续指示用户的状态(例如,通过一个图片显示)以便用户不能不注意地消费过度的资源或者忍受过度的延迟。
13.1.5规则和警告的例外情况
在某些情况下,缓存的操作者应该设置缓存返回陈旧的响应,即使此响应没有被客户端请求。这个判定本来不应 该轻易决定,但是由于某些原因可能会这样做,特别是当缓存和源服务器连接不好时。无能何时当缓存会返回一个陈旧的响应时,缓存必须给此响应做个标记(利用 Warning头域),因为这样能使客户端提示用户响应是陈旧的。
用户代理照样能采取步骤去获得第一手的或保鲜的响应。由于这个原因,如果客户端显示地请求第一手的或保鲜的响应,缓存就不能返回一个陈旧的响应,除非由于技术或策略方面的原因。
13.1.6由客户控制的行为(Client-controlled Behavior)
当源服务器是过期信息得主要来源时,有时候客户端可能需要控制缓存去判定是否返回一个缓存响应而不需要缓存通过服务器验证它。客户端通过利用一些Cache-Control头域来达到此目的。
客户端的请求可能会指定自己愿意接受一个没有验证的响应的最大的年龄(age);指定0值会强迫缓存重验证所有的响应。客户端照样会指定在响应过期前的最小保持时间。这些选项增加了对缓存行为的限制,同时这样做并不能进一步地放松缓存语义透明的近似。
客户端可能照样会指定它会接受陈旧响应直到陈旧数达到最大数量。这放松了对缓存的限制,同时这可能违反了源服务器指定对语义透明性的限制,但是这可能支持无连接的操作或者高获得性当连接不好时。
13.2 过期模型 (Expiration Model)
13.2.1 服务器指定模型(Server-Specified Expiratiion)
HTTP 缓存会工作的很好,这是因为缓存能完全地避免客户端对源服务器的请求。避免对源服务器请求的主要机制是服务器将来会提供一个显示过期时间 (explicit expiration time),此显示过期时间用来指示响应可能会满足后续的请求。也就是说,客户端请求响应时,缓存能返回一个保鲜(fresh)的响应而不需要从源服务器 那里获得。
我们希望服务器给响应设置显示过期时间(explicit expiration time),服务器相信在过期时间之前实体不会改变。这能保持语义透明性(译注:语义透明性情况1.3节里关于“语义透明”的解释),只要服务器对过期时间仔细选择。
过期机制只能应用于能从缓存获得的响应,不能应用于客户端请求的第一手(first-hand,见1.3节术语)的响应。
如果源服务器希望强制一个语义透明缓存去验证每个请求,它会使显示过期时间(explicit expiration time)设为过去。这就是说响应总是陈旧的,所以此缓存应该验证此响应当缓存利用此响应去服务于后续的请求时。见14.9.4节,有更多强迫重验证的方 法
如果源服务器希望强迫任何HTTP/1.1缓存(不管此缓存是怎样设置的)去验证每一个请求,源服务器应该在Cache-Control头域里指定“must-revalidate”缓存控制指令(见14.9节)。
源服务器利用Expires头域或在Cache-Control头域里通过max-age缓存控制指令,来指定显示过期时间(explicit expiration time)。
过期时间不能被用于强制客户代理去重新刷新它的显示或重载资源;过期的语义只能应用于缓存机制,并且这个机制值只需要检测资源的过期状态当请求那个资源的一个新请求发生时。见13.13节,关于缓存和历史机制的区别。
13.2.2 启发式过期
因为源服务器不能总是提供一个显示过期时间(explicit expiration time),HTTP缓存通常会设置一个启发式过期时间(heuristic expiration time),它采用一种算法,此算法利用其它的值(例如Last-Modified时间)去估计一个似乎合理的过期时间。HTTP/1.1规范没有提供特 定的算法,但是的确是加强了对这些算法结果的最坏情况的限制。因为启发式过期时间可能会对语义透明性妥协,他们本应该被谨慎地使用,并且我们鼓励源服务器 尽可能提供显示过期时间。
13.2.3 年龄(Age)计算
为了了解缓存项(译注:缓存项是用来响应请求的,它包含缓存的响应实体)是否是保鲜的(fresh),缓存需要知道其年龄是否已超过保鲜寿命(freshness lifetime)。我们在13.2.4节中讨论如何计算保鲜寿命,本节讨论如何计算响应或缓存项的年龄。
在此讨论中我们用“now”来表示主机进行计算时时钟的“当前值”。使用HTTP协议的主机,特别是运行于源服务器和缓存的主机,应该使用NTP[28] 或其他类似协议来将其时钟同步到一个全球性的精确时间标准上。
HTTP1.1协议要求源服务器尽可能在发送每条响应时都附加一个Date头域,要尽可能在每个响应里给出响应产生的时间(见14.18节)。我们利用术语“date_value”去表示Date头域的值,这是一种适合于运算操作的表示方法。
当从缓存里获取响应消息时,HTTP/1.1利用Age响应头域来传送响应消息的估计年龄。Age响应头域值是缓存对响应从产生或被重验证开始到现在的时间估计值。
实际上,年龄的值是响应从源服务器途径每一个缓存的逗留时间的总和,再加上响应在网络路径上传输的时间。
我们用“age_value”来表示Age头域的值,这是一种适于算术操作的表示方法。
一个响应的年龄(age)可以通过两种完全不同的途径来计算::
如果本地时钟与源服务器时钟同步的相当好,则用 now - date_value ,若结果为负,则取零。
如果途径响应路径(response path)的所有缓存均遵循HTTP1.1协议,则用age_value。
如果我们有两种独立的方法计算响应的年龄(译注:注意这里是响应的年龄),我们可以合并二者如下:
corrected_received_age = max(now – date_value,age_value)
并且只要我们或者有同步的时钟或者响应途径的所有缓存遵循HTTP/1.1,我们就能得到一个可信赖的结果。
由于网络附加延时,一些重要时隙会在服务器产生响应与下一个缓存或客户端接收之间流逝。如果不经修订,这一延迟会带来不正常的低年龄。
由于导致产生年龄值的请求(译注:就是存在Age头域的请求)是早于年龄值的产生,我们能校正网络附加延迟通过记录请求产生的时间。然后,当一个年 龄值被接收后,它必须被解释成相对于请求产生的时间,而不是相对响应接收的时间。不管有多少延迟,此算法会导致稳定的结果。所以,我们计算:
corrected_initial_age = corrected_received_age + (now - request_time)
这里“request_time”是请求的发送时间。
缓存收到响应时,它计算响应年龄的算法如下:
/*
* age_value
* is the value of Age: header received by the cache with this response.
* date_value
* is the value of the origin server's Date: header
* request_time
* is the (local) time when the cache made the request
* that resulted in this cached response
* response_time
* is the (local) time when the cache received the response
* now
* is the current (local) time
*/
apparent_age = max(0, response_time - date_value); //缓存收到响应时响应的年龄
corrected_received_age = max(apparent_age, age_value);
response_delay = response_time - request_time;
corrected_initial_age = corrected_received_age + response_delay;
resident_time = now - response_time; //即收到响应到现在的时间间隔
current_age = corrected_initial_age + resident_time;
缓存项(cache entry)的current_age是缓存项从被源服务器最后验证开始到现在的时间间隔(以秒记)加上corrected_initial_age。当 从缓存项里产生一条响应时,缓存必须在响应里包含一个Age头域,它的值应该等于缓存项的current_age值。
Age头域出现在响应里说明响应不是第一手的(first-hand)(译注:第一手的说明,响应是直接来自于源服务器到达接收端的,而不是来自于 缓存里保存的副本)。然而相反的情况并不成立,因为响应里缺少Age头域并不能说明响应是第一手的(fisrt-hand),除非所有请求路径上的缓存都 遵循HTTP/1.1协议(也就是说,以前HTTP版本缓存没有定义Age头域)。
13.2.4 过期计算(Expiration Calculations)
为了确定一条响应是保鲜的(fresh)还是陈旧的 (stale),我们需要将其保鲜寿命(freshness lifetime)和年龄(age)进行比较。年龄的计算见13.2.3节,本节讲解怎样计算保鲜寿命,以及判定一个响应是否已经过期。在下面的讨论中, 数值可以用任何适于算术操作的形式表示。
我们用术语“expires_value”来表明Expires头域的值。我们用术语“max_age_value”来表示Cache-Control头域里“max-age”控制指令的值(见14.9.3节)。
max-age指令优于Expires头域执行,所以如果max-age出现在响应里,那么定义如下:
freshness_lifetime = max_age_value
否则,若Expires头域出现在响应里,定义如下:
freshness_lifetime = expires_value - date_value
注意上述运算不受时钟误差影响,因为所有信息均来自源服务器。
如果Expires, Cache-Control:max-age, 或 Cache-Control:s-maxage (见 14.9.3) 均未在响应中出现,且响应没有包含对缓存的其他控制,那么缓存可以用启发式算法计算保鲜寿命(freshness lifetime)。缓存器必须对年龄大于24小时的响应附加113警告,如果此响应不带这种警告。
而且,如果响应有最后修改时间,启发式过期值应不大于从那个时间开始间隔的某个分数。典型设置为间隔的10% 。
计算响应是否过期非常简单:
response_is_fresh = (freshness_lifetime > current_age)
13.2.5澄清过期值(Disambiguation Expiration Values)
由于过期值很容易被设置,有可能两个缓存会包含同一资源的不同保鲜值。
如果客户端执行请求接收到一个非第一手的响应,此响应在此客户端拥有的缓存里仍然是保鲜的,并且缓存里的缓存项里的Date头域的值比此新响应的 Date头域值要新,那么客户端应该忽略此响应。如果这样,它可能会重新以“Cache-Control:max-age=0”指令(见14.9节)请求 去强制任何中间缓存通过源服务器对其进行检查。
如果缓存对有不同验证器(validator)的同一个表现形式(representation)有两个保鲜响应,那么缓存必须利用Date头域值 最近的响应。这种情况可能发生由于缓存会从其他缓存得到响应,或者由于客户端请求对一个保鲜缓存项的重载或重验证(revalidation)。
13.2.6澄清多个响应(Disambiguating Multiple Response)
因为客户端可能收到经多个路径而来的响应,所以有些响应会经过一些缓存,而其他的响应会经过其他的缓存,客户端收到响应的顺序可能与源服务器发响应的顺序不同。我们希望客户端利用最新的响应,即使旧响应仍然是保鲜的。
实体标签(entity tag)和过期值都不能影响响应的顺序,因为可能会出现晚一点的响应可能会故意携带过早的过期时间。日期值的精度被规定只有一秒。
当客户端试着重新验证一个缓存项的时候(译注:这里的客户端应该包含一个本地缓存),而且接收到的响应的Date头域晚于已存在的缓存项,那么客户端应该重新进行无条件请求,并且包含
Cache-Control: max-age=0
去强制任何中间缓存通过源服务器来验证(validate)这些中间缓存的副本,或者
Cache-Control: no-cache
去强制任何中间缓存去从源服务器获得一个新的副本。
13.3 验证模型(Validation Model)
当缓存有一个旧缓存项并且缓存想利用此缓存项来作为客户端请求的响应时,缓存必须 首先通过源服务器(或者可能通过一个有保鲜响应的中间缓存)对其进行检验看看此缓存项是否可用。我们称做“验证(validating)”此缓存项。因为 我们不想对完整响应的传输付出太大代价,而且如果缓存项无效时也不会产生额外的回路(译注:回路的意思,如:从请求到响应就一条回路),HTTP1.1协 议支持使用条件方法。.
协议支持条件方法的关键特征是围绕“缓存验证器(cache validator)”展开的。当源服务器产成一个完整响应时,它同时会附加一些验证器给响应,这些验证器和缓存项一起保存。当客户端(用户代理或缓存服 务器)对对应有缓存项的资源执行条件请求时,客户端包含一个相关的验证器(validator)在请求里。
服务器(译注:服务器可能是缓存服务器)则核对请求里的验证器和当前本地的验证器是否匹配,如果他们匹配(见13.3.3),则返回一个特定状态码 (通常为304(没有改变))的响应并且此响应不包含实体主体(entity body)。如果不匹配,服务器就返回完整响应(包含实体主体)。这样,我们就避免了传输完整响应如果验证器匹配,同时我们也避免了额外的回路如果验证器 不匹配。
在HTTP1.1协议中,一个条件请求和普通的请求差不多,除了条件请求携带一些特殊的头域(这些头域包含验证器),包含这些特殊的头域隐含地表明请求方法(通常是GET方法)为条件请求方法。
协议中包括缓存验证条件的正和负。也就是说请求方法将会执行如果验证器的匹配或不会执行如果验证器不匹配。
注意:缺少验证器的响应可能会被缓存,而且会被缓存用来为请求提供服务直到缓存的副本过期,除非显示地用缓存控制指令来禁止缓存这样做。然而,缓存不能执行条件方法获取资源如果它没有此资源实体的验证器,那意味着缓存副本将会不可更新在它过期后。
13.3.1最后修改日期 (Last-Modified Dates)
Last-Modifed实体头域值经常被用作一个缓存验证器。简而言之,缓存项被认为是有效的如果实体自从Last-Modifed值之后没有改变。
13.3.2 实体标签缓存验证器(Entity Tag Cache Validators)
ETag响应头域值是实体标签,它提供了一个“不透明(opaque)的缓存验证器。这能得到更可靠的验证当在不方便存放修改日期的情况下,当在HTTP日期值的一秒精度不能满足需要的情况下,或当在源服务器希望避免使用修改日期产生的冲突的情况下。
实体标签在3.11节描述了。使用了实体标签的头域在14.19,14.24,14.26和14.44节里描述了。
13.3.3 强,弱验证器 (Weak and Strong Validators)
由于源服务器和缓存会比较两个验证器来确定他们是否代表相同的实体,所以通常希望实体(entity,实体主体或实体头域)发生任何变化时验证器也相应变化,这样的验证器为强验证器。.
然而,可能存在这样的请求,服务器倾向于仅在实体发生重要的语义变化时才改变验证器,而在实体的某些方面不发生重大改变时就不改变验证器。在资源变化时验证器未必变化的称为弱验证器.。
实体标签通常是强验证器,但协议提供一种机制来使实体标签变成弱验证器。可以认为强验证器在实体的每一字节变化时而变化,而弱验证器仅在实体的语义变化时才变化。换言之,我们能认为强验证器是特定实体的标识,而弱验证器是同一类语义等价实体的标识。
注: 强验证器的例子:一个整数他会随着每次实体发生变化而递增。一个实体的修改时间,如果以秒为精度,能被当作弱验证器,因为在一秒内资源可能改变两次。是否支持弱验证器是选择的。然而,弱验证器可以能高效的去缓存等效对象。
.
客户端产生请求并把验证器包含在一个验证头域里的时候或在服务器比较两个验证器的时候均用到验证器。强验证器可在任何情况下使用,而弱验证器仅在不 依赖严格相等时才可用。当客户端产生条件GET请求来请求一个完整实体时,任何类型的验证器都可以使用。然而,子范围(sub-range)请求时只能使 用强验证器,因为客户端可能会得到一个不一致的实体。
客户端可以在发出简单(非子范围)GET请求里既可以包含弱验证器也可以包含强验证器。客户端不能利用弱验证器在其它的请求形式里。
HTTP1.1协议定义验证的唯一功能就是比较。有两种验证器的比较方法,这依赖于是否能利用弱验证器进行比较。
.
- 强比较方法:在考虑相等的情况下,两验证器必须完全一致,并且两个验证器都是强验证器。.
- 弱比较方法:在考虑相等的情况下,两验证器必须完全一致,但至少有一个验证器可能在不影响结果的情况下被标明为“weak”。
实体标签是强验证器除非它被显示地标记为弱(weak)的。3.11节给出了实体标签的语法。
最后修改时间(译注:Last-Modifed头域的值)被用作请求的验证器时默认为弱验证器,,除非满足下列规则才判定它是强验证器:.
- 验证器被源服务器用来和当前实体的验证器进行比较并且源服务器知道相关的实体不会在当前验证器函盖的秒数内改变两次,
或者
- 验证器可能被客户端用于If-Modified-Since 或者 If-Unmodified-Since头域里,因为客户端有一个关于此实体的缓存项,并且
- 缓存项包含一个日期值,此日期值给出了源服务器发送源响应(译注:源服务器产生的响应)的时间,并且
- Last-Modifed头域值至少提前于日期值(Date头域值)60秒。
或者
- 验证器已经被中间缓存同此实体的缓存项里的验证器比较过 ,并且
- 缓存项包含日期值(Date头域值),此日期值指明了源服务器发送源响应的时间,并且
- Last-Modifed头域值至少提前于日期值(Date头域值)60秒。
此种方法依赖于以下事实,如果两个响应被服务器在同一秒内被发出,但这两个响应都有相同的最后修改(Last-Modified)时间,那么至少有 一个响应的日期值(译注:Date头域的值)和最后修改时间值(Last-Modifed头域的值)是相等的。60秒的限制能保证Date和Last- Modifed头域的值在不同时刻产生。一个实现可能会利用大于60秒的值,如果它认为60秒太短。
如果客户端希望执行子范围(sub-range)请求来请求一个只有最后修改(Last-Modifed)时间和而没有透明验证器的值时,它可能会认为只有最后修改(Last-Modified)时间是强的。
若缓存或源服务器收到一个条件请求,而不是得到完整响应的GET请求时,他必须使用强比较方法去计算此条件。
此规定允许HTTP1.1的,缓存和客户端安全地执行子范围(sub-range)请求来请求从HTTP/1.0得来的值。
13.3.4 关于何时使用实体标签和最后修改时间的规则
我们对源服务器,客户端和缓存采用一套规则和建议来规定不同的验证器何时应该被使用,出
于何种目的被使用。
HTTP/1.1 源服务器:
- 应该发送一个实体标签验证器除非源服务器产生这样一个实体标签不可行。
- 可能会发送弱实体标签而不是强实体标签,如果使用弱实体标签能提高性能的话或者如果发送一个强实体标签不可行的情况下。
- 应该发送一个Last-Modifed值如果可行的话,除非打破语义透明性(这可能由利用If-Modified-Since头域里的日期产生)可能会导致严重的后果。
换句话说,对http1.1源服务器来说,比较好的做法是同时发送强实体标签和Last-Modified值。.
为了合法,强实体标签必须随相关联的实体值改变而改变。弱实体标签应该随相关联的实体在语义上发生改变而改变。
注意:为保证语义透明缓存,源服务器必须避免为两个不同的实体重复利用一个特定的强实体标签值。缓存项应该能保持任意长的时间,而不管过期时间(expiraton time),所以缓存可能会再去尝试去利用在过去某一时刻获得的验证器去验证缓存项。
HTTP/1.1 客户端:
- 若实体标签被源服务器提供,HTTP/1.1客户端必须在任何缓存条件请求(利用了If-Match或If-None-Match的请求)里利用实体标签.。
- 仅当Last-Modified值被源服务器提供时,HTTP/1.1客户端应该在非子范围缓存条件请求(利用If-Modified-Since)里利用此值。
- 仅当Last-Modified值被HTTP/1.0源服务器提供,HTTP/1.1客户端可能会在子范围缓存条件请求(利用了If-Unmodified-Since)里利用此值。
- 如果一个实体标签和Last-Modified值被源服务器提高,HTTP/1.1客户端应该在缓存条件请求里利用这两个验证器。这允许HTTP/1.1和HTTP/1.1缓存能合适地进行响应。
HTTP/1.1源服务器,当接收到一个条件请求并且此请求同时包含Last-Modifed日期(例如,在If-Modified-Since, 或If-Unmodified-Since头域里)和一个或多个实体标签(例如在If-Match,If-None=Match,或If-Range头域 里)作为缓存验证器时,源服务器不能返回一个304状态响应(Not Modified)除非这样做能当当前实体的验证器和请求里所有的条件头域里的验证器一致。
HTTP/1.1缓存服务器,当接收到一个条件请求并且此请求里同时包含Last-Modified日期和一个或多个实体标签作为缓存验证器时,它不能返回一个本地的副本给客户端除非那个副本的验证器和所有请求里条件头域里的验证器一致。
注意:这些规则背的常用的原则是HTTP/1.1服务器和客户端应该在请求和响应里尽可能传输非冗余的信息。接收这些非冗余信息的HTTP/1.1系统将会假设它接收了验证器。
HTTP/1.0客户端和缓存将会忽略实体标签。通常,Last-Modified值被这些系统接收后将会保证透明和高效的缓存行为,所以HTTP /1.1源服务器这时将会提供Last-Modified值。在这些情况下,当HTTP/1.0系统利用一个Last-Modified值作为一个验证器 可能会带来严重的后果时,HTTP/1.1服务器将不会提供Last-Modified值。
13.3.5非验证条件(Non-validating Conditionls)
实体标签背后的原则是只有服务的作者才知道资源的语义而去选择一个合适的缓存验证机制,并且任何验证器比较方法的标准都可能会带来风险。所以,任何其他的头域的比较(除了Last-Modified,为了兼容HTTP/1.0)从来不会被用于验证缓存项。
13.4 响应的可缓存性(Response Cacheability)
除非被缓存控制(见14.9节)指令明确地限制,缓存系统可以将 一成功的响应作为缓存项,可以返回缓存项里的响应副本而不需要验证它如果此副本是保鲜的,并且也可以在验证成功后返回它。如果既没有和响应相关的缓存验证 器也没有和响应相关的显示过期时间(explicit expiration time,译注:见13.3.1节里关于什么是显示过期时间的说明),我们不会认为它是可缓存的,但是某些缓存可能会违反这个期望(例如,当不能进行网络 连接时)。客户端能经常发现从缓存里获得的响应,只需要通过把Date头域值同当前时间作比较。
注意:某些HTTP1.0缓存可能违反这一期望而不提示警告。
还有,某些情况下可能不便保留一实体,或将其返回给后续请求.
然而,在一些情况下,缓存不适合保存一个实体,或者不适合把它放于后续请求的响应里。这可能因为服务作者认为完全语义透明性是有必要的,或着因为安全和隐私的考虑。某些缓存控制指令是为了让服务器能指明某些资源实体或其中的一部分不能被缓存。
注意在14.8节里描述了防止一个共享缓存去保存和返回一个以前请求的响应,如果那个请求包含一个Authorization头域。
除非缓存控制指令防止此响应被缓存,一个接收的响应如果它的状态码是200,203,206,300,301或410,那么此响应应该被缓存保存而 且可用于后续的请求,但这必须受限于过期机制(expiration mechanism)。然而,缓存如果不支持Range和Content-Range头域,那么它不能缓存206响应(部分内容)响应。
接收到的响应如果是其他的状态码(如,302和307),那么此响应不能被用于服务于后续的请求,除非缓存控制指令或其他的头域明确地允许它能这样 做。例如,这些头域包含下面的头域:Expires头域(见14.21);“max-age”,“s-maxage”,“must- revalidate”,“prox-revalidate”,“public”或“private”缓存控制指令(见14.9)。
13.5 从缓存里构造响应
缓存的目的是存储请求的响应信息,为了响应将来的请求。在很多情况下,缓存能返回响应的合适部分给请求者。然而,如果缓存拥有一个基于以前响应的缓存项,它可能必须把新响应的部分和它缓存项里的内容合起来。
13.5.1End-to-end和Hop-by-hop头域
为定义缓存和非缓存代理服务器的行为,我们将HTTP头域分成两类:
- end-to-end头域,他们被传输给最终请求或响应的接收者。响应里end-to-end头域必需作为缓存项的一部分存储,并且必须在从缓存项形成的响应里传输。
- hop-by-hop头域,他们被只对传输层上的连接有意义,并且不能被缓存保存或被代理转发。
下面的HTTP/1.1头域是hop-by-hop头域:
- Connection
- Keep-Alive
- Proxy-Authenticate
- Proxy-Authorization
- TE
- Trailers
- Transfer-Encoding
- Upgrade
所有其他被HTTP/1.1定义的头域均为end-to-end头域。
13.5.2不可更改的头域 (Non-modifiable Headers)
HTTP1.1的某些特征,如数字认证,基于某一些end-to-end头域。一个透明代理不应该改变end-to-end头域,除非这些头域的定义要求或允许它这样做。
一个透明代理(译注:代理一般是缓存,缓存可以叫缓存代理,缓存服务器,代理服务器)不能改变请求或响应里的下面的头域,而且它不能添加这些头域到没有这些头域的请求或响应里:
- Contents-location
- Content-MD5
- ETag
- Last-Modified
一个透明代理不能改变响应里下面的的头域:
- Expires
但它可以添加这些头域如果响应里没有这些头域的时候。如果一个Expires头域被添加,它必须等于响应里Date头域的值。
一个代理(译注:缓存就是一个代理,我们一般称做缓存代理,或缓存服务器,或缓存代理服务器,都是一个意思)不能在消息中改变或添加下面的头域如果此消息包含no-transform缓存控制指令,或在任何请求里也不能添加或改变这些头域。
- Content-Encoding
- Content-Range
- Content-Type
一个非透明代理可能会改变或添加这些头域给一个消息如果此消息不包含no-transform缓存控制指令,但是如果代理这样做了,它必须添加一个警告214(转换被应用)(见14.46节)。
警告:end-to-end头域的不必要的改变可能会导致认证机的失败如果更强的认证机制被应用于后续的HTTP版本中。此认证机制可能依赖于没有在此出现的头域值。
请求或响应里的Content-Length头域被添加或被删除会根据4.4节的规则。一个透明代理必须保留实体主体的entity-length(见7.2.2),尽管它可以可能改变transfer-length(4.4节)。
13.5.3联合头域(Combining Headers)
当一个缓存对服务器发出验证请求时,而且服务器提供304(没有改变)响应或206(部分内容)响应时,那么缓存将构造一个响应发送给请求客户端。
如果状态码是304(没有改变),缓存利用缓存项里的实体主体(entity-body)作为客户端请求响应的实体主体。如果响应状态码是 206(部分内容)并且Etag或Last-Modified头域能精确匹配,那么缓存可能把存入缓存项里的内容和接收到的响应里的新内容合并并且利用最 后合并的结果作为输出响应(见13.5.4)。
存储于缓存项里端end-to-end头域被用于构造响应,除了:
- 任何存储的警告码是1xx(见14.46)Warning头域必须从缓存项和转发的响应里删除。
- 任何存储的警告码是2xx的Warning头域必须要在缓存项和转发的响应里保留。
- 任何在304或206响应里的end-to-end头域必须替换缓存项里相应的头域
除非缓存决定去删除缓存项,否则它必须照样能用接收的响应里的相应的end-to-end头域去替换存储在缓存项里的头域,除了上面描述的Warning头域。如果输入响应里的一个头域匹配缓存项里多个头域,那么所有这些旧的头域必须被替换。
从另一方面说,输入响应的所有end-to-end头域会覆盖缓存项里所有相应的end-to-end头域(除了缓存的警告码是1xx的Warning头域,它将会被删除即使没有被覆盖)。
注意:此规则允许源服务器去利用304(没有改变)或一个206(部分内容)响应去更新任何同一实体或实体的子范围的以前响应的头域,虽然它可能没 有意义或这样做不正确。这条规则不允许源服务器去利用304(没有改变)或206(部分内容)响应去完全地删除一个以前响应的头域。
13.5.4联合字节范围(Combing Byte Ranges)
一条响应可能仅传送一个实体主体的某一部分,这是由于请求包含一个或多个Range指定的范围,或者由于连接会被过早地断开。在几次这样得传输后,缓存可能已经接收了同一个实体主体的多个范围部分。
如果缓存有一个实体的非空子范围,并且一个输入(incoming)响应(译注:输入响应是进入缓存的响应,输出响应是从缓存出去的响应)携带了另一个子范围,那么缓存可能会把新的子范围和已经存在的子范围联合起来如果两者遵循下面的规则:
- 输入响应和缓存项都有缓存验证器。
- 当利用强比较方法的时候,两个缓存验证器完全匹配(见13.3.3)。
如果任何一个要求不能满足,缓存必须利用最近的部分响应(这基于任何响应的Date头域值,并且会利用输入响应如果这些Date头域值相等或丢失了),而且必须丢弃其他的部分信息。
13.6 缓存已经协商过的响应(Caching Negotiated Responses)
在Vary头域出现在响应里的时候,服务器驱动内容协商(12.1节)的使用会改变缓存利用响应去响应后续请求利用的条件和过程。见14.44节关于服务器利用Vary头域的描述。
服务器应该利用Vary头域去通知一个缓存什么样的请求头域应该被使用从而从一个可缓存的并受限于服务器驱动协商的响应的多个表现形式中选择合适的表现形式。Vary头域里指定的头域被称做选择请求头域(selecting request-header)。
当缓存接收到一个后续的请求,此请求的URI对应一个或多个包含了Vary头域的缓存项时,此缓存不能利用这样一个缓存项去构造出一个响应去服务于新来的请求,除非所有出现在新请求里的选择请求头域匹配存储在源请求里被存储的请求头域。
在两个请求里,我们定义这两个选择请求头域匹配,如果并且只有第一个请求的选择请求头域能被转换为第二个请求里的头域通过添加或删除线性空白(被允许出现在相应的BNF里的线性空白)和/或把多个消息头域结合成一个头域通过4.2节里的规则。
一个Vary头域值是“*”总是不能匹配的,并且后续那个资源的请求只能合适地被源服务器解析。
如果缓存项里的选择请求头域(selecting request-header)不能匹配新请求的选择请求头域,那么缓存不能利用缓存项去满足请求除非它能以一个条件请求把此新请求接力到源服务器并且此 源服务器以一个304(没有改变)的状态码进行响应并包含一个实体标签或者Content-Location头域指明将要被使用的实体。
如果一个实体标签被赋予一个缓存的表现形式,那么此转发的请求将是条件的并且所有那个资源的缓存项的实体标签将会被包含于If-None- Match头域里。这向服务器表达当前缓存拥有的实体集,以至于如果这些实体里的任何实体匹配请求的实体,服务器会利用Etag头域在304(没有改变) 响应里去告诉缓存哪个缓存项是合适的。如果新响应的实体标签匹配已经存在的缓存项,那么新响应应该被利用去更新已经存在的缓存项的头域,而且此结果必须返 回给客户端。
如果任何已经存在的缓存项包含只有相关实体的部分内容,那么此实体的实体标签不应该被包含在If-None-Match头域里除非这个请求是为了请求实体的一个范围,此范围完全可以被缓存项满足。
如果缓存接收到一个成功的响应,此响应的Content-Location头域匹配于已经存在的缓存项的Content-Location头域对同 一请求URI来说,并且此响应的实体标签不同于已经存在的缓存项的实体标签,而且此响应的Date头域值比已经存在的缓存项更近,那么已经存在的缓存项不 能被返回去响应将来的请求并且将会从缓存里删除。
13.7 共享和非共享缓存 (Shared and Non-Shared Caches)
出于安全和保密考虑,有必要区分共享和非共享缓存。非共享缓存是仅供一个用户使用的缓存,此种情况下,可用性由适当的安全机制控制。所有其它的缓存均被认为是共享缓存。此协议的其它部分给一些对共享缓存的限制以防止隐私丢失或访问控制的失败。
13.8 错误和不完全的响应缓存行为
缓存收到不完整响应(例如响应的字节数比Content-Length头域指定的值要小)也可以存储 它,但是必须把它看作部分响应。部分相应可以合并(见13.5.4);合并结果可能是完整的响应或可能仍是部分的响应。.缓存不能把部分响应返回给客户端 除非有明确要求可以这样做例如利用206(部分内容)状态码响应。缓存不能使用一个200(OK)状态码返回一个部分响应。
如果缓存在试图重验证一个缓存项而收到一个5xx响应时,它既可以将此响应转发给请求的客户端,或者做的就像服务器不能响应似的。在后面的情况下,它可能返回一个以前的接收的响应除非缓存项包含一个“must-revalidate”缓存控制指令(见14.9节)。
13.9 GET 和 HEAD 的副作用(Side Effects of GET and HEAD)
除非源服务器明确地禁止缓存保存它们的响应,对任何资源应用的GET和HEAD方法不应该有副作用,这些副作用会导致错误的行为如果这些响应将从缓存产生。他们可能会仍然有副作用,但缓存在决定缓存时不必考虑这些副作用。缓存总是期望去观察一个源服务器对缓存的明确限制。
一个例外:有些应用习惯于在在GETs和HEADs方法里使用查询URLs(在rel_path_part里包含一个“?”)去执行一个操作而带来 很大的副作用,缓存不能把此URIs的响应看作一个保鲜的除非服务器提供一个显示过期时间(explicit expiration time)。这意味着此URIs的以前从HTTP/1.0服务器产生的响应不能来自于缓存。见9.1.1节相关的信息。
13.10 在更新或删除后的无效性
对源服务器上的某资源执行方法的副作用可能会使一个或多个已经存在的缓存项的不透明性无效。那就是说,虽然他们可能会继续是保鲜的,但是他们不能准确的反应出源服务器将会对这一个新的请求返回什么。
HTTP协议无法保证所有此类缓存项均被标明无效。例如,引起源服务器上资源变化的请求可能不会穿过存有一个缓存项的代理。然而,一些规则帮助减少错误行为的可能。
在此节里,短语“使实体无效”意味着缓存会将所有那个实体的实例从它的存储里移除,或者把这些实体的实例标记为“无效的”并且在他们可能作为后续请求的响应时会被进行重验证。
一些HTTP方法必须让缓存去使一个实体无效 。这些实体被请求URI指定,或在Location或在Content-Location头域里被指定(如果出现的话)。这些方法是:
- PUT
- DELETE
- POST
为了防止服务器***拒绝,一个基于Location或Content-Location头域里的URI的无效性处理必须只有在host部分和请求URI里的host部分相同时才被执行。
一个缓存如果不能理解请求里的方法,那么它应该使请求URI指定的任何实体无效。
13.11 强制写通过( Write-Through Mandatory)
所有可能对源服务器资源进行修改的方法都要写通过给源服务器 (译注:直接穿过缓存在服务器上修改)。这通常包括所有方法除了GET和HEAD方法。缓存在将此种请求转发给服务器并获得相应的响应前不能对请求客户端 做出响应。 这个不能防止代理缓存(译注:也可以叫缓存服务器,缓存)在服务器已经发送最终回复之前发送100(继续)响应。
相反情况(通常叫“写回”或“拷贝回”缓存)在HTTP1.1中是不允许的,这是由于提供一致更新非常困难,并且服务器,缓存和网络的故障会出现在比写回早。
13.12 缓存替换 (Cache Replacement)
如果一个新的可缓存的响应被缓存接收,同时对这一资源的响应已经在缓存里存 在,那么缓存应该利用最新的响应去回复当前的请求。缓存可能会把此新响应放进存储里,并且如果它满足所有其他的要求,缓存将会利用此响应来响应任何将来的 请求。如果缓存想把此新的响应加进缓存的存储,13.5.3的规则必须应用。
说明:一个新响应如果它的Date头域值比已经存在的缓存响应的Date头域值老,那么它是不能缓存的。
13.13 历史列表 (History Lists)
用户代理经常使用历史机制,如“Back”按钮和历史列表,来重新展示在一个会话里接收的一个稍早的实体。
历史机制和缓存机制是不同。历史机制不应该尝试展示一个当前资源状态的语义透明视图。其历史机制只是为了展示在获得资源时看到了什么。
默认情况,一个过期时间不会应用于一个历史机制。如果实体仍然在存储里,历史机制应该显示它即使实体过期了,除非用户叫用户代理去刷新过期的文档。
这不能被解释去防止历史机制告诉用户视图可能过期了。
注意:如果历史机制没必要地防止了用户看陈旧的资源,那么这会强制服务作者去避免利用HTTP过期控制和缓存控制。服务作者可能会认为这是非常重要 的当用户没有被呈现错误消息或警告消息当他们利用导向按钮(如BACK按钮)去看以前获得的资源时。即使有时这些资源本不能被缓存保存或应该可能会很快过 期,用户界面可能会强制服务作者去求助于其他防止缓存的方法(例如,一次性URLs)为了避免历史机制功能的不合适的作用。
转载于:https://blog.51cto.com/sensen/578286