Изображение из HttpHandler не будет кэшироваться в браузере

StackOverflow https://stackoverflow.com/questions/994135

  •  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);
Это было полезно?

Решение

НАСКОЛЬКО МНЕ ИЗВЕСТНО, ты несут ответственность за отправку 304 Not Modified, что означает, что я не знаю ничего в платформе .Net, которая делает это за вас в этом случае использования, когда вы отправляете «динамические» данные изображения.Что вам нужно будет сделать (в псевдокоде):

  • Проверьте заголовок If-Modified-Since в запросе и проанализируйте дату (если она существует).
  • Сравните ее с датой последней модификации исходного изображения (динамически созданного).Отслеживание этого, пожалуй, самая сложная часть решения этой проблемы.В вашей текущей ситуации вы воссоздаете изображение при каждом запросе;ты не хочу сделать это, если только вам это не абсолютно необходимо.
  • Если дата файла в браузере новее или равна дате изображения, отправьте 304 Not Modified.
  • В противном случае продолжите текущую реализацию.

Простой способ отслеживать время последнего изменения на вашей стороне — кэшировать вновь созданные изображения в файловой системе и хранить в памяти словарь, который сопоставляет идентификатор изображения со структурой, содержащей имя файла на диске и дату последней модификации.Используйте 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 (он же Cassini) всегда устанавливает для Cache-Control частный статус.

Смотрите также: Учебное пособие по кэшированию для веб-авторов и веб-мастеров

Вот как это делается в 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.

Я выбрал значение по умолчанию 304, если есть действительные изменения, но при необходимости это можно изменить.

Происходит ли у вас какая-либо буферизация ответов?В этом случае вы можете установить заголовки перед записью в выходной поток.то естьпопробуйте переместить Response.OutputStream.Write() линию вниз до уровня ниже строк настройки кэша.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top