Фильтры в http4k

Фильтры в http4k #

Васильев Андрей Михайлович, 2024

Версии презентации


Фильтры в http4k #

fun interface Filter : (HttpHandler) -> HttpHandler

Фильтры позволяют выполнить действия, общие для ряда запросов от пользователя:

  • Проверить аутентификацию пользователя
  • Выполнить авторизацию действия пользователя
  • Выполнить обработку внутренних ошибок сервера
  • Выполнить обработку ошибочных запросов от пользователя
  • Выполнить журналирование запросов
  • Настроить общие политики для предоставления содержимого, установка CORS-заголовков, Content-Type-заголовков
  • Выполнить кеширование ответов на запросы

Процесс обработки запроса с фильтрами #

  • С помощью фильтров определяются действия, которые могут выполняться до и после работы HTTP-обработчика
  • В любой стадии фильтр может самостоятельно сформировать ответ пользователю
  • Фильтр может применяться к одиночному HTTP-обработчику
  • Фильтр может применяться ко всему приложению

diagram


Реализация собственного фильтра #

Интерфейс фильтра (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

  • DebuggingFilters — фильтры, помогающие в отладке работы приложения
  • ServerFilters — основные серверные фильтры
    • BasicAuth — реализация базовой HTTP-авторизации
    • CatchAll — перехватывание всех внутренних исключений, возникающих при обработке сообщений
    • Cors — установка CORS-заголовков
    • HandleRemoteRequestFailed — обработка ошибок от HTTP-запросов к внешним ресурсам
    • RequestTracing — структурированное отслеживание запросов в формате Zipkin

Добавление заголовка 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-обработчиком

© A. M. Васильев, 2024, CC BY-SA 4.0, andrey@crafted.su