Изображение из HttpHandler не будет кэшироваться в браузере - PullRequest
21 голосов
/ 15 июня 2009

Я подаю изображение из базы данных, используя 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);

Ответы [ 4 ]

23 голосов
/ 15 июня 2009

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

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

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

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

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

7 голосов
/ 15 июня 2009

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

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 значение private.

См. Также: Руководство по кэшированию для веб-авторов и веб-мастеров

6 голосов
/ 23 марта 2013

Вот как это делается в Обработчик файлов (. Wiki) Roadkill:

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, если есть действительное изменение с тех пор, но его можно настроить при необходимости.

0 голосов
/ 15 июня 2009

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

...