Добавление новых элементов #
Запросы на изменение состояния приложения #
Состояние приложения описывается его данными. Следующие операции изменяют состояние приложения:
- Добавление нового элемента в список.
- Удаление элемента из списка.
- Изменение элемента списка.
- Замена содержимого всего списка.
Другие действия пользователя, связанные с разным отображением хранящихся данных, не влияют на его состояние.
Первый класс операций в HTTP-приложениях реализуется с помощью POST-запросов. Второй класс — с помощью GET-запросов.
Подход к обработке запроса #
Рассмотрим принципиальную схему по обработке POST-запроса на изменение состояния приложения.
sequenceDiagram
autonumber
actor Пользователь
participant Браузер
participant Сервер
Пользователь ->> Браузер: Нажимает на ссылку
к странице формы
Браузер ->> Сервер: Отправляет GET-запрос
по адресу документа
Сервер ->> Браузер: HTML-документ,
содержащий форму
Браузер ->> Пользователь: Показывает документ
с формой
Пользователь ->> Браузер: Заполняет интерактивные
элементы на форме
Пользователь ->> Браузер: Нажимает на кнопку
отправки запроса
Браузер ->> Сервер: Отправляет POST-запрос
с содержимым формы
alt Форма не содержит ошибок
Сервер ->> Браузер: Ответ со статусом 302, FOUND
else
Сервер ->> Браузер: HTML-документ, содержащий форму
с данными пользователя
и сообщениями об ошибках, пункт 3
end
В отличие от задачи фильтрации данный сценарий сложнее. В нём предусмотрен вариант, когда пользователь неправильно заполнил форму. В данном случае в качестве ответа HTTP-приложение должно вернуть HTML-документ, в котором
- будут показаны данные, которые пользователь отправил приложению (шаг № 6);
- будут отображены сообщения об ошибках, которые допустил пользователь при составлении.
В таком случае пользователь легко исправит ошибки, допущенные при составлении документа и быстро отправит повторный запрос.
В случае успеха HTTP-обработчик не должен возвращать HTML-документ, вместо этого должен перенаправить на другой документ, где пользователь сможет посмотреть на результат своей работы.
Работа с POST-формами #
Для описания POST-формы используются такие же элементы, как и в предыдущей практической работе. Единственное отличие — метод для передачи данных должен быть установлен равным post
. В таком случае данные с формы будут закодированы (см. атрибут enctype) и будут переданы не как часть запроса, а внутри тела HTTP-запроса.
Стоит учесть, что библиотека http4k не скрывает от разработчика разные форматы кодирования форм. И для различных типов кодировки форм предлагаются различные программные интерфейсы. В рамках данной практики рассмотрим сначала обработку кодировки тела HTTP-запроса в формате application/x-www-form-urlencoded
.
Доступ к данным формы #
Данные внутри формы кодируются таким же образом, как и параметры запроса: внутри формы может быть указано несколько полей ввода с одинаковыми именами. Следовательно форма — это набор пар название-значение.
Для получение доступа к форме http4k предоставляет следующие средства:
- Функция
fun Request.form(name: String): String?
позволяет получить значение первого параметра формы с названиемname
. - Функция
fun Request.form(): Form
позволяет получить ссылку на закодированную форму, которая является альтернативным названием дляParameters
, который был рассмотрен в предыдущих практиках. - Функция
fun Request.formAsMap(): Map<String, List<String?>>
позволяет получить ассоциативный массив, где для каждого названия поля (строки) будет указан список переданных значений с формы. В документации указано, что данный метод эффективнее альтернатив. - Для тестирования также предлагается функция
fun Request.form(name: String, value : String): Request
, которая позволяет установить значение для поля формы.
Задача № 1. Добавление треугольника без проверки ввода #
Добавьте страницу для указания параметров о треугольнике. На данной странице пользователь должен иметь возможность ввести длины сторон треугольника и отправить их на сервер. После отправки информации, приложение должно открыть страницу с информацией о треугольнике, где будут указана информация о добавленном треугольнике.
Предполагается на этом этапе, что пользователь вводит будет вводить корректные данные на форму.
Маршрут для добавления новых треугольников: /triangles/new
.
Примерный план реализации:
- Создайте пустую модель
NewTriangleVM
. - Создайте шаблон для отображения модели в файле
src/main/resources/ru/yarsu/models/NewTriangleVM.pub
. В данном шаблоне опишите форму:- В рамках элемента
form
укажите метод для отправки данных равнымPOST
. - Внутри элемента
form
добавьте три поля для ввода вещественных чисел с помощью элементаinput
. Каждому полю укажите имя:side-one
,side-two
иside-three
соответственно. - Добавьте интерактивную кнопку для отправки содержимого формы, элемент
button
.
- В рамках элемента
- Создайте обработчик запроса для показа формы.
- Внутри тела обработчика создайте экземпляр объекта
NewTriangleVM
, инициализировав его числом 0. - Сформируйте тело ответа путём вызова шаблонизатора,
renderer
с передачей ему созданного объекта.
- Внутри тела обработчика создайте экземпляр объекта
- Свяжите обработчик запроса с маршрутом
/triangles/new
. Связывание производите для GET-запроса. Связывание с данным маршрутом необходимо расположить до шаблонного маршрута для отображения информации по конкретному треугольнику. - На странице со списком треугольников добавьте ссылку на страницу добавления нового треугольника в список.
- Убедитесь, что обработчик корректно обрабатывается приложением.
- Реализуйте операцию по добавлению нового треугольника.
- Данная операция должна принимать в качестве аргумента хранилище треугольников.
- Операция должна предоставлять возможность по добавлению объекта треугольника.
- В качестве аргументов функция по добавлению должна принимать объект треугольника.
- В качестве ответа функция должна возвращать уникальный идентификатор добавленного треугольника.
- Реализуйте HTTP-обработчик POST-запроса на добавление треугольника.
- Конструктор класса должен принимать ссылку на операцию по добавлению треугольника.
- Внутри тела функции по обработке HTTP-запроса получите объект формы путём вызова функции
form
на объекте HTTP-запроса. - Из формы извлеките строки, которые были переданы от клиента. Для получения строки воспользуйтесь методом
findSingle
. Не забудьте преобразовать null-строку к пустой. - Преобразуйте строки в числа с использованием метода
toFloat()
. - Преобразованные данные запишите в качестве значений свойств нового объекта
Triangle
. В качестве уникального идентификатора объекта используйте произвольное значение. - Добавьте данный треугольник в список треугольников путём вызова соответствующей операции. Сохраните уникальный идентификатор добавленного треугольника.
- В качестве ответа сформируйте ответ с перенаправлением веб-браузера на другой адрес.
- Код ответа —
FOUND
- С помощью метода
header
установите заголовокLocation
равным/triangles/ID
, гдеID
— уникальный идентификатор нового треугольника.
- Код ответа —
- Свяжите данный обработчик с POST-запросом по пути
/triangles/new
в маршрутизаторе. - Убедитесь, что после корректного заполнения формы http://localhost:9000/triangles/new происходит переход на страницу с отображением информации о треугольнике. Проверьте, что все запросы успешно передаются. Используйте инструменты отладки веб-браузера.
Обработка некорректного ввода #
При получении некорректных данных от пользователя приложение должно заново отобразить форму, на которой:
- необходимо показать все данные, которые ввёл пользователь;
- для каждого проблемного поля показать сообщение, в котором сообщить пользователю почему данные неверны и, по возможности, как их стоит исправить.
Для описания ошибок можно воспользоваться ассоциативным массивом, где для каждого уникального названия поля, указан список сообщений, который будет содержать полезную информацию:
typealias FormErrors = Map<String, List<String>>>
Данную структуру можно поместить в класс и предоставить ряд методов, которые помогут с получением информации о состоянии конкретных полей.
Таким образом процедура обработки HTTP-запроса от формы будет выглядеть следующим образом:
- Получить все данные с формы, которые отправил пользователь.
- Провести проверку каждого из поля: корректного ли типа были переданы данные, логичны ли эти данные с точки зрения предметной области. В случае наличия проблемы составить список сообщений для пользователя.
- Провести проверку логических связей полей между собой. В случае наличия проблем добавить сообщения для пользователей.
- Объединить все сообщения в единую структуру.
- Если количество ошибок больше нуля, то необходимо сформировать HTML-документ, содержащий форму с заполненными значениями полей ввода. Первый выход.
- Если данные от пользователя верны, то необходимо добавить новый элемент в хранилище, а затем перенаправить пользователя по адресу, на котором он сможет увидеть результаты работы. Второй выход.
Ввиду того, что действий становится очень много, то можно из обработчика выделить ещё один модуль (функцию, класс), выполняющий валидацию входящих данных. Входными данными являются параметры, пришедшие от пользователя, а выходными — извлечённые данные со списком ошибок, выявленный при их проверке. Для описания выходных данных скорее всего потребуется сформировать класс.
Таким образом логически структура такого обработчика выглядит следующим образом:
flowchart TD request["Получение запроса от пользователя"] validator["Извлечение данных из ввода пользователя"] correct{"Данные можно извлечь\nи они верны?"} sendForm["Отправить форму \nс данными и сообщениями"] modifyData["Вызвать операцию\nпо изменению данных"] redirect["Перенаправить на страницу\n для просмотра результатов"] request --> validator validator --> correct correct -- верны --> modifyData modifyData --> redirect correct -- есть ошибки --> sendForm
Задача № 2. Реализуйте шаг обработки входных данных #
Модифицируйте обработку данных с формы таким образом, чтобы при указании некорректных данных, пользователь видел форму:
- С полями, заполненными данными.
- Со списком сообщений об ошибках.
Можно реализовать обработку сразу всей формы, тогда внутри компонента логически будут сразу выполнена работа со всеми полями формы, либо разбить обработку по компонентам. При работе с данными по отдельным полям для описания одного из них можно воспользоваться следующей структурой данных:
data class ValidatedFormField(
val name: String,
val values: List<String>,
val errors: List<String>,
}
Данная структура данных позволит закодировать одновременно и данные, которые ввёл пользователь для формы, а также список сообщений об ошибках, которые следует предоставить пользователю.
Данный класс можно расширить методами-помощниками, которые:
- позволят получить единственное значение для поля;
- позволяют узнать: есть ли ошибки у данного поля или нет.
Данный класс можно далее расширять нужными методами.
Альтернативным вариантом может стать использование специализированных библиотек для валидации данных:
Задача № 3. Сохранение данных после завершения приложения #
Отслеживание завершения приложения #
JVM-процесс может завершить свою работу в следующих случаях:
- Все основные потоки внутри процесса завершают свою работу.
- Частный пример: завершает работу основной поток, который запускается для обработки функции main.
- Программа получает сигнал завершения работы от операционной системы и
При завершении работы могут быть запущены потоки-обработчики данной ситуации
Для регистрации потока используется метод Platform.addShutdownHook(Thread thread)
Для описания потока в Kotlin можно воспользоваться thread
val hook = thread(start = false) {
println("Работаю во время выключения")
}
Runtime.getRuntime().addShutdownHook(hook)
Задача #
Добавьте обработку завершения работы приложения. При завершении работы приложение должно записывать список треугольников в файл triangles.json
.