ASP.NET - HTTP Handler & HTTP Module

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

ASP.NET Handler - это процесс (или endpoint), который запускается для обработки соответствующего неким критериям входящего HTTP запроса (таким критерием является расширение файла, который запрашивается). Наиболее известным handler’ом является обработчик запросов к .aspx страницам.

В случае с пользовательскими сценариями это может быть handler, формирующий файл RSS (обрабатывающий запросы с расширением *.rss) или image server, который трансформирует изображения в зависимости от нужд (ресайзит).

ASP.NET Module - некая сборка (модуль), которая после подключения (регистрации) запускается для каждого входящего запроса и отрабатывает некую функциональность.

К примерам пользовательских модулей обычно относятся реализации логирования, сборка статистики и изменение ответа от сервера (это могут быть изменение http заголовков или response, например, для приведения его к унифицированному (единому) виду).

Реализация HTTP Handler

Прежде чем имплементировать собственный handler нужно знать о том, что handler’ы бывают 2-х видов: синхронные (IHttpHandler) и асинхронные (IHttpAsyncHandler). Оба интерфейса содержат свойство IsReusable, значение которого показывает фабрике IHttpHandlerFactory (которая отвечает за их создание) возможность переиспользования уже созданного экземпляра обработчика для нескольких запросов, тем самым исключая его повторное создание и улучшая производительность. В противном случае обработчик будет создаваться для каждого входящего запроса.

Непосредственно сама логика работы обработчика (формирование ответа от сервера на входящий запрос) реализуется в наследуемом методе void ProcessRequest(HttpContext context).

Для регистрации (подключения) нашего обработчика нам потребуется прописать в web.config следующее:

<configuration>
    <system.web>
        <httpHandlers>
            <add verb="*" path="*.ext" type="SampleHandler, SampleHandlerAssembly" />
        </httpHandlers>
    </system.web>
</configuration>

Где:

  1. verb - тип HTTP запроса;
  2. path - условие (расширение файла, запрос к которому будет обрабатываться нашим handler’ом);
  3. type - наименование обработчика (класса);

Асинхронный HTTP Handler

В отличии от синхронного обработчика, который не возвращает ответ пользователю до тех пор, пока не завершит свою работу, асинхронный обработчик по природе своей призван запустить некое длительное задание или вычисление, результата выполнения которого пользователь не собирается дожидаться.

С точки зрения производительности ASP.NET приложения асинхронные обработчики хороши тем, что после запуска задания или вычисления (например, вызов удалённого сервера) ASP.NET возвращает использованный для этого поток обработчика обратно в пул потоков IIS и может использовать его для обработки других запросов до тех пор, пока задание не будет выполнено и не получен результат. После этого выполнение асинхронного обработчика будет продолжено. Такой подход уменьшает вероятность блокировки потоков и улучшает производительность приложения, т.к. кол-во одновременно выполняемых потоков ограничено. В случае большого количества HTTP запросов к серверу, обрабатываемых синхронными HTTP Handler’ами, есть вероятность отсутствия свободных потоков для обслуживания, что приведёт к задержкам.

Реализация асинхронного обработчика аналогична синхронному за тем лишь исключением, что вместо одного метода ProcessRequest требуется реализовать два: BeginProcessRequest и EndProcessRequest.

Фабрика HTTP Handler’ов

Как я уже отмечал выше, за создание обработчиков отвечает HttpHandlerFactory, которую также можно (в случае необходимости) реализовать. Всё что для этого нужно, так это реализовать интерфейс IHttpHandlerFactory и зарегистрировать её в web.config’e:

<configuration>
    <system.webServer>
        <handlers>
            <add verb="GET,POST" path="*.sample" name="HandlerFactory" type="HandlerFactory"/>
        </handlers>
    </system.webServer>
</configuration>

HTTP Module

Модулем в ASP.NET является сборка, код которой выполняется для каждого входящего запроса к приложению. Вызов модуля осуществляется в рамках pipeline запроса, в связи с чем у него есть доступ к входным аргументам и возможность редактировать ответ от сервера. Примерами реализации могут быть функционал аутентификации, кеширование запросов и сессии.

Реализация модуля представляет из себя имплементацию интерфейса IHttpModule, а регистрация осуществляется аналогично HTTP Handler’у, посредством добавления в web.config следующего содержимого (пример регистрации для integrated mode):

<configuration>
    <system.webServer>
        <modules>
            <add name="HelloWorldModule" type="HelloWorldModule"/>
        </modules>
    </system.webServer>
</configuration>

На этапе создания экземпляра HttpApplication ASP.NET находит все зарегистрированные модули и создаёт их. После создания каждого из них вызывается метод void Init(HttpApplication context) для последующей инициализации, в коде которой (в случае integrated mode) должна быть реализована подписка событий BeginRequest и EndRequest на соответствующие методы обработки. Хочу обратить ваше внимание, что вызов метода Init осуществляется только 1 раз в рамках одного экземпляра HttpApplication, что отличается от поведения обработчиков, у которых значение поля IsReusable = false.

После инициализации все модули добавляются в массив Modules экземпляра HttpApplication.

Т.к. в процессе работы модуль имеет полный доступ к HttpContext, то к его возможностям относятся проверка входных аргументов или заголовков, изменение содержимого ответа от сервера, редирект (например, на страницу логина в случае отсутствия прав доступа) и иные манипуляции.

Это важно: HttpApplication & application pool

Многие из вас знают, что обработкой запроса в IIS занимается отдельный поток, который берётся из application pool. Но вот что интересно,- для каждого потока создаётся свой собственный экземпляр HttpApplication (к слову, метод Application_OnStart вызывается лишь для первого экземпляра), который, в свою очередь, создаёт и инициализирует собственную коллекцию HTTP Module’й, что может приводить к блокировкам, проблемам с производительностью и ресурсами, в случае недолжного отношения к их освобождению.

Резюмируя, я опишу процесс начала работы приложения:

  1. Для первого входящего HTTP запроса IIS/ASP.NET создаёт столько экземпляров HttpApplication, сколько позволяет ограничение на количество потоков в рамках приложения (от 1 до 100, в зависимости от аппаратных средств, нагрузки, конфигурации и пр);
  2. Для первого созданного экземпляра HttpApplication вызывается метод Init();
  3. Т.к. текущий экземпляр первый, то вызывается метод Application_OnStart();
  4. Затем создаются дополнительные экземпляры HttpApplication, и для каждого из них вызывается метод Init();
  5. Вызов метода Application_OnStart() не осуществляется, т.к. он уже был вызван ранее для первого созданного;
  6. Инициализация всех созданных HttpApplication посредством метода Init() вызывает создание всех зарегистрированных HTTP Module’й с дальнейшей инициализацией каждого модуля;

Ссылки по теме:

  1. How to correctly use IHttpModule to handle Application_OnStart event;
  2. HTTP Handlers and HTTP Modules.
Written on March 20, 2017