来自 HttpHandler 的图像不会在浏览器中缓存

发布于 2024-07-25 03:41:13 字数 1767 浏览 2 评论 0原文

我正在使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

半窗疏影 2024-08-01 03:41:13

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

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

跟踪最后修改时间的一个简单方法是在文件系统上缓存新生成的图像,并在内存中保留一个字典,将图像 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):

  • Check for the If-Modified-Since header in the request and parse out the date (if it exists).
  • Compare it to the last modification date of your original image (dynamically generated) image. Tracking this is probably the most complex part of the solution to this problem. In your current situation, you are re-creating the image on every request; you don't want to do that unless you absolutely have to.
  • If the date of the file the browser has is newer or equal to what you have for the image, send a 304 Not Modified.
  • Otherwise, continue with your current implementation

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.

绾颜 2024-08-01 03:41:13

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

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

此外,请确保使用 IIS 进行测试,而不是从 Visual Studio 进行测试。 ASP.NET 开发服务器(又名 Cassini)始终将 Cache-Control 设置为私有。

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

If you have source file on disk you can use this code:

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

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

南薇 2024-08-01 03:41:13

这就是 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;

Thomas 关于 IIS 不提供状态代码的回答是关键,没有它,您每次都会返回 200 秒。

浏览器只会向您发送它认为文件上次修改的日期和时间(根本没有标头),因此如果不同,您只需返回 200。您确实需要标准化文件的日期以删除毫秒并确保这是 UTC 日期。

如果有有效的修改,我会默认为 304s,但如果需要的话可以进行调整。

This is how it's done in Roadkill's (a .NET wiki) file handler:

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'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.

草莓酥 2024-08-01 03:41:13

您是否有任何响应缓冲发生? 如果是这样,您可能需要在写入输出流之前设置标头。 即尝试将 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文