Содержание
До этого момента мы создавали компоненты 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)
— при движении запроса по конвейеру в обратном направлении.
Теперь приложение будет выглядеть следующим образом:
при ошибке авторизации:
при успешной авторизации
UseExceptionHandler()
;Итого
Сегодня мы рассмотрели построение конвейера обработки запросов ASP.NET Core с учётом порядка включения компонентов в конвейер, а также разобрались с тем, как компонент middleware может обрабатывать запрос при его движении от последнего компонента к первому.