Обработка переменных пути

Обработка переменных пути #

Документация #

Описание маршрутизатора и обработчика с переменной пути #

При описании маршрутов в маршрутизаторе http4k разработчик может указать переменную пути:

val triangleStorage = TriangleStorage()
val triangleHandler = TriangleHandler(triangleStorage)
routes(
    "/v1/triangles/{triangle-id}" bind Method.GET to triangleHandler,
)

Внутри обработчика можно получить доступ к переменной пути с помощью метода Request.path:

class TriangleHandler(
    private val triangleStorage: TriangleStorage,
    private val jsonFactory: JsonFactory
) : HttpHandler {
    override fun invoke(request: Request): Response {
        val rawId: String? = request.path("triangle-id")
        ...
    }
}
  • Данные приходят в строковом формате.
  • Если пользователь не передал данные, то и обработчик не будет вызван.

Как работает функция Request.path #

Рассмотрим реализацию функции Request.path:

fun Request.path(name: String): String? = when (this) {
    is RoutedMessage ->
        (xUriTemplate ?: throw IllegalStateException("Request was not routed, so no uri-template present"))
            .extract(uri.path)[name]

    else -> throw IllegalStateException("Request was not routed, so no uri-template present")
}

Из реализации видно, что функция работает не с интерфейсом Request, а с интерфейсом RoutedMessage. Единственный класс, который реализует данный интерфейс внутри библиотеки http4k — это класс RequestWithContext, обладающий следующим конструктором:

constructor(delegate: Request, uriTemplate: UriTemplate)

Таким образом, объект класса RequestWithContext соединяет в себе оригинальный объект запроса и шаблон подсистемы маршрутизации. Подсистема маршрутизации http4k создаёт этот объект автоматически, поэтому внутри HTTP-обработчика переменную пути можно получить напрямую через Request.path.

Описание шаблона пути происходит с помощью класса UriTemplate, который создаётся функцией-конструктором UriTemplate.from(template: String). Например:

val template = UriTemplate.from("/v1/triangle/{triangle-id}")

Задание № 1. Расширение возможностей хранилища #

Для расширенного доступа к данным приложения добавьте следующие возможности в класс-хранилище треугольника:

  • Получение треугольника по его уникальному идентификатору. Реализуйте функцию fun getById(id: UUID): Triangle?. Если треугольник с таким идентификатором не найден, функция возвращает null.
  • Добавление треугольника. Реализуйте функцию fun add(triangle: Triangle): UUID. Функция возвращает назначенный идентификатор. Для получения уникального идентификатора используйте функцию UUID.randomUUID().

Задание № 2. Реализация отображения конкретного треугольника #

Реализуйте отображение информации о конкретном треугольнике по пути /v1/triangles/{triangle-id}. Если по указанному пути треугольник не был найден, то обработчик должен возвращать сообщение со статусом 404, NOT_FOUND.

Ключевые части по решению задачи:

  • Реализуйте HTTP-обработчик для отображения информации о треугольнике:
    • Он должен принимать в качестве аргумента объекты генератора JSON и хранилища.
    • Из запроса извлеките значение переменной пути triangle-id, преобразуйте его к UUID функцией UUID.fromString(name: String): UUID. При некорректном значении эта функция возбуждает исключение IllegalArgumentException, которое необходимо обработать.
    • Если значение переменной пути не является корректным UUID, то должен быть возвращён ответ с кодом 400, BAD REQUEST, и следующим JSON-документом. Случай, когда переменная пути не передана вовсе, рассматривается в задании № 3.
      {
        "error": "Некорректный идентификатор треугольника. Для параметра triangle-id ожидается UUID, но получено значение «ЗНАЧЕНИЕ»"
      }
      Вместо ЗНАЧЕНИЕ подставьте фактическое значение переменной пути, полученное из запроса. Поле error формируется динамически. Заголовок Content-Type ответа должен иметь значение application/json.
    • Вызовите функцию хранилища, передав ей полученный идентификатор треугольника.
    • Если извлечь данные не удалось (функция вернула null), то обработчик должен вернуть ответ с кодом 404, NOT FOUND, и следующим JSON-документом:
      {
        "Id": "f0dcea8c-7a56-4223-ae43-3c664b50a603",
        "error": "Треугольник не найден"
      }
      Значение поля Id — идентификатор из переменной пути. Заголовок Content-Type ответа должен иметь значение application/json.
    • При успешном получении треугольника верните ответ со статусом 200, OK. JSON-документ формируется из полей объекта Triangle (модель и правила преобразования описаны в занятии «Формат JSON»). Пример документа:
      {
      
        "Id": "66ade672-2301-4408-9471-8a17aa883d65",
        "SideA": 1,
        "SideB": 2,
        "SideC": 3,
        "RegistrationDateTime": "2024-07-01T00:00:00.0",
        "BorderColor": "BLUE",
        "FillColor": "GREEN",
        "Description": "странные данные, это не треугольник",
        "Area": null,
        "Type": "некорректный"
      }
  • Реализуйте модульные тесты на созданный HTTP-обработчик. В тестах создавайте объект Request программно и проверяйте код Status и заголовок Content-Type полученного объекта Response. Пример построения запроса:
val request = Request(Method.GET, "/v1/triangles/f0dcea8c-7a56-4223-ae43-3c664b50a603")
val response = handler(request)
  • Свяжите данный обработчик HTTP-запроса с маршрутом /v1/triangles/{triangle-id}.

Задание № 3. Обработка отсутствия параметра #

Шаблон /v1/triangles/{triangle-id} требует непустого значения переменной пути. Запрос по пути /v1/triangles/ не соответствует этому шаблону, поэтому обработчик не вызывается, и http4k возвращает ответ с кодом 404.

Зарегистрируйте отдельный маршрут /v1/triangles/ и свяжите его с обработчиком, который возвращает ответ с кодом 400, BAD REQUEST, и JSON-документ с сообщением о некорректном идентификаторе треугольника (см. задание № 2).

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