Microsoft OWIN и конвейер обработки запросов ASP.NET. Часть третья, детальное описание конвейера OWIN для веб-приложения не использующего IIS и ASP.NET.

В данной статье я опишу конвейер обработки запросов веб-приложения, которое построено по спецификации OWIN с использованием проекта Katana. При этом не используются IIS/ASP.NET. Спецификация OWIN была описана в данной статье, а пример реального приложения (версия на английском), построенного по этой спецификации, был показан в данной. Не использовать IIS/ASP.NET, именно в этом случае мы получаем всю мощь и гибкость, используя Katana. Но я при этом не говорю, что надо отказываться от IIS. Просто, у нас уже есть альтернативный вариант, а списывать ASP.NET и IIS со счетов ещё рано. Про то, как будет выглядеть конвейер IIS/ASP.NET при использовании OWIN, я покажу в следующей статье. Предлагаю посмотреть и познакомиться с полным описанием конвейера обработки запросов IIS и ASP.NET, который был проиллюстрирован проиллюстрирован в статье "Общее описание конвейера IIS и ASP.NET до появления спецификации OWIN", если вы пока ещё этого не сделали. Чтобы можно было наглядно представить и сравнить, какие кординальные изменения произошли. Модернизация классического конвейера IIS 6, которая была сделана при переходе на IIS 7 и выше, так называемый "интегрированный режим" (как вы знаете он используется и в IIS 7.5, IIS 8, IIS 8.5), была большим шагом вперёд. Большим шагом впрерёд в сторону ещё большего использования управляемой среды и упраляемого кода (среда CLR, платформа .NET Framework) и уменьшения использования неуправляемого, что сильно облегчает жизнь и не может не радовать. Но вот полностью отказаться от использования "старого багажа" не так то просто. Одна из основных причин – поддержка и совместимость уже имеющихся приложений. Без этого никак. Ещё больший шаг впрерёд, или даже целых два это – OWIN, и проект Katana. Это новая инфраструктура, которая целиком и полностью опирается на управляемый код. Нативного кода тут нет, ну или почти нет. Весь конвейер обработки запросов строится при помощи управляемого кода .NET и он открыт. Ниже показана схема работы конвейера (ссылка на pdf файл высокого качества).

Всё начинается с того, что не используются никакие WAS и W3SVC, хотя эти службы имеют и выполняют свою положительную роль в составе IIS. Управляемой обёрткой служит класс System.Net.HttpListener, который напрямую работает с HTTP.SYS. Класс не новый, он появился ещё со времён .NET 2.0 и широко используется, в том числе и для написания собственнх HTTP-серверов или простых прослушивателей протокола. Но это не главное, главное то, что строится целая веб-инфраструктура при помощи управляемого кода (платформа .NET Framework) по спецификации OWIN. Примером такого сервера и служит класс OwinHttpListener из проекта Katana, использующий HttpListener из библиотеки FCL и представляет собой OWIN-совместимый сервер. Непосредственно он занимается приёмом и отправкой запросов. Здесь важную роль играет метод StartProcessingRequest(HttpListenerContext context). Дальше запрос передаётся на обработку модулям. Настройка модулей происходит не путём использования XML файла конфигурации, а при помощи кода конфигурации. Реальный пример, как я уже отметил есть тут. В отличие от конвейера IIS/ASP.NET запросы обрабатываются только модулями. Нет обработчиков HTTP-данных (HttpHandler) специально предназначенных для обработки результирующих данных. Модули OWIN не имеют ничего общего с HTTP-модулями (HttpModule) IIS. Они не подписываются на события, тут нет такого понятия, а вызываются цепочкой: каждый модуль может вызвать следующий или завершить обработку запроса. На них не закладывается жёсткое ограничение в виде реализации интерфейса. Но всё же, есть некоторые соглашения, без которых модуль не будет работать. В роли модуля может выступать как класс, так и отдельный метод. Метод обычно используется если задачи модуля небольшие, например – трассировка. Если надо подключить целую платформу типа Web API или SignalR, то тут уже разумнее иметь класс. Ниже представлены примеры кода в случае метода

  using AppFunc = Func<IDictionary<string, object>, Task>;
 
 //. . . . . . . . . . . . . . . . .
 
 // Делегат в роли модуля.
 var module = new Func<AppFunc, AppFunc>(
     nextModule => enviroment =>
         {
             // Обработать запрос и отправить ответ
             {
                 // Работа по обработке запроса.
             }
 
             // или передать управление следующему модулю.
             return nextModule(enviroment);
         });

и класса.

 // Произвольный класс как модуль OWIN.
public class ArbitraryClass
{
    private readonly Func<IDictionary<string, object>, Task> nextModule;
 
    // Параметр nextModule обязательный, можно также передать любое количество
    // других параметров.
    public ArbitraryClass(Func<IDictionary<string, object>, Task> nextModule, 
        params object[] args)
    {
        Contract.Requires<ArgumentNullException>(nextModule != null);
 
        this.nextModule = nextModule;
    }
 
    // Обработать запрос. Имя – Invoke и сигнатура обязательны. 
    public Task Invoke(IDictionary<string, object> enviroment)
    {
        // Обработать запрос и отправить ответ
        {
            // Работа по обработке запроса.
        }
 
        // или передать управление следующему модулю.
        return this.nextModule(enviroment);
    }
}
 

Как видно из вышеописанного, всё достаточно просто и открыто. Нет нативных модулей, отдельных обработчиков и прочего. То есть ковейер не так сложно устроен, как здесь. И это – здорово.