Маршрутизация HTTP-запросов #
Васильев Андрей Михайлович, 2024
Версии презентации
Описание Uri в http4k #
Библиотека предоставляет класс org.http4k.core.Uri, с помощью которого описываются пути в запросах и ответах
Объекты класса можно создать с помощью:
- Конструктора, передав туда все части URI
- Функции из объекта-компаньона
of
, которой передаётся строка
В обработчиках HTTP-запросов URI находится в свойстве uri
объекта-запроса
С помощью Uri-объекта можно получить доступ к следующим частям URI:
- Путь запроса в свойстве
path
- Параметры запроса в свойстве
query
- Схема доступа в свойстве
scheme
- Адрес сервера в свойстве
host
Маршрутизация запросов #
Всю логику по обработке всех возможных запросов можно реализовать в рамках одного обработчика HTTP-запросов, функциональном типе HttpHandler, но
- Его будет чрезвычайно сложно тестировать
- Дорабатывать его функциональность будет очень сложно
- Разрабатывать совместно практически невозможно
Решение проблемы — разделение обработки HTTP-запросов между разными обработчиками, выделение слоя маршрутизации
Выделение маршрутизаторов #
- Маршрутизатор является обработчиком HTTP-запросов
- HTTP-обработчик привязывается к конкретному маршруту
- Обычным подходом к разделению зоны ответственности является деление маршрута
по делителю
/
- Подобный маршрутизатор можно написать самостоятельно (обработать request.uri) или воспользоваться подсистемой используемого инструмента
Инфиксные функции в Kotlin #
Функция может быть помечена ключевым словом infix
, если
- Функция является членом другой функции или расширением
- Функция принимает только один параметр
- Параметр не описывает произвольное количество агументов
- Параметр не должен иметь значения по умолчанию
Описание инфиксной функции и её использование
infix fun Int.shl(x: Int): Int { /*...*/ }
1 shl 2
1.shl(2)
Обязательно указывать как получателя, так и параметр
Описание маршрутизатора средствами http4k #
Функция routes
позволяет связать HTTP-обработчики с маршрутами
val handlerOne: HttpHandler = { Response(OK).body("First response") }
val handlerTwo: HttpHandler = { Response(OK).body("Second response") }
val app = routes(
"first" bind GET to handlerOne,
"second".bind(GET).to(handlerTwo),
)
handlerOne
иhandlerTwo
являются обработчиками, HttpHandler- Конфигурация обрабатывает пути
/first
и/second
- В переменную
app
был помещён HTTP-обработчик, его типRoutingHttpHandler
- Обработчик вернёт ответ с кодом 404 и пустым телом, если для переданного ему пути нет связи с конкретным вложенным HTTP-обработчиком
- Структура обрабатываемых маршрутов доступна через свойство
description
Описание связи маршрута и обработчика #
routes(
"bob" bind GET to { Response(OK).body("you GET bob") },
"rita" bind POST to { Response(OK).body("you POST rita") },
"sue" bind DELETE to { Response(OK).body("you DELETE sue") },
)
- Описание маршрута с помощью шаблонной строки
- Вызов инфиксной функции
bind
связывает строку с методом по его обработке, возвращая объект типаPathMethod
- Указание названия HTTP-метода, который надо обработать
- Инфиксная функция
PathMethod.to
связывает результат работы предыдущего метода с обработчиками запроса, порождая объект типа RoutingHttpHandler - Передача обработчика HttpHandler в качестве обработчика маршрута
Сформированный список объектов RoutingHttpHandler передаётся на вход функции routes
, которая сама возвращает обработчик RoutingHttpHandler
Поддерживаемые HTTP-методы #
http4k поддерживает следующие HTTP-методы: GET
, POST
, PUT
, DELETE
, OPTIONS
, TRACE
, PATCH
, PURGE
, HEAD
. Методы описаны в перечислении org.http4k.core.Method
Вложенные маршруты #
Функции routes
в качестве аргумента можно передать объект RoutingHttpHandler, созданный в результаты другого вызова функции routes
:
val webCourseRouter = routes(
"topic" bind GET to { Response(OK).body("Веб-разработка") },
"length" bind GET to { Response(OK).body("1 семестр") }
)
val unixCourseRouter = routes(
"topic" bind GET to { Response(OK).body("Использование UNIX") },
"length" bind GET to { Response(OK).body("1 или 2 семсетра")},
)
val coursesApp = routes(
"test/ping" bind GET to { Response(OK).body("pong") },
"unix" bind unixCourseRouter,
"web" bind webCourseRouter,
)
Обрабатываются маршруты:
/test/ping
/unix/topic
,/unix/length
/web/topic
,/web/length
Динамические маршруты #
В рамках строки-шаблона могут содержаться переменные, которые можно использовать в рамках обработки запроса
routes (
"/book/{title}" bind GET to { request ->
Response.invoke(Status.OK).body(request.path("title").orEmpty())
},
"/author/{name}/latest" bind GET to { request ->
Response.invoke(Status.OK).body(request.path("name").orEmpty())
},
)
- Переменные могут располагаться в любой части строки, их может быть несколько
- Переменные должны быть отделены друг от друга каким-то символом
- Рекомендуется использовать
/
для разделения:/book/{author}/{title}
- Входом на вход HTTP-обработчику передаётся объект типа
RoutedRequest
- Для получения переданных данных из пути используется метод
Request.path
fun Request.path(name: String): String?
- Метод
String?.orEmpty()
возвращает пустую строку, если ссылка содержитnull
Данные для формирования ответа #
Зачастую информации из запроса недостаточно для формирования ответа:
- Необходимо получить доступ к данным из базы данных (хранилища)
- Необходимо выполнить проверку уровня доступа пользователя
- Необходимо обратиться к ресурсам
Для решения этих задач в конструктор обработчика надо передать зависимости:
// Конструктор в виде класса
class UserListerHandler(private val users: List<String>): HttpHandler {
override fun invoke(request: Request): Response =
Response(OK).body(users.joinToString(", "))
}
val userHandler = UserListerhandler(listOf("Иван", "Марья"))
// Констуктор в виде функции-генератора
fun messageResponder(message:String): HttpHandler = {
Response(OK).body(message)
}
val messageHandler = messageResponder("Привет!")
Если объекты-обработчики создаются внутри маршрутизатора, то их необходимо передать через слой маршрутизатора тоже
Архитектурный взгляд на приложение #
Под архитектурой подразумевается вопрос грамотного разделения приложения на компоненты, каждый из которых решает чётко поставленную задачу. Т.е. так, чтобы из небольших компонентов составлять полнофункциональное сложное приложение
Компоненты веб-приложения:
- Маршрутизатор HTTP-запросов
- Обработчики HTTP-запросов
- Реализация классов предметной области
- Подсистема преобразования данных в пользовательское представление
Все компоненты соединяются между собой внутри маршрутизатора и коде обработчиков HTTP-запросов
- Не надо перегружать внутреннюю логику обработчиков HTTP-запросов
- Выделяйте специфическую логику в отдельные подсистемы