Кэширование изображений в ASP.NET
ОГЛАВЛЕНИЕ
Одним из простейших, но самых эффективных методов повышения производительности веб-приложений является кэширование изображений в клиенте.
• Скачать CachingHandler - 76 KB
Введение
Есть много способов повышения производительности веб-приложений. Одним из простейших, но самых эффективных методов является кэширование изображений в клиенте. В данной статье показано, как было реализовано кэширование изображений для сайта DotNetNuke.
Проблема
При создании сайта http://www.software-architects.com/ использовалось много изображений в стилях css для отображения фоновых изображений для элементов меню. После переноса файлов на веб-сервер было проверено с помощью сетевого монитора Microsoft, сколько трафика генерирует запрос к начальной странице. Это инструмент для сбора и анализа протоколов сетевого трафика. Его можно скачать из центра загрузок Microsoft.
С помощью сетевого монитора Microsoft 3.1 был записан вызов к http://www.software-architects.com/. В результате было получено 20 запросов к 20 разным файлам для отображения одной страницы. Сетевой монитор Microsoft показывает, что около половины запросов требуются для изображений меню.
Есть два разных способа устранения этой проблемы. С одной стороны, можно приказать IIS кэшировать изображения в клиенте, и, с другой стороны, это можно делать прямо в ASP.net (что чуть сложней).
Кэширование изображений в IIS
Кэширование в IIS очень простое. Выберите папку в левой панели или один файл в правой панели и откройте диалоговое окно свойств.
Отметьте "Включить истечение срока действия содержимого" и выберите, когда срок действия вашего содержимого должен истечь.
Все! IIS сообщает клиенту с помощью заголовка "Cache-Control(управление КЭШем)", что содержимое может кэшироваться в клиенте. Заголовок "Expires(истекает)" содержит дату истечения срока действия. Клиент знает, что после этой даты он должен запросить у сервера новое содержимое.
Данный подход работает очень хорошо, если
• вы можете поместить все изображения и другие кэшируемые файлы в одну или несколько папок,
• и, главное, у вас есть доступ к IIS.
В нашем случае не выполняются оба условия. В проекте DotNetNuke изображения разбросаны по множеству папок, поэтому настроить IIS было бы сложно. И главное, поставщик услуг хостинга не предоставляет доступ к IIS. Поэтому придется искать другое решение.
Кэширование изображений с помощью специального обработчика HttpHandler
Первой задачей, требующей решения, был обход IIS для получения запроса к ASP.NET. Было решено написать специальный обработчик http, слушающий файлы с путями *.gif.ashx, *.jpg.ashx и *.png.ashx. Хорошую статью об IHttpHandler читайте на сайте APress: Используйте локальную область видимости для повышения производительности.
Был создан новый проект библиотеки классов в Visual Studio с классом CachingHandler, отвечающим за обработку запросов к изображениям. CachingHandler реализует интерфейс IHttpHandler, как делает класс Page(страница). Интерфейс предоставляет свойство IsReusable и метод ProcessRequest.
IsResuable показывает, может ли другой запрос повторно использовать обработчик http. То есть, надо гарантировать потокобезопасность метода ProcessRequest.
ProcessRequest делает реальную работу. Он получает текущий контекст и отправляет результат клиенту.
namespace SoftwareArchitects.Web
{
public class CachingHandler : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
...
}
}
}
Обработчик http должен отправить файл клиенту. При слушании файлов с путями *.gif.ashx, *.jpg.ashx и *.png.ashx надо удалить ".ashx" из пути запроса, чтобы получить файл, который надо отправить клиенту. Кроме того, надо извлечь имя файла и расширение из файла.
public void ProcessRequest(HttpContext context)
{
string file = context.Server.MapPath
(context.Request.FilePath.Replace(".ashx", ""));
string filename = file.Substring(file.LastIndexOf('\\') + 1);
string extension = file.Substring(file.LastIndexOf('.') + 1);
Далее загружается конфигурация для CachingHandler из файла web.config. Поэтому был создан класс CachingSection (показан позже), содержащий свойство CachingTimeSpan(интервал времени кэширования) и коллекцию FileExtensions(расширения файлов), знающую тип содержимого для каждого расширения файла. С помощью класса config конфигурируется объект ответа HttpCachePolicy(правила кэширования http):
• SetExpires говорит клиенту, когда истекает срок действия содержимого.
• SetCacheability говорит клиенту, кому разрешено кэшировать содержимое. Кэшируемость задается публичная. Отсюда следует, что ответ кэшируется клиентами и общими кэшами (кэш-посредниками).
• SetValidUnitExpires задает, должен ли кэш ASP.NET игнорировать заголовки HTTP Cache-Control (управление кэшем), отправляемые клиентом, аннулирующие кэш.
• ContentType устанавливает MIME-тип ответа.
CachingSection config = (CachingSection)context.GetSection
("SoftwareArchitects/Caching");
if (config != null)
{
context.Response.Cache.SetExpires
(DateTime.Now.Add(config.CachingTimeSpan));
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetValidUntilExpires(false);
FileExtension fileExtension = config.FileExtensions[extension];
if (fileExtension != null)
{
context.Response.ContentType = fileExtension.ContentType;
}
}
Наконец, в ответ добавляется заголовок content-disposition(расположение содержимого), сообщающий клиенту, что он должен открыть файл в браузере (встроенном). Кроме того, в качестве имени файла задается имя без расширения .ashx, потому что это имя будет отображаться при скачивании файла. Затем WriteFile используется для отправки файла клиенту.
context.Response.AddHeader("content-disposition",
"inline; filename=" + filename);
context.Response.WriteFile(file);
}
Определение специальных секций конфигурации в web.config
В обработчике http использовался специальный класс для считывания конфигурационных данных из файла web.config. Поэтому был создан класс CachingSection, производный от ConfigurationSection. В этом классе реализовано свойство CachingTimeSpan, хранящее значение TimeSpan для времени кэширования объектов в клиенте и свойство FileExtensions, хранящее коллекцию объектов FileExtension. Чтобы сопоставить эти свойства с элементами в web.config, надо добавить к каждому свойству атрибут ConfigurationProperty, устанавливаемый в web.config.
namespace SoftwareArchitects.Web.Configuration
{
/// <summary>
/// Конфигурация для кэширования
/// </summary>
public class CachingSection : ConfigurationSection
{
[ConfigurationProperty("CachingTimeSpan", IsRequired = true)]
public TimeSpan CachingTimeSpan
{
get { return (TimeSpan)base["CachingTimeSpan"]; }
set { base["CachingTimeSpan"] = value; }
}
[ConfigurationProperty("FileExtensions", IsDefaultCollection = true,
IsRequired = true)]
public FileExtensionCollection FileExtensions
{
get { return ((FileExtensionCollection)base["FileExtensions"]); }
}
}
Чтобы поддерживать не только отдельные значения, но и коллекции, надо реализовать класс, производный от ConfigurationElementCollection. В нашем примере нужна коллекция для настройки списка правильных расширений с соответствующими им типами содержимого.
Можно скачать полный код для файла CachingSection.cs.
/// <summary>
/// Список доступных расширений файлов
/// </summary>
public class FileExtensionCollection : ConfigurationElementCollection
{
...
}
Для каждого расширения нужен класс, хранящий свойство для расширения и свойство для типа содержимого.
/// <summary>
/// Конфигурация для расширения файла
/// </summary>
public class FileExtension : ConfigurationElement
{
[ConfigurationProperty("Extension", IsRequired = true)]
public string Extension
{
get { return (string)base["Extension"]; }
set { base["Extension"] = value.Replace(".", ""); }
}
[ConfigurationProperty("ContentType", IsRequired = true)]
public string ContentType
{
get { return (string)base["ContentType"]; }
set { base["ContentType"] = value; }
}
}
}
Теперь надо добавить раздел конфигурации в web.config. В теге configSections добавляется новый sectionGroup с именем SoftwareArchitects. В эту группу добавляется раздел по имени Caching. Тип атрибута задает класс и сборку класса CachingSection. Конечно, надо добавить сборку с классом CachingSection в папку bin веб-приложения. Затем можно добавить новый тег с именем группы в тег конфигурации. Внутрь группы добавляется новый тег с именем раздела, и в этом разделе теперь доступны все свойства, определенные в классе CachingSection.
<configuration>
<configSections>
<sectionGroup name="SoftwareArchitects">
<section name="Caching" requirePermission="false"
type="SoftwareArchitects.Web.Configuration.CachingSection,
SoftwareArchitects.Web.CachingHandler" />
</sectionGroup>
</configSections>
<SoftwareArchitects>
<Caching CachingTimeSpan="1">
<FileExtensions>
<add Extension="gif" ContentType="image\gif" />
<add Extension="jpg" ContentType="image\jpeg" />
<add Extension="png" ContentType="image\png" />
</FileExtensions>
</Caching>
</SoftwareArchitects>
...
Прежде чем можно будет использовать CachingHandler, надо добавить его в раздел httpHandlers в web.config. Туда надо добавить запись для каждого расширения файла, которое надо сопоставить с обработчиком http. Было решено поддерживать изображения с расширениями .gif, .jpg и .png. Поэтому был добавлен обработчик для путей *.gif.ashx, *.jpg.ashx и *.png.ashx. В атрибуте типа был указан класс и сборка обработчика http. Конечно, сборку также надо поместить в папку bin.
<httpHandlers>
<add verb="*" path="*.gif.ashx"
type="SoftwareArchitects.Web.CachingHandler,
SoftwareArchitects.Web.CachingHandler"/>
<add verb="*" path="*.jpg.ashx"
type="SoftwareArchitects.Web.CachingHandler,
SoftwareArchitects.Web.CachingHandler"/>
<add verb="*" path="*.png.ashx"
type="SoftwareArchitects.Web.CachingHandler,
SoftwareArchitects.Web.CachingHandler"/>
</httpHandlers>
</configuration>
Также можно использовать другие расширения файлов, например *.gifx. Но для этого надо иметь доступ к IIS, чтобы настроить обработку новых расширений с помощью aspnet_isapi.dll. Так как поставщик услуг хостинга не давал доступ к IIS, пришлось использовать *.ashx, потому что он уже сопоставлен с aspnet_isapi.dll.
Наконец, было добавлено расширение .ashx ко всем изображениям на сайте (в файлах .css и в файлах .aspx). При повторной проверке запроса к главной странице сайта первый запрос по-прежнему генерировал 20 запросов к веб-серверу, но второй запрос потребовал лишь 7 запросов к серверу для загрузки страницы, так как изображения были закэшированы в клиенте.
Его работу можно увидеть на сайте http://www.software-architects.at/TechnicalArticles/CachinginASPNET/tabid/75/language/de-AT/Default.aspx. Щелкните правой кнопкой мыши по изображению и откройте диалоговое окно свойств. Вы увидите, что URL заканчивается на .ashx. Когда вы щелкаете правой кнопкой мыши по изображению и выбираете "Сохранить изображение как...", предложенное имя файла не содержит расширение .ashx из-за заголовка content-disposition.
Разумеется, можно использовать обработчик для других типов файлов, таких как файлы javascript или файлы css. Следовательно, вновь сократится число запросов.