来自 HttpHandler 的图像不会在浏览器中缓存
我正在使用 IHttpHandler 从数据库提供图像。 相关代码在这里:
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "image/jpeg";
int imageID;
if (int.TryParse(context.Request.QueryString["id"], out imageID))
{
var photo = new CoasterPhoto(imageID);
if (photo.CoasterPhotoID == 0)
context.Response.StatusCode = 404;
else
{
byte[] imageData = GetImageData(photo);
context.Response.OutputStream.Write(imageData, 0, imageData.Length);
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetExpires(DateTime.Now.AddMinutes(5));
context.Response.Cache.SetLastModified(photo.SubmitDate);
}
}
else
context.Response.StatusCode = 404;
}
问题是浏览器不会缓存图像,大概是因为我没有在响应标头中指示正确的内容。 我认为调用 HttpCachePolicy 属性上的方法的部分会强制浏览器保留图像,但事实并非如此。 我认为“正确”的做法是让处理程序返回没有图像的 304 状态代码,对吧? 如何使用 IHttpHandler 实现这一点?
编辑:
根据最佳答案,我运行了这段代码,它完全解决了问题。 是的,它需要一些重构,但它通常展示了我想要的东西。 相关部分:
if (!String.IsNullOrEmpty(context.Request.Headers["If-Modified-Since"]))
{
CultureInfo provider = CultureInfo.InvariantCulture;
var lastMod = DateTime.ParseExact(context.Request.Headers["If-Modified-Since"], "r", provider).ToLocalTime();
if (lastMod == photo.SubmitDate)
{
context.Response.StatusCode = 304;
context.Response.StatusDescription = "Not Modified";
return;
}
}
byte[] imageData = GetImageData(photo);
context.Response.OutputStream.Write(imageData, 0, imageData.Length);
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetLastModified(photo.SubmitDate);
I'm serving up an image from a database using an IHttpHandler. The relevant code is here:
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "image/jpeg";
int imageID;
if (int.TryParse(context.Request.QueryString["id"], out imageID))
{
var photo = new CoasterPhoto(imageID);
if (photo.CoasterPhotoID == 0)
context.Response.StatusCode = 404;
else
{
byte[] imageData = GetImageData(photo);
context.Response.OutputStream.Write(imageData, 0, imageData.Length);
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetExpires(DateTime.Now.AddMinutes(5));
context.Response.Cache.SetLastModified(photo.SubmitDate);
}
}
else
context.Response.StatusCode = 404;
}
The problem is that the browser won't cache the image, presumably because I'm not indicating the right thing in the response headers. The part calling methods on the HttpCachePolicy property is what I thought would force the browser to hold on to the image, but it doesn't. I think the "right" thing is for the handler to return a 304 status code without an image, right? How do I achieve that using IHttpHandler?
EDIT:
Per the best answer, I got this code running and it completely solves the problem. Yes, it needs some refactoring, but it generally demonstrates what I was after. The relevant parts:
if (!String.IsNullOrEmpty(context.Request.Headers["If-Modified-Since"]))
{
CultureInfo provider = CultureInfo.InvariantCulture;
var lastMod = DateTime.ParseExact(context.Request.Headers["If-Modified-Since"], "r", provider).ToLocalTime();
if (lastMod == photo.SubmitDate)
{
context.Response.StatusCode = 304;
context.Response.StatusDescription = "Not Modified";
return;
}
}
byte[] imageData = GetImageData(photo);
context.Response.OutputStream.Write(imageData, 0, imageData.Length);
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetLastModified(photo.SubmitDate);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
AFAIK,您负责发送 304 Not Modified,这意味着我不知道 .Net 框架中是否有任何内容可以在您发送“动态”图像数据的用例中为您执行此操作。 您需要做什么(以伪代码):
跟踪最后修改时间的一个简单方法是在文件系统上缓存新生成的图像,并在内存中保留一个字典,将图像 ID 映射到包含磁盘上文件名的结构以及最后修改日期。 使用 Response.WriteFile 从磁盘发送数据。 当然,每次重新启动工作进程时,字典都会是空的,但您至少可以获得一些缓存好处,而不必在某处处理持久缓存信息。
您可以通过将“图像生成”和“通过 HTTP 发送图像”的关注点分成不同的类来支持这种方法。 现在你正在同一个地方做两件截然不同的事情。
我知道这听起来可能有点复杂,但这是值得的。 我最近刚刚实施了这种方法,处理时间和带宽使用量的节省令人难以置信。
AFAIK, you are responsible for sending 304 Not Modified, meaning I am not aware of anything in the .Net framework that does it for you in this use case of you sending "dynamic" image data. What you will have to do (in pseudo code):
A simple way to track last modified times on your end is to cache newly generated images on the file system and keep an in-memory dictionary around that maps the image ID to a struct containing the file name on disk and the last modification date. Use Response.WriteFile to send the data from disk. Of course, every time you restart your worker process, the dictionary would be empty, but you're getting at least some caching benefit without having to deal with persisting caching information somewhere.
You can support this approach by separating the concerns of "Image Generation" and "Sending Images over HTTP" into different classes. Right now you're doing two very different things in the same place.
I know this may sound a little complex, but it's worth it. I just recently implemented this approach and the savings in processing time and bandwidth usage were incredible.
如果磁盘上有源文件,则可以使用以下代码:
此外,请确保使用 IIS 进行测试,而不是从 Visual Studio 进行测试。 ASP.NET 开发服务器(又名 Cassini)始终将 Cache-Control 设置为私有。
另请参阅:面向 Web 作者和网站管理员的缓存教程
If you have source file on disk you can use this code:
Also, make sure that you test using IIS, not from Visual Studio. ASP.NET Development Server (aka Cassini) always sets Cache-Control to private.
See also: Caching Tutorial for Web Authors and Webmasters
这就是 Roadkill 的(.NET wiki)文件处理程序:
Thomas 关于 IIS 不提供状态代码的回答是关键,没有它,您每次都会返回 200 秒。
浏览器只会向您发送它认为文件上次修改的日期和时间(根本没有标头),因此如果不同,您只需返回 200。您确实需要标准化文件的日期以删除毫秒并确保这是 UTC 日期。
如果有有效的修改,我会默认为 304s,但如果需要的话可以进行调整。
This is how it's done in Roadkill's (a .NET wiki) file handler:
Thomas's answer about IIS not supplying the status code is the key, without it you just get 200s back each time.
The browser will simply send you a date and time for when it thinks the file was last modified (no no header at all), so if it differs you just return a 200. You do need to normalize your file's date to remove milliseconds and ensure it's a UTC date.
I've gone for defaulting to 304s if there's a valid modified-since, but that can be tweaked if needed.
您是否有任何响应缓冲发生? 如果是这样,您可能需要在写入输出流之前设置标头。 即尝试将 Response.OutputStream.Write() 行向下移动到缓存设置行下方。
Do you have any response buffering happening? If so you might want to set the headers before you write to the output stream. i.e. try moving the
Response.OutputStream.Write()
line down to below the Cache setting lines.