Изображение из 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);
Решение
НАСКОЛЬКО МНЕ ИЗВЕСТНО, ты несут ответственность за отправку 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()
линию вниз до уровня ниже строк настройки кэша.