我对HTTP缓存感到困惑

13

我一直在思考在RESTful环境中如何进行批量读写操作,并且我认为我已经意识到我对HTTP缓存有更广泛的问题。(下面我使用逗号(“,”)来分隔多个记录ID,但该细节并不特定于本讨论。)

我从这个问题开始:

1. 批量更新使单个GET无效

GET /farms/123         # get info about Old MacDonald's Farm
PUT /farms/123,234,345 # update info on Old MacDonald's Farm and some others
GET /farms/123

当一个缓存服务器位于客户端和Farms服务器之间时,它如何知道在看到PUT请求时使其缓存无效的/farms/123

然后我意识到这也是一个问题:

2. 单个(或批量)更新会使批量GET无效

GET /farms/123,234,345 # get info about a few farms
PUT /farms/123         # update Old MacDonald's Farm
GET /farms/123,234,345

当缓存看到 PUT 时,它如何知道要使多个农场的 GET 无效?

所以我认为问题实际上只出现在批量操作中。然后我意识到任何关系都可能导致类似的问题。假设一个农场有零或一个所有者,而所有者可以拥有零或一个农场。

3. 由相关记录更新导致单个GET无效

GET /farms/123   # get info about Old MacDonald's Farm
PUT /farmers/987 # Old MacDonald sells his farm and buys another one
GET /farms/123

当缓存看到PUT方法被触发时,它如何知道要使之前的单个GET方法无效?

即使您更改模型以更符合RESTful标准,使用关联模型,您仍然会面临同样的问题:

GET    /farms/123           # get info about Old MacDonald's Farm
DELETE /farm_ownerships/456 # Old MacDonald sells his farm...
POST   /farm_ownerships     # and buys another one
GET    /farms/123

#3的两个版本中,第一个GET应该返回类似以下JSON的内容:

farm: {
  id: 123,
  name: "Shady Acres",
  size: "60 acres",
  farmer_id: 987
}

第二个GET请求应返回类似以下内容:

farm: {
  id: 123,
  name: "Shady Acres",
  size: "60 acres",
  farmer_id: null
}

即使你妥善使用ETag, 缓存服务器也不能缓存! 你不能期望缓存服务器检查内容的ETag--内容可能被加密。而且你不能期望服务器通知缓存记录应该被作废--缓存不会向服务器注册。

那么是否有我遗漏的头信息? 是否有一些指示缓存对某些资源进行任何GET之前应该做HEAD的东西? 如果我能告诉缓存哪些资源可能经常更新,我可以接受每个资源的双重请求。

那么关于一个缓存接收到PUT并知道作废其缓存,而另一个没有看到它的问题呢?


刚刚构建了一个快速响应的Ajax Web应用程序,我非常关注这个问题将如何得到解答。+1和Star! - Karl
也许我正在寻找的是一个“invalidates-other”头,服务器在从PUT、POST或DELETE返回时可以添加。然而,它似乎并不存在。 - James A. Rosen
在HTTPS的情况下,代理服务器只能看到主机名和端口,无法看到头信息或pathInfo,因此问题不存在。 +++ 在找到你出色的问题之前,Invalidates-Other正是我努力构思问题的内容。 - maaartinus
5个回答

6
缓存服务器应该在收到PUT请求时使URI所指的实体失效(但正如您所注意到的,这并不能涵盖所有情况)。
除此之外,您可以在响应中使用缓存控制头来限制或防止缓存,并尝试处理请求头,以询问URI是否自上次获取以来已被修改。
这仍然是一个非常复杂的问题,事实上仍在继续研究(例如,请参见http://www.ietf.org/internet-drafts/draft-ietf-httpbis-p6-cache-05.txt)。
如果内容被加密(至少通过SSL),则代理内部的缓存不会真正应用,因此这不应该是一个问题(但客户端可能仍然存在问题)。

原始问题没有提到缓存服务器,我认为它是关于浏览器本地缓存的。 - Karl
不,我的原始问题是:“当缓存服务器在客户端和Farms服务器之间看到PUT请求时,它如何知道要使其/farms/123的缓存失效?” 我指的是缓存服务器和本地缓存。 - James A. Rosen
关于SSL:请看我的评论,关于在未加密通道上传输加密内容的问题。 - James A. Rosen

2
HTTP协议支持一种称为“If-Modified-Since”的请求类型,基本上允许缓存服务器询问Web服务器该项是否已更改。HTTP协议还支持HTTP服务器响应中的“Cache-Control”标头,告诉缓存服务器如何处理内容(例如,永不缓存此内容,或者假设它在1天后过期等)。
另外您提到了加密响应。HTTP缓存服务器无法缓存SSL,因为这样做需要它们作为“中间人”解密页面。这样做将具有技术挑战性(解密页面,存储它,并为客户端重新加密),并且还会违反页面安全性,在客户端上引起“无效证书”警告。理论上可以让缓存服务器这样做,但这会带来更多问题,是一个坏主意。我怀疑任何缓存服务器都没有执行这种操作。

1

不幸的是,HTTP缓存是基于精确的URI,如果不强制客户端进行缓存重新验证,您无法在您的情况下实现明智的行为。

如果您已经:

GET /farm/123
POST /farm_update/123

你可以使用 "Content-Location" 标头指定第二个请求修改了第一个请求。据我所知,您不能在多个 URI 中执行此操作,并且我还没有检查过这是否在流行的客户端中起作用。
解决方案是快速使页面过期,并处理 "If-Modified-Since" 或 "E-Tag" 与 "304 Not Modified" 状态。

1

你不能缓存动态内容(不带任何缺陷),因为...它是动态的。


0
关于SoapBox的回答:
  1. 我认为If-Modified-Since就是我在问题结尾建议的两阶段GET。当内容很大时(即重复请求的成本和开销被不再重新发送内容所带来的收益所抵消时),这似乎是一个可以接受的解决方案。但在我的农场示例中并非如此,因为每个农场的信息都很简短。

  2. 在未加密的(HTTP)通道上发送加密内容是完全合理的。想象一下服务导向架构的情景,其中更新不频繁,而GET则(a)频繁,(b)需要非常快速,(c)必须加密。您将构建一个服务器,该服务器需要一个FROM头部(或等效地,在请求参数中提供API密钥),并为请求者发送内容的非对称加密版本。非对称加密很慢,但如果正确缓存,则会击败组合SSL握手(非对称加密)和对称内容加密。在此服务器前面添加缓存将大大加快GET

  3. 缓存服务器可以合理地缓存HTTPS GETs一段时间。我的银行可能会在我的账户主页和最近交易上放置约5分钟的缓存控制。我不太可能在网站上花费很长时间,因此会话不会很长,并且在寻找我最近发送给SnorgTees的支票时,我可能会多次访问我的账户主页。


如果自从上次修改后没有增加请求数,则不需要使用If-modified-since。 - Kornel
我非常确定它可以。如果缓存可以找出哪些条目是当前的,就不必发送"If-Modified-Since"请求了。你说得对,它并不会使数量翻倍。这取决于读写比率。 - James A. Rosen
随着我深入研究,我发现HTTP缓存是建立在缓存通过If-Modified-Since验证的想法上的。这似乎有很多开销,但它似乎可以解决我所有的问题。 - James A. Rosen
实际上,有一些商业代理可以进行一些丑陋的欺骗,以避免SSL证书警告,但这是一个非常可怕的解决方案,并需要将代理视为受信任的CA。 - frankodwyer
@frankodwyer -- 我一直以为代理服务器可以看到 SSL 流量的头部。我会在第三点上认错的。好评论。 - James A. Rosen
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接