Изображение из 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);
Ответы
Ответ 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()
вниз ниже линий настройки кэша.