Подтвердить что ты не робот

Изображение из 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?

EDIT:

В лучшем ответе я получил этот код, и он полностью решает проблему. Да, он нуждается в некотором рефакторинге, но он, как правило, демонстрирует, что я был после. Соответствующие части:

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

Ответ 1

AFAIK, вы несут ответственность за отправку 304 Not Modified, то есть я ничего не знаю о структуре .NET, которая делает это для вас в этом случае, когда вы отправляете "динамические" данные изображения, Что вам нужно сделать (в псевдокоде):

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

Простым способом отслеживания последних измененных времен на вашем конце является кэширование вновь созданных изображений в файловой системе и хранение в памяти словаря, который сопоставляет идентификатор изображения с структурой, содержащей имя файла на диске, и последнюю модификацию Дата. Используйте Response.WriteFile для отправки данных с диска. Конечно, каждый раз, когда вы перезапускаете рабочий процесс, словарь будет пустым, но вы получаете хотя бы какое-то преимущество в кешировании, не имея места где-то в кэшировании.

Вы можете поддержать этот подход, разделив проблемы "Генерация изображений" и "Отправка изображений по HTTP" в разные классы. Сейчас вы делаете две разные вещи в одном и том же месте.

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

Ответ 2

Если у вас есть исходный файл на диске, вы можете использовать этот код:

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 в закрытый.

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

Ответ 3

Вот как это делается в обработчике файла 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, не поставляющий код состояния, является ключом, без него вы просто получаете 200s назад каждый раз.

Браузер просто отправит вам дату и время, когда будет считаться, что файл был последним изменен (нет никакого заголовка вообще), поэтому, если он отличается, вы просто возвращаете 200. Вам нужно нормализовать дату файла для удаления миллисекунды и обеспечить дату UTC.

Я пошел на дефолт до 304s, если у вас есть действующий файл с измененной версией, но при необходимости может быть изменен.

Ответ 4

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