这里的一种解决方法是阻止浏览器缓存下载的对象。这似乎源于 S3 与 Chrome 处理缓存对象的方式交互的不正确行为。我最近回答过关于服务器故障的类似问题,您可以在那里找到更多详细信息。
当您从简单的 HTML(例如<img>
tag),然后在跨域上下文中再次获取相同的对象。
Chrome 会缓存第一个请求的结果,然后使用缓存的响应,而不是在第二次发出新请求。当它检查缓存的对象时,没有Access-Control-Allow-Origin
标头,因为它是从不受 CORS 规则约束的请求中缓存的...因此,当发出第一个请求时,浏览器不会发送Origin
标头。因此,S3 没有回复Access-Control-Allow-Origin
标头(或任何与 CORS 相关的标头)。
问题的根源似乎与 HTTP 有关Vary:
响应头,与缓存相关。
Web 服务器(本例中为 S3)可以使用Vary:
响应头向浏览器发出信号,表明服务器能够生成返回对象的多种表示形式——以及浏览器是否愿意vary请求、响应的属性might不同。当浏览器考虑使用缓存对象时,它应该检查该对象在新上下文中是否有效,然后再得出缓存对象适合当前需要的结论。
事实上,当您发送Origin
向 S3 请求标头,您会得到一个响应,其中包括Vary: Origin
。这告诉浏览器,如果请求中发送的源是不同的值,则响应也可能不同——例如,因为并非所有源都被允许。
根本问题的第一部分是 S3——可以说——应该always return Vary: Origin
每当在存储桶上配置 CORS 时,即使浏览器没有发送原始标头,因为Vary
可以针对您实际未包含在请求中的标头指定,以告诉您如果包含它,响应可能会有所不同。但是,当Origin
不存在。
问题的第二部分是 Chrome——当它查询其内部缓存时——发现它已经拥有该对象的副本。播种缓存的响应不包括Vary
,因此 Chrome 假定该对象对于 CORS 请求也完全有效。显然,事实并非如此,因为当 Chrome 尝试使用该对象时,它发现跨域响应标头丢失。据推测,Chrome 是否收到了Vary: Origin
S3 对原始请求的响应,它会意识到第二个请求的临时请求标头包括Origin:
,因此它会正确地获取对象的不同副本。如果这样做了,问题就会消失——正如我们通过设置所说明的那样Cache-Control: no-cache
对象上,阻止 Chrome 缓存它。但是,事实并非如此。
因此,我们通过设置来解决这个问题Cache-Control: no-cache
在 S3 中的对象上,这样 Chrome 就不会缓存第一个对象,而是为第二个对象发出正确的 CORS 请求,而不是尝试使用缓存的副本,这会失败。
请注意,如果您想避免更新 S3 中的对象以包含Cache-Control: no-cache
响应,还有另一种选择可以解决此问题,而无需实际将标头添加到 S3 中的静态对象。实际上,还有两个选择:
S3 API 尊重在查询字符串中传递的值response-cache-control=no-cache。将其添加到签名 URL 将指示 S3 将标头添加到响应中,无论Cache-Control
与对象一起存储的元数据值(或缺少)。您不能简单地将其附加到查询字符串中——您必须将其添加为 URL 签名过程的一部分。但是一旦将其添加到代码中,您的对象将返回Cache-Control: no-cache
在响应标头中。
或者,如果您可以在呈现页面时分别为同一对象生成这两个签名 URL,则只需更改其中一个签名 URL 相对于另一个签名 URL 的过期时间即可。延长一分钟,或者类似的事情。将过期时间从一个更改为另一个将强制两个签名 URL 不同,并且具有两个不同查询字符串的两个不同对象应被 Chrome 解释为两个单独的对象,这也应该消除第一个缓存对象的错误使用以满足其他请求。