Обработка строки запроса от пользователя #
Строка запроса #
В рамках URI, который передаётся от клиента к серверу, можно передать набор именованных параметров в строке запроса. Строка запроса отделяется от пути знаком ?
, затем идут пары параметров в формате название=значение
, разделённые символом &
. Рассмотрим следующий пример:
http://localhost:9000/objects?shape=triangles&min-area=90
Запрос от клиента происходит по протоколу HTTP по пути /objects
к серверу localhost:9000
. Данному пути передаются два параметра:
- параметр
shape
со значениемtriangles
; - параметр
min-area
со значением90
.
В рамках одной строки запроса может быть передано несколько параметров с одинаковым названием. Эта возможность обычно используется для передачи информации из HTML-форм. Важно понять, что набор параметров невозможно свести к ассоциативному массиву с уникальными ключами.
Классы Uri и Parameters #
Библиотека http4k предоставляет удобный класс для работы с URI-идентификаторами, Uri. Все объекты класса Request предоставляют свойство uri
, в котором находятся данные запроса от пользователя. Объекты класса Uri можно также создать самостоятельно из строки с помощью метода Uri.of(value: String): Uri
:
val uri = Uri.of("https://lms.crafted.su/tech-notes/")
Для работы со строкой запроса класс Uri предоставляет средства, отражённые в списке ниже.
- Свойство
query
для низкоуровневого доступа к строке, пришедшей от пользователя. - Функция
queries(): Parameters
позволяет получить доступ к списку параметров для получения значений параметров. - Функция
query(name: String, value: String?): Uri
добавляет ещё один параметр к запросу. При применении данной функции создаётся новый объект класса Uri, в значение свойстваquery
которого записывается строка с добавленным параметром. Оригинальный объект не изменяется. - Функция
removeQuery(name: String): Uri
удаляет все вхождения параметров с данным названием. - Функция
fun query(query: String): Uri
позволяет заменить целиком строку запроса на новую. Результатом работы функции является новый объект Uri, включающий новую строку запроса. Оригинальный объект класса Uri не изменяется.
Parameters является псевдонимом для списка параметров:
typealias Parameters = List<Parameter>
Для получения значений из данного списка можно воспользоваться средствами, описанными в списке ниже.
- Функция
Parameters.findSingle(name: String): String?
позволяет выполнить поиск первого значения для параметра с указанным названием. - Функция
Parameters.findMultiple(name: String): List<String?>
позволяет найти все значения для параметра с указанным названием. - Функция
Parameters.toParametersMap(): Map<String, List<String?>>
позволяет преобразовать параметры в ассоциативный массив, где для каждого названия (строки) будет указан список переданных значений.
Задача № 1. Протестируйте работу классов Uri и Parameters #
Реализуйте тесты для следующих свойств классов.
- Пользователь класса Uri может получить доступ к низкоуровневому содержимому строки запроса.
- Пользователь класса Uri может удалить существующий параметр из строки запроса.
- Пользователь класса Uri может добавить новый параметр в строку запроса.
- Пользователь класса Uri может добавить новый параметр с существующим именем.
- Пользователь классов Uri и Parameters может получить значения одиночных параметров.
- Пользователь классов Uri и Parameters может получить список значений множества параметров с одним именем.
- Пользователь классов Uri и Parameters получает null-значения при поиске значений несуществующих параметров.
Проблема отображения большого объёма данных #
При работе в среде распределённых систем не принято обмениваться большими объёмами данных. Большая часть взаимодействия происходит небольшими по объёму документами. Это снижает вероятность повреждения документов при передаче и ускоряет их получение финальным адресатом.
Данный вопрос особо актуален для клиентов, которые пользуются ненадёжными или перегруженными средствами связи: мобильный доступ к сети Интернет, публичные точки доступа WiFi, особенно в сельской местности.
Можно также привести следующие аргументы за частичное предоставление информации пользователю:
- Большой объём данных долго обрабатывает система (как на сервере, так и на клиенте), что делает работу пользователя с системой неудобной.
- Пользователю неудобно работать с очень большими документами. Поэтому, например, от свитков отказались и перешли на систему страниц.
- Системы хотят самостоятельно обрабатывать свои данные и не делиться этими данными с другими системами.
Типичным примером документа с большим объёмом данных является страница с показом информации со списком данных, например списком товаров в магазине или выдача в поисковом запросе. Для обеспечения быстроты и удобства работы пользователя данные в списке разделяются на части, каждая из которых отображается на своей странице.
Сценарий постраничного вывода #
Рассмотрим базовый сценарий реализации постраничного вывода.
sequenceDiagram autonumber actor user as Пользователь participant brow as Браузер participant serv as Сервер user ->> brow : Открывает ссылку со списком brow ->> serv : Выполняет GET-запрос документа по ссылке serv ->> brow : HTML-документ с данными и навигационной панелью brow ->> user : Отображает полученный документ user ->> brow : Выбирает номер страницы на панели и нажимает на ссылку brow ->> serv : Посылает GET-запрос на документ с номером страницы serv ->> brow : HTML-документ с данными и навигационной панелью brow ->> user : Отображает документ с нужной страницей
Ключевые особенности подхода:
- У маршрута для отображения документа появляется необязательный параметр — номер страницы.
- По умолчанию номер страницы равен единице (или нулю в зависимости от удобства дальнейшей обработки).
- На HTML-странице отображается навигационный элемент для перехода между страницами. Навигационный элемент может быть информативным, либо состоять из одной кнопки «показать больше».
- Внутри навигационной панели находятся подготовленные ссылки, включающие номера страниц, на которые пользователь может перейти.
- Пользователь может скопировать ссылку и поделиться ей с другими пользователями.
В ссылках на другие страницы необходимо сохранять все параметры, которые были переданы при отправке на страницу. Это необходимо для сохранения параметров, которые пользователь ввёл ранее или они были заданы . Эти параметры необходимо использовать при формировании ссылок постраничного вывода, чтобы не потерять данные.
Задача № 2. Формирование данных для постраничного вывода #
Создайте класс Paginator, в задачу которого должно входить формирование данных для отображения компонента переходами между страниц. Данный класс должен предоставлять следующие данные:
- Есть ли возможность перейти к предыдущей странице или нет.
- Список страниц (номер страницы и URI к странице) для передвижения назад.
- Есть ли возможность перейти к следующей странице или нет.
- Список страниц (номер страницы и URI к страние) для передвижения впрёд.
- Номер текущей страницы.
Базовыми данными для вычисления этих параметров можно использовать:
- Базовый URI-адрес страницы.
- Текущий номер страницы.
- Количество страниц.
Напишите модульные тесты для данного класса.
Задача № 3. Отображение #
Модифицируйте приложение для отображения списка треугольников. Реализуйте постраничный вывод информации о треугольниках на странице списка треугольников, которые вводились в систему. Для облегчения реализации:
- Инициализируйте список треугольников 10 разными треугольниками.
- Ограничьте количество данных на одной странице 6 треугольниками.
Рекомендуется реализовать метод внутри класса-списка треугольников, возвращающий подмножество списка треугольников в зависимости от переданной страницы. Страница передаётся в качестве аргумента метода.
fun trianglesByPageNumber(pageNumber: Int): List<Triangles>
Также потребуется реализовать метод для подсчёта количества страниц в классе-списке треугольников:
fun pageAmount(): Int
Проверьте краевые ситуации:
- В списке треугольников нет нужных данных.
- На последней страницы корректно отображается укороченный список.
- При передаче номера страницы за пределами возможных значений приложение показывает пустой список.
Используйте данные методы и созданный класс Paginator для реализации постраничного отображения данных в списке треугольников.