Обработка запросов
Эта страница переведена с английского автоматически. Читать оригинал
Обработчики запросов, также известные как «middleware», — это функции, которые выполняются до или после выполнения запроса роутером. Их можно определить как для отдельного маршрута, так и для всего роутера.
Существует два типа обработчиков запросов:
- BeforeResponse: определяет, что обработчик будет выполнен до вызова действия роутера.
- AfterResponse: определяет, что обработчик будет выполнен после вызова действия роутера. Отправка HTTP‑ответа в этом контексте перезапишет ответ, сформированный действием роутера.
Оба типа обработчиков могут переопределять фактический ответ функции обратного вызова роутера. Кстати, обработчики запросов могут быть полезны для проверки запроса, например аутентификации, содержимого или любой другой информации, такой как сохранение данных, журналирование или другие шаги, которые можно выполнить до или после ответа.

Таким образом, обработчик запроса может прервать всё выполнение и вернуть ответ до завершения цикла, отбрасывая всё остальное в процессе.
Пример: предположим, что обработчик аутентификации пользователя не аутентифицирует его. Он предотвратит продолжение жизненного цикла запроса и «зависнет». Если это происходит в обработчике на второй позиции, третий и последующие обработчики не будут оцениваться.

Создание обработчика запросов #
Чтобы создать обработчик запросов, можно создать класс, наследующий интерфейс IRequestHandler, в следующем виде:
public class AuthenticateUserRequestHandler : IRequestHandler
{
public RequestHandlerExecutionMode ExecutionMode { get; init; } = RequestHandlerExecutionMode.BeforeResponse;
public HttpResponse? Execute(HttpRequest request, HttpContext context)
{
if (request.Headers.Authorization != null)
{
// Возврат null указывает, что цикл запроса может продолжаться
return null;
}
else
{
// Возврат объекта HttpResponse указывает, что этот ответ перезапишет соседние ответы.
return new HttpResponse(System.Net.HttpStatusCode.Unauthorized);
}
}
}В приведённом выше примере мы указали, что если заголовок Authorization присутствует в запросе, цикл должен продолжиться и будет вызван следующий обработчик запроса или обратный вызов роутера, в зависимости от того, что идёт дальше. Если обработчик выполняется после ответа (см. свойство ExecutionMode) и возвращает ненулевое значение, он перезапишет ответ роутера.
Когда обработчик запроса возвращает null, это означает, что запрос должен продолжаться и должен быть вызван следующий объект, либо цикл завершится ответом роутера.
Предупреждение
Экземпляр обработчика запроса создаётся один раз и используется всеми запросами, которые он обрабатывает, включая параллельные запросы. Никогда не храните состояние отдельного запроса в самом классе обработчика (поля или свойства, такие как текущий пользователь, разобранный токен или таймер), потому что это значение будет «просачиваться» в другие запросы. Храните значения, относящиеся к конкретному запросу, в RequestBag объекта HttpContext:
public HttpResponse? Execute(HttpRequest request, HttpContext context)
{
// неправильно: this.currentUser = FindUser(request);
context.RequestBag.Set(FindUser(request));
return null;
}Если вы наследуете встроенный класс RequestHandler, можете вернуть Next(), чтобы явно указать это намерение:
public class AuthenticateUserRequestHandler : RequestHandler
{
public override HttpResponse? Execute(HttpRequest request, HttpContext context)
{
if (request.Headers.Authorization is not null)
return Next();
return new HttpResponse(System.Net.HttpStatusCode.Unauthorized);
}
}Для обработчиков, которым требуется ввод‑вывод, наследуйтесь от AsyncRequestHandler:
public class LoadUserRequestHandler : AsyncRequestHandler
{
public override async Task<HttpResponse?> ExecuteAsync(HttpRequest request, HttpContext context)
{
var user = await UserRepository.FindAsync(request.Headers.Authorization, request.DisconnectToken);
if (user is null)
return new HttpResponse(System.Net.HttpStatusCode.Unauthorized);
request.Bag.Set(user);
return Next();
}
}Небольшие встроенные обработчики также можно создать с помощью RequestHandler.Create или AsyncRequestHandler.Create:
var requireJson = RequestHandler.Create((request, context) =>
{
if (request.Headers.ContentType?.Contains("application/json") == true)
return null;
return new HttpResponse(System.Net.HttpStatusCode.UnsupportedMediaType);
});Привязка обработчика запроса к отдельному маршруту #
Можно определить один или несколько обработчиков запросов для маршрута.
mainRouter.Map(RouteMethod.Get, "/", IndexPage, new IRequestHandler[]
{
new AuthenticateUserRequestHandler(), // before request handler
new ValidateJsonContentRequestHandler(), // before request handler
// -- method IndexPage will be executed here
new WriteToLogRequestHandler() // after request handler
});Или создать объект Route:
Route indexRoute = Route.Get("/", IndexPage);
indexRoute.RequestHandlers = new IRequestHandler[]
{
new AuthenticateUserRequestHandler()
};
mainRouter.Map(indexRoute);Привязка обработчика запроса к роутеру #
Можно определить глобальный обработчик запросов, который будет выполнен на всех маршрутах роутера.
mainRouter.GlobalRequestHandlers = new IRequestHandler[]
{
new AuthenticateUserRequestHandler()
};Привязка обработчика запроса к атрибуту #
Можно задать обработчик запроса в атрибуте метода вместе с атрибутом маршрута.
public class MyController
{
[RouteGet("/")]
[RequestHandler<AuthenticateUserRequestHandler>]
static HttpResponse Index(HttpRequest request)
{
return new HttpResponse() {
Content = new StringContent("Hello world!")
};
}
}Обратите внимание, что необходимо передавать тип требуемого обработчика, а не экземпляр объекта. Таким образом, обработчик будет создан парсером роутера. Аргументы можно передать в конструктор класса через свойство ConstructorArguments.
Пример:
[RequestHandler<AuthenticateUserRequestHandler>("arg1", 123, ...)]
public HttpResponse Index(HttpRequest request)
{
return res = new HttpResponse() {
Content = new StringContent("Hello world!")
};
}Можно также создать собственный атрибут, реализующий RequestHandler:
public class AuthenticateAttribute : RequestHandlerAttribute
{
public AuthenticateAttribute() : base(typeof(AuthenticateUserRequestHandler), ConstructorArguments = new object?[] { "arg1", 123, ... })
{
;
}
}И использовать его так:
[Authenticate]
static HttpResponse Index(HttpRequest request)
{
return res = new HttpResponse() {
Content = new StringContent("Hello world!")
};
}Обход глобального обработчика запросов #
После определения глобального обработчика запросов на маршруте, можно игнорировать этот обработчик на отдельных маршрутах.
var myRequestHandler = new AuthenticateUserRequestHandler();
mainRouter.GlobalRequestHandlers = new IRequestHandler[]
{
myRequestHandler
};
Route publicRoute = Route.Get("/", IndexPage);
publicRoute.Name = "My route";
publicRoute.BypassGlobalRequestHandlers = new IRequestHandler[]
{
myRequestHandler, // ok: тот же экземпляр, что и в глобальных обработчиках
new AuthenticateUserRequestHandler() // неправильно: не пропустит глобальный обработчик
};
mainRouter.Map(publicRoute);Примечание
Если вы обходите обработчик запросов, необходимо использовать ту же ссылку на объект, который был создан ранее, чтобы пропустить его. Создание нового экземпляра обработчика не позволит обойти глобальный обработчик, так как ссылка изменится. Помните, что следует использовать одну и ту же ссылку на обработчик как в GlobalRequestHandlers, так и в BypassGlobalRequestHandlers.