问题描述
我正在尝试帮助一位同事调试过去 6 个月内没有出现的问题.在最近部署 ASP.NET MVC 2 应用程序之后,强制用户打开或保存 PDF 文件的 FileResult
响应在客户端计算机上存在足够长的时间以供 PDF 阅读器打开它们.
I'm attempting to help a colleague debug an issue that hasn't been an issue for the past 6 months. After the most recent deployment of an ASP.NET MVC 2 application, FileResult
responses that force a PDF file at the user for opening or saving are having trouble existing long enough on the client machine for the PDF reader to open them.
IE 的早期版本(尤其是 6)是唯一受影响的浏览器.Firefox 和 Chrome 以及更新版本的 IE (>8) 都按预期运行.考虑到这一点,下一部分定义了重新创建问题所需的操作.
Earlier versions of IE (expecially 6) are the only browsers affected. Firefox and Chrome and newer versions of IE (>8) all behave as expected. With that in mind, the next section defines the actions necessary to recreate the issue.
- 用户单击指向操作方法的链接(带有
href
属性的普通超链接). - action 方法生成一个表示为字节流的 PDF.always 方法重新创建 PDF.
在 action 方法中,设置了 headers 来指示浏览器如何缓存响应.它们是:
- User clicks a link that points to an action method (a plain hyperlink with an
href
attribute). - The action method generates a PDF represented as a byte stream. The method always recreates the PDF.
In the action method, headers are set to instruct browsers how to cache the response. They are:
response.AddHeader("Cache-Control", "public, must-revalidate, post-check=0, pre-check=0");
response.AddHeader("Pragma", "no-cache");
response.AddHeader("Expires", "0");
对于那些不熟悉标题 做:
一个.缓存控制:公共
表示响应可以被任何缓存缓存,即使它通常是不可缓存的或只能在非共享缓存中缓存.
Indicates that the response MAY be cached by any cache, even if it would normally be non-cacheable or cacheable only within a non- shared cache.
b.缓存控制:必须重新验证
当缓存接收到的响应中存在 must-revalidate 指令时,该缓存不能在条目变得陈旧后使用该条目来响应后续请求,无需先使用源服务器重新验证它
When the must-revalidate directive is present in a response received by a cache, that cache MUST NOT use the entry after it becomes stale to respond to a subsequent request without first revalidating it with the origin server
c.Cache-Control: pre-check(IE5引入)
以秒为单位定义一个时间间隔,在此之后必须检查实体的新鲜度.检查可能会在向用户显示资源后进行,但可确保在下一次往返中缓存的副本是最新的.
Defines an interval in seconds after which an entity must be checked for freshness. The check may happen after the user is shown the resource but ensures that on the next roundtrip the cached copy will be up-to-date.
d.Cache-Control: post-check(在 IE5 中引入)
d. Cache-Control: post-check (introduced with IE5)
以秒为单位定义一个时间间隔,在该时间间隔之后,必须在向用户显示资源之前检查实体的新鲜度.
Defines an interval in seconds after which an entity must be checked for freshness prior to showing the user the resource.
e.Pragma: no-cache(确保向后兼容 HTTP/1.0)
e. Pragma: no-cache (to ensure backwards compatibility with HTTP/1.0)
当请求消息中存在 no-cache 指令时,应用程序应该将请求转发给源服务器,即使它有正在请求的缓存副本
When the no-cache directive is present in a request message, an application SHOULD forward the request toward the origin server even if it has a cached copy of what is being requested
f.过期
Expires 实体标头字段给出了响应被视为过时的日期/时间.
The Expires entity-header field gives the date/time after which the response is considered stale.
我们从动作中返回文件
We return the file from the action
return File(file, "mime/type", fileName);
向用户显示打开/保存对话框
The user is presented with an Open/Save dialog box
这里有六个其他应用程序使用相同的标题强制 Excel、CSV、PDF、Word 和大量其他内容向用户展示,从来没有出现过问题.
There are a half dozen other apps here that use the same headers to force Excel, CSV, PDF, Word, and a ton of other content at users and there's never been an issue.
- 标题是否适合我们正在尝试做的事情?我们希望文件暂时存在(被缓存),但总是被新版本替换,即使请求可能相同).
在返回 FileResult
之前在 action 方法中设置响应标头.我已经让我的同事尝试创建一个继承自 FileResult
的新类,而是重写 ExecuteResult
方法,以便它修改标头,然后执行 base.ExecuteResult()
代替 - 没有状态.
The response headers are set in the action method before return a FileResult
. I've asked my colleague to try creating a new class that inherits from FileResult
and to instead override the ExecuteResult
method so that it modifies the headers and then does base.ExecuteResult()
instead -- no status on that.
我有一种预感,0"的Expires"标题是罪魁祸首.根据 这篇 W3C 文章,将其设置为 "0"表示已经过期".我确实希望它过期,我只是不希望 IE 在处理它的应用程序有机会打开它之前将其从文件系统中删除.
I have a hunch the "Expires" header of "0" is the culprit. According to this W3C article, setting it to "0" implies "already expired." I do want it to be expired, I just don't want IE to go removing it off of the filesystem before the application handling it gets a chance to open it.
一如既往,谢谢!
经过进一步测试(使用 Fiddler 检查标头),我们发现我们认为设置的响应标头不是浏览器正在解释的标头.由于我自己不熟悉代码,我不知道一个潜在的问题:标题在操作方法之外被踩到了.
Upon further testing (using Fiddler to inspect the headers), we were seeing that the response headers we thought were getting set were not the ones being interpreted by the browser. Having not been familiar with the code myself, I was unaware of an underlying issue: the headers were getting stomped on outside of the action method.
不过,我将保留这个问题.仍然很突出的是:在 Expires
标头的值为 0
与 -1
之间似乎存在一些差异.如果有人可以声称在设计上与 IE 存在差异,我仍然想听听.至于解决方案,上述标头确实可以在所有浏览器中将 Expires
值设置为 -1
的预期工作.
Nonetheless, I'm going to leave this question open. Still outstanding is this: there seems to be some discrepancy between the Expires
header having a value of 0
vs. -1
. If anybody can lay claim to differences by design, in regards to IE, I would still like to hear about it. As for a solution though, the above headers do work as intended with the Expires
value set to -1
in all browsers.
帖子How to control web page caching, across all browsers?详细描述了可以在所有浏览器中防止缓存在设置 Expires = 0 的帮助下浏览器.我仍然不支持这个 0
与 -1
参数...
The post How to control web page caching, across all browsers? describes in detail that caching can be prevented in all browsers with the help of setting Expires = 0. I'm still not sold on this 0
vs -1
argument...
推荐答案
我认为你应该使用
HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0));
或
HttpContext.Current.Response.Headers.Set ("Cache-Control", "private, max-age=0");
设置 max-age=0
这意味着缓存重新验证(参见 这里).如果您将在标头中另外设置 ETag
并使用您自定义的数据哈希校验和,则来自先前请求的 ETag 将被发送到服务器.服务器可以返回数据,或者如果数据与之前完全相同,它可以返回空正文和 HttpStatusCode.NotModified
作为状态码.在这种情况下,网络浏览器将从本地浏览器缓存中获取数据.
to set max-age=0
which means nothing more as the cache re-validating (see here). If you would be set additionally ETag
in the header with some your custom checksum of hash from the data, the ETag from the previous request will be sent to the server. The server are able either to return the data or, in case that the data are exactly the same as before, it can return empty body and HttpStatusCode.NotModified
as the status code. In the case the web browser will get the data from the local browser cache.
我建议您使用 Cache-Control: private
强制执行两件重要的事情:1)关闭代理上的数据缓存,它有时具有非常激进的缓存设置 2)它将允许缓存数据,但不允许与其他用户共享缓存.它可以解决隐私问题,因为您返回给一个用户的数据可能不允许其他用户读取.顺便代码 HttpContext.Current.Response.Cache.SetMaxAge(new TimeSpan (0))
在 HTTP 中设置 Cache-Control: private, max-age=0
默认情况下标头.如果您确实想使用 Cache-Control: public
您可以使用 SetCacheability (HttpCacheability.Public);
覆盖行为或使用 Headers.Set
而不是 Cache.SetMaxAge
.
I recommend you to use Cache-Control: private
which force two important things: 1) switch off caching the data on the proxy, which has sometimes very aggressive caching settings 2) it will allows the caching of the the data, but not permit sharing of the cache with another users. It can solve privacy problems because the data which you return to one user could be not allowed to read by another users. By the way the code HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0))
set Cache-Control: private, max-age=0
in the HTTP header by default. If you do want to use Cache-Control: public
you can use SetCacheability (HttpCacheability.Public);
to overwrite the behavior or use Headers.Set
instead of Cache.SetMaxAge
.
如果您有兴趣研究更多 HTTP 协议的缓存选项,我建议您阅读 缓存教程一个>.
If you have interest to study more caching options of HTTP protocol I would recommend you to read the caching tutorial.
更新:我决定再写一些信息来澄清我的立场.对应于来自 Wikipedia 的 信息,即使是 Mosaic 2.7、Netscape 2.0 和Internet Explorer 3.0 支持 1996 年 3 月,RFC 2068 中描述的 HTTP/1.1 的预标准.所以我想(但没有测试)旧的网络浏览器支持 max-age=0
HTTP 标头.无论如何,Netscape 2.06 和 Internet Explorer 4.0 都明确支持 HTTP 1.1.
UPDATED: I decide to write some more information to clear my position. Corresponds to the information from the Wikipedia even so old web browsers like Mosaic 2.7, Netscape 2.0 and Internet Explorer 3.0 supports March 1996, pre-standard of HTTP/1.1 described in RFC 2068. So I suppose (but not test it) that the old web browsers support max-age=0
HTTP header. In any way Netscape 2.06 and Internet Explorer 4.0 definitively supports HTTP 1.1.
所以您应该首先问您:您使用哪些 HTML 标准?您是否仍然使用 HTML 2.0 而不是 1997 年 1 月发布的更晚的 HTML 3.2?我想您至少使用 1997 年 12 月发布的 HTML 4.0.因此,如果您至少在 HTML 4.0 中构建应用程序,您的网站可以面向支持 HTTP 1.1 的 Web 客户端,而忽略(不支持)支持 HTTP 1.1 的 Web 客户端不支持 HTTP 1.1.
So you should ask you first: which HTML standards you use? Do you still use HTML 2.0 instead of more late HTML 3.2 published in January 1997? I suppose you use at least HTML 4.0 published in December 1997. So if you build your application at least in HTML 4.0, your site can be oriented on the web clients which supports HTTP 1.1 and ignore (don't support) the web clients which don't support HTTP 1.1.
现在将其他Cache-Control"标头称为private, max-age=0".在我看来,包括标题是纯粹的偏执狂.由于我自己有一些缓存问题,我也尝试包含不同的其他标头,但后来在仔细阅读了 RFC2616 的第 14.9 节后,我只使用了Cache-Control: private, max-age=0".
Now about other "Cache-Control" headers as "private, max-age=0". Including of the headers is in my opinion is pure paranoia. As I have some caching problem myself I tried also to include different other headers, but later after reading carefully the section 14.9 of RFC2616 I use only "Cache-Control: private, max-age=0".
唯一可以额外讨论的Cache-Control"标头是我之前引用的第 14.9.4 节中描述的must-revalidate".这是报价:
The only "Cache-Control" header which can be additionally discussed is "must-revalidate" described on the section 14.9.4 which I referenced before. Here is the quote:
must-revalidate 指令对于支持可靠某些协议功能的操作.在任何情况下HTTP/1.1 缓存必须遵守 must-revalidate 指令;特别是,如果缓存由于任何原因无法到达源服务器,它必须生成 504(网关超时)响应.
The must-revalidate directive is necessary to support reliable operation for certain protocol features. In all circumstances an HTTP/1.1 cache MUST obey the must-revalidate directive; in particular, if the cache cannot reach the origin server for any reason, it MUST generate a 504 (Gateway Timeout) response.
服务器应该发送 must-revalidate 指令当且仅当未能重新验证对实体的请求可能会导致不正确的操作,例如默默未执行的财务交易.收件人不得采取任何自动操作违反此指令,并且不得自动提供如果重新验证失败,则实体的未验证副本.
Servers SHOULD send the must-revalidate directive if and only if failure to revalidate a request on the entity could result in incorrect operation, such as a silently unexecuted financial transaction. Recipients MUST NOT take any automated action that violates this directive, and MUST NOT automatically provide an unvalidated copy of the entity if revalidation fails.
虽然这是不推荐,用户代理在严重连接下运行约束可能违反此指令,但如果是,必须明确警告用户提供了未经验证的响应.这必须在每个未经验证的访问上提供警告,并且应该需要明确的用户确认.
Although this is not recommended, user agents operating under severe connectivity constraints MAY violate this directive but, if so, MUST explicitly warn the user that an unvalidated response has been provided. The warning MUST be provided on each unvalidated access, and SHOULD require explicit user confirmation.
有时,如果我遇到 Internet 连接问题,我会看到带有网关超时"消息的空白页面.它来自必须重新验证"指令的使用.我不认为网关超时"消息真的对用户有帮助.
Sometime if I have problem with Internet connection I see the empty page with "Gateway Timeout" message. It come from the the usage of "must-revalidate" directive. I don't think that "Gateway Timeout" message really help the user.
因此,如果在呼叫老板时听到忙"信号,人们更愿意开始自毁程序,应该在Cache-Control"标头中另外使用must-revalidate"指令.我建议其他人只使用Cache-Control: private, max-age=0",仅此而已.
So the persons, how prefer to start self-destructive procedure if he hears "Busy" signal on the call to his boss, should additionally use "must-revalidate" directive in the "Cache-Control" header. Other persons I recommend just use "Cache-Control: private, max-age=0" and nothing more.
这篇关于ASP.NET MVC 和 IE 缓存 - 操作响应标头无效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!