Порядок построения конвейера обработки запроса в ASP.NET Core

До этого момента мы создавали компоненты middleware, не заостряя внимание на важном моменте, а именно на том, что порядок, в котором компоненты middleware добавляются в файл Program.cs, определяет порядок их вызова при запросах и соблюдать этот порядок крайне важно для обеспечения безопасности, производительности и функциональности. Сегодня рассмотрим построение конвейера запросов с учётом порядка компонентов middleware в конвейере.

Стоит напомнить, что каждый компонент middleware может обрабатывать запрос до и после последующих в конвейере компонентов. Отсюда и проистекает важность установления порядка компонентов middleware в конвейер обработки запроса ASP.NET Core. Один из примеров того, когда порядок играет важную роль — авторизация пользователя. Рассмотрим простой пример.

Простейшая проверка авторизации пользователя в ASP.NET Core

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

Вначале, напишем класс middleware для проверки токена.

public class AuthorizationMiddleware
{
    private RequestDelegate _next;

    public AuthorizationMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var token = context.Request.Query["token"];
        if (string.IsNullOrWhiteSpace(token))
            context.Response.StatusCode = 401;
        else
        {
            //псевдо-проверка правильности токена
            //если передается 123, то считаем, что токен устарел - запрещаем доступ
            if (token == "123")
                context.Response.StatusCode = 419;//419 - Authentication Timeout
            else
               await _next(context);//все остальные токену считаем "хорошими" - передаем управление следующему компоненту
        }
    }
}

Здесь, если параметр token не передан в запросе, то устанавливается код статуса 401 Unauthorized. Если передается значение 123, то считаем такой токен устаревшим и устанавливаем код статуса 419 Authentication Timeout. Все прочие значения токена считаем действительными и передаем управление следующему компоненту middleware в конвейере обработки запроса.

Второй компонент middleware будет возвращать пользователю строку, в зависимости от того, какой путь будет задан:

public class RouterMiddleware
{
    public RouterMiddleware(RequestDelegate _)
    {

    }   

    public async Task InvokeAsync(HttpContext context) 
    {
        var path = context.Request.Path;    
        if (path == "/") 
        {
            await context.Response.WriteAsync("Index Page");
        }
        else 
        {
            if (path == "/admin")
            {
                await context.Response.WriteAsync("Admin Panel");
            }
            else
            {
                context.Response.StatusCode = 404;
            }
        }
    }
}

Этот middleware является терминальным так как не передает управление следующему компоненту. Если пользователь задает путь отличный от /admin и / , то код статуса устанавливается как 404 Not Found.

Очевидно, что, если пользователь не авторизован, то выполнение всех прочих middleware бесполезно. Следовательно в конвейер обработки запроса мы включим наши компоненты следующим образом:

public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();

    app.UseMiddleware<AuthorizationMiddleware>();
    app.UseMiddleware<RouterMiddleware>();

    app.Run();
}

Запустим приложение и посмотрим на результат:

сразу после запуска приложения в URL отсутствуют какие-либо параметры:

вводим «устаревший» токен

вводим «живой» токен и загружаем страницу admin

Демонстрация прохождения запроса от последнего middleware к первому

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

public class ErrorMiddleware
 {
     private RequestDelegate _next;

     public ErrorMiddleware(RequestDelegate next) 
     {
         _next = next;
     }

     public async Task InvokeAsync(HttpContext context) 
     {
         await _next(context); //сначала пропускаем запрос в следующий middleware
         switch (context.Response.StatusCode) 
         {
             case 401: 
                 {
                     await context.Response.WriteAsync("Unauthorized");
                     break; 
                 }
             case 419: 
                 {
                     await context.Response.WriteAsync("Authentication Timeout");
                     break; 
                 }
             default: 
                 {
                     await context.Response.WriteAsync($"\r\n Token {context.Request.Query["token"]} is valid");
                     break; 
                 }
         }
     }
 }

Здесь стоит обратить внимание на то, как организован метод InvokeAsync — вначале мы передаем запрос на обработку следующему в конвейере компоненту:

await _next(context);

таким образом, запрос пройдет сразу в AuthorizationMiddleware, а из этого компонента либо пройдет дальше в RouterMiddleware, либо, если пользователь не будет авторизован вернется снова в ErrorMiddleware и будет обработан частою кода которая располагается ниже вызова await _next(context).

Таким образом организуется обработка запроса в двух направлениях: весь код перед await _next(context) выполняется при движении запроса по конвейеру в прямом направлении, а весь код который стоит после await _next(context) — при движении запроса по конвейеру в обратном направлении.

Теперь приложение будет выглядеть следующим образом:

при ошибке авторизации:

при успешной авторизации

Кстати, в ASP.NET Core уже имеется middleware для обработки исключений и перенаправлении, при необходимости, пользователя на заданную страницу с ошибкой. Этот компонент должен включаться в конвейер обработки запроса первым с использованием метода расширения UseExceptionHandler();

Итого

Сегодня мы рассмотрели построение конвейера обработки запросов ASP.NET Core с учётом порядка включения компонентов в конвейер, а также разобрались с тем, как компонент middleware может обрабатывать запрос при его движении от последнего компонента к первому.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии