Фильтры в http4k
#
Васильев Андрей Михайлович, 2024
Версии презентации
Фильтры в http4k
#
fun interface Filter : (HttpHandler) -> HttpHandler
Фильтры позволяют выполнить действия, общие для ряда запросов от пользователя:
- Проверить аутентификацию пользователя
- Выполнить авторизацию действия пользователя
- Выполнить обработку внутренних ошибок сервера
- Выполнить обработку ошибочных запросов от пользователя
- Выполнить журналирование запросов
- Настроить общие политики для предоставления содержимого, установка
CORS-заголовков, Content-Type-заголовков
- Выполнить кеширование ответов на запросы
Процесс обработки запроса с фильтрами
#
- С помощью фильтров определяются действия, которые могут выполняться до и
после работы HTTP-обработчика
- В любой стадии фильтр может самостоятельно сформировать ответ пользователю
- Фильтр может применяться к одиночному HTTP-обработчику
- Фильтр может применяться ко всему приложению
Реализация собственного фильтра
#
Интерфейс фильтра (Filter) принимает в качестве аргумента объект HttpHandler и
должен вернуть объект HttpHandler: interface Filter : (HttpHandler) -> HttpHandler
val appHandler: HttpHandler = ... // HTTP-обработчик, который надо обернуть
val timingFilter = Filter { // Создаём фильтр
next: HttpHandler -> { // HTTP-обработчик, который оборачиваем
request: Request -> // Код нового HTTP-обработчика
val start = System.currentTimeMillis() // Код до
val response = next(request) // Вызов обработчика
val latency = System.currentTimeMillis() - start // Код после
println("Задержка составила $latency мс")
response // Возврат ответа клиенту
}
}
// Создаём цепочку из стандартного фильтра
val latencyAndBasicAuth: Filter = ServerFilters.BasicAuth(
"Моё окружение", "user", "password")
.then(timingFilter) // А после добавляем наш фильтр
val app: HttpHandler = latencyAndBasicAuth.then(appHandler)
Фильтр в примере выполняет измерение скорости выполнения кода HTTP-обработчиков
Реализация фильтров с помощью классов
#
При реализации функциональных интерфейсов с помощью классов потребуется создать два класса:
- Один необходимо унаследовать от интерфейса
org.http4k.core.Filter
- Второй класс будет реализовывать интерфейс HttpHandler, его конструктор будет
принимать ссылку на оборачиваемый HTTP-обработчик
class CounterFilter : Filter {
override fun invoke(next: HttpHandler): HttpHandler =
CounterHandler(next)
}
class CounterHandler(private val next: HttpHandler) : HttpHandler {
override fun invoke(request: Request): Response {
val start = System.currentTimeMillis()
val response = next(request)
val latency = System.currentTimeMillis() - start
println("I took $latency ms")
return response
}
}
Стандартные фильтры http4k
#
Пакет org.http4k.filter содержит описание фильтров, поставляемых с помощью http4k
Добавление заголовка content-type
#
Если в каждом обработчике HTTP-запроса в качестве ответа высылается
JSON-документ, то в каждом ответе необходимо установить заголовок в ответе
Можно уменьшить дублирование, перенеся данную логику в фильтр
val jsonContentTypeFilter = Filter { next: HttpHandler ->
{ request ->
val response = next(request)
if (response.body.hasContentToRead()) { // Если есть содержимое
response.contentType(ContentType.APPLICATION_JSON)
} else {
response
}
}
}
- При наличии содержимого в ответе добавляется заголовок с указанием типа данных
- Данный фильтр можно применить ко всему приложению
Обработка ошибок линз
#
При работе с приложением пользователь может передать некорректные данные в
качестве параметров запроса или в его теле
Если обработка ситуации должна быть единой для всех обработчиков HTTP-запроса,
то логично делегировать эту задачу единому фильтру
При получении данных с помощью линзы в случае ошибки будет выброшено исключение
LensFailure, обработку которого можно делегировать фильтру
fun lensFailureFilter(): Filter =
Filter { next: HttpHandler ->
{ request: Request ->
try {
next(request)
} catch (lensFailure: LensFailure) {
Response(Status.BAD_REQUEST)
.body("{\"error\":\"${lensFailure.message}\"}")
}
}
}
Теперь в HTTP-обработчиках можно применять линзу для извлечения данных, в случае
возникновения ошибки будет выброшено исключение
Цепочка из фильтров
#
Фильтры поддерживают выстраивание их в цепочку, для связывания фильтров
используется функция then
Рассмотрим следующую функцию по созданию именованных фильтров
fun namedFilter(name: String): Filter = Filter { next: HttpHandler -> {
println(name)
next(it)
}
}
Свяжем данные фильтры с HTTP-обработчиком и обратимся к нему:
namedFilter("первый").then(namedFilter("второй")).then(pingHandler())
Вывод приложения:
Функция then
также связывает фильтры с HTTP-обработчиком