Васильев Андрей Михайлович, 2024
Версии презентации
Структура URI
[ схема ":" ] [ // источник ] путь [ "?" запрос ] [ "#" фрагмент ]
?
, знака вопроса=
&
http://some.domain/some/path?key1=value1&key2=value2&key3=value3
На первый взгляд параметры очень напоминают структуру данных словарь, т.к. состоят из пар ключ-значение
Однако есть важное отличие от словаря: клиент может передать несколько параметров с одинаковыми ключами и сервер должен обработать их все
При наличии ошибки в значении параметра сервер должен сообщить об ошибке
Дополнительные параметры, переданные от клиента, обычно игнорируются
Для всех параметров, которые ожидает HTTP-сервер, он выполняет следующие проверки:
Подходы к обработке параметров, приходящих в запросах от пользователя:
uri
, содержащее объект класса Uriquery
содержит строку запроса полностьюfun query(query: String): Uri
позволяет создать новый объект Uri с новым значением запросаfun queries(): Parameters
возвращает набор пар ключ-значенияfun Uri.query(name: String, value: String?): Uri
позволяет добавить новый параметр к новому Uri-объектуfun Uri.removeQuery(name: String): Uri
позволяет удалить все параметры с указанным ключом, новое состояние сохраняется в возвращаемом объекте типа Urifun Parameters.findSingle(name: String): String?
позволяет найти первый параметр с указанным именемfun Parameters.findMultiple(name: String): List<String?>
позволяет найти все значения для параметра с указанным именемval queryParameters: Parameters = request.uri.queries()
val maxPrise: String? = queryParameters.findSingle("maxPrise")
// Каждый вызов queries() приводит к разбору строки
val page: String? = request.uri.queries().findSingle("maxPrice")
Для установки нового значения для параметра надо сначала удалить старые, а после добавить один новый
val newUri = request.uri.removeQuery("data").query("data", "value")
Если не удалить старые, то вместо замены будет добавлена новая пара
При работе с большим объёмом информации возникает ряд проблем:
Для обеспечения удобного доступа к большому объёму данных пользователю предоставляются инструменты для выполнения:
Даже после выполнения фильтрации объём данных скорее всего слишком большой для передачи пользователю, да и сервер не хочет отдавать все свои данные
Общепринятое решение — передача данных блоками, страницами
Обработчик запроса должен учитывать все параметры: сначала выполнить фильтрацию, затем отобразить нужную страницу
Параметры постраничного вывода обычно являются необязательными со значением по умолчанию
При выполнении фильтрации и сортировки порядок элементов в массиве скорее всего не будет соответствовать изначальному:
В приложении появляется потребность в классе-хранилище, элементов, который
class TriangleStorage() {
private val triangles: ...
fun add(triangle: Triangle): Int { ... }
fun get(id: Int): Triangle? { ... }
}
При добавлении нового элемента в хранилище либо
Хранилище может предоставлять функции для получения всех элементов в виде списка, для выполнения операций фильтрации и т.д.
Обработку запроса от пользователя можно разделить на три логических этапа, следующих один за другим:
В рамках обработки HTTP-запроса может быть выполнено несколько обращений к каждому из слоя извлечения данных и слоя предметной области
Рекомендуется каждый этап оформлять внутри отдельного компонента, а задачей HTTP-обработчика становится их логическое объединение для достижения задачи
В рамках HTTP-обработчика необходимо иметь доступ к хранилищу данных, т.к. все действия приложения так или иначе сводятся к выборке или изменению набора данных
При использовании большого класса-хранилища
При написании теста обработчика ему необходимо предоставить тестовый дубль класса-хранилища, а может быть и не одного
Можно добавить промежуточный слой - классов операций
Если классы-операции предоставляют только один публичный метод, то в рамках теста их легко заместить тестовым дублем
При создании операции можно удобно описать её интерфейс:
interface GetTriangleOperation {
fun get(id: Int): Triangle?
}
class GetTriangleOperationImpl(
private val storage: TriangleStorage
) : GetTriangleOperation {
override fun get(id: Int) = storage.get(id)
}
Внутри HTTP-обработчика реализовать получение объекта, реализующего интерфейс:
class ShowTriangleHandler(
getTriangleOperation: GetTriangleOperation
) : HttpHandler { ... }
При написании теста создаём тестовый дубль для тестирования обработчика:
class ReturnTriangle(private val triangle: Triangle?)
: GetTriangleOperation {
override fun get(id: Int) = triangle
}