来自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实现这一目标?

编辑:

根据最佳答案,我运行了此代码,它完全解决了问题。 是的,它需要一些重构,但它通常会certificate我所追求的是什么。 相关部分:

 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); 

AFAIK, 负责发送304 Not Modified,这意味着在您发送“动态”图像数据的用例中,我不知道.Net框架中的任何内容都可以为您完成。 您需要做什么(伪代码):

  • 检查请求中的If-Modified-Since标头并解析出日期(如果存在)。
  • 将其与原始图像(动态生成)图像的上次修改日期进行比较。 跟踪这可能是解决此问题的最复杂部分。 在您目前的情况下,您将在每个请求上重新创建图像; 除非绝对必要,否则你不想这样做。
  • 如果浏览器所具有的文件的日期比图像的日期更新或相等,则发送304 Not Modified。
  • 否则,继续执行当前的实施

跟踪最后修改时间的一种简单方法是在文件系统上缓存新生成的图像,并保留一个内存中的字典,将图像ID映射到包含磁盘上文件名和最后修改日期的结构。 使用Response.WriteFile从磁盘发送数据。 当然,每次重新启动工作进程时,字典都将为空,但您至少可以获得一些缓存优势,而无需在某处处理持久缓存信息。

您可以通过将“图像生成”和“通过HTTP发送图像”的问题分成不同的类来支持这种方法。 现在你在同一个地方做了两件非常不同的事情。

我知道这听起来有点复杂,但值得。 我刚刚实施了这种方法,节省的处理时间和带宽使用量令人难以置信。

如果磁盘上有源文件,则可以使用以下代码:

 context.Response.AddFileDependency(pathImageSource); context.Response.Cache.SetETagFromFileDependencies(); context.Response.Cache.SetLastModifiedFromFileDependencies(); context.Response.Cache.SetCacheability(HttpCacheability.Public); 

此外,请确保使用IIS进行测试,而不是使用Visual Studio进行测试。 ASP.NET Development Server(也称为Cassini)始终将Cache-Control设置为private。

另请参阅: Web作者和网站管理员的缓存教程

这是在Roadkill (一个.NET wiki)文件处理程序中完成的:

 FileInfo info = new FileInfo(fullPath); TimeSpan expires = TimeSpan.FromDays(28); context.Response.Cache.SetLastModifiedFromFileDependencies(); context.Response.Cache.SetETagFromFileDependencies(); context.Response.Cache.SetCacheability(HttpCacheability.Public); int status = 200; if (context.Request.Headers["If-Modified-Since"] != null) { status = 304; DateTime modifiedSinceDate = DateTime.UtcNow; if (DateTime.TryParse(context.Request.Headers["If-Modified-Since"], out modifiedSinceDate)) { modifiedSinceDate = modifiedSinceDate.ToUniversalTime(); DateTime fileDate = info.LastWriteTimeUtc; DateTime lastWriteTime = new DateTime(fileDate.Year, fileDate.Month, fileDate.Day, fileDate.Hour, fileDate.Minute, fileDate.Second, 0, DateTimeKind.Utc); if (lastWriteTime != modifiedSinceDate) status = 200; } } context.Response.StatusCode = status; 

托马斯关于IIS没有提供状态代码的答案是关键,如果没有它,你每次只能获得200秒。

浏览器只会向您发送一个日期和时间,以确定它是否认为文件上次被修改(根本没有标题),所以如果它不同,您只需返回200.您需要规范化文件的日期以删除毫秒并确保这是一个UTC日期。

如果有一个有效的修改后,我已经默认为304s,但如果需要可以调整。

你有任何缓冲响应吗? 如果是这样,您可能需要在写入输出流之前设置标头。 即尝试将Response.OutputStream.Write()行向下移动到Cache设置行下方。