2009-06-15 55 views
20

我使用IHttpHandler从数据库提供图像。相关的代码是在这里:来自HttpHandler的图片不会在浏览器中缓存

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

它看起来像浏览器应缓存5分钟。它是否缓存了这么长时间?那是你的意图吗? – 2009-06-15 00:47:38

回答

23

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

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

一个简单的方法来跟踪你的最终最后修改时间是在文件系统缓存新生成的图像,并保持在内存中的字典周围的映射图像ID到包含磁盘上的文件名和最后修改日期的结构。使用Response.WriteFile从磁盘发送数据。当然,每次你重启你的工作进程时,字典都是空的,但你至少得到了一些缓存的好处,而不必在某处处理持久化缓存信息。

可以通过分离的“图像一代”的关注和“通过HTTP发送图片”到不同的类别支持这种做法。现在你在同一个地方做两件完全不同的事情。

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

0

你有什么反应缓冲发生?如果是这样,您可能需要在写入输出流之前设置标题。即尝试将Response.OutputStream.Write()行向下移动到高速缓存设置线以下。

7

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

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

此外,请确保您测试使用的是IIS,而不是从Visual Studio。 ASP。NET开发服务器(又名卡西尼)总是将Cache-Control设置为私有。

参见:Caching Tutorial for Web Authors and Webmasters

6

这是它是如何在Roadkill's完成(一个.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,但如果需要可以调整。