来自 HttpHandler 的图像不会缓存在浏览器中
-
13-09-2019 - |
题
我正在使用 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);
解决方案
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开发服务器(又称卡西尼)总是设置缓存控制私人。
这就是它的完成方式 路杀者 (.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;
Thomas 关于 IIS 不提供状态代码的回答是关键,没有它你每次只会得到 200 秒的时间。
浏览器只会向您发送它认为文件上次修改的日期和时间(根本没有标题),因此如果不同,您只需返回 200。您确实需要标准化文件的日期以删除毫秒并确保它是 UTC 日期。
如果有有效的修改,我会默认为 304s,但如果需要的话可以进行调整。
你有任何响应缓冲发生了什么?如果是这样,你可能想你写入到输出流之前设置的标头。即尝试向下移动Response.OutputStream.Write()
线到下面的缓存设定线。