Васильев Андрей Михайлович, 2024
Версии презентации
В рамках концепции гипертекста доступ ко всем данным может быть осуществлён с помощью одного приложения, браузера, способного отображать документы и удобным образом переходить к другим документам в сети
Базовые технологии интернета гипертекста
Является протоколом прикладного уровня, решающий задачу передачи документов по общей сети удобным для пользователя образом
Новые версии обратно совместимы со старыми, добавляют новые возможности
Данные технологии детально не рассматриваются в курсе, но их знание является необходимым для разработки больших продуктовых систем
В рамках протокола выделяются 2 роли: сервер и клиент
HTTP-приложения являются распределёнными приложениями, в которых как сервер, так и клиент должен поддерживать совместное актуальное состояние
Между клиентом и сервером может находится несколько проксирующих серверов
Изначально HTTP-протокол разрабатывался как текстовый, то есть клиент и сервер обмениваются специально оформленными текстовыми сообщениями друг с другом
В новых версиях текст был заменён на бинарное представление для ускорения передачи данных, но концептуально логика взаимодействия не изменилась
GET /index.html HTTP/1.1
Host: developer.mozilla.org
Accept-Language: ru
HTTP/1.1 200 OK
Date: Sat, 09 Oct 2010 14:28:02 GMT
...
<!DOCTYPE html...
GET /index.html HTTP/1.1
Host: uniyar.ac.ru
Accept-Language: ru
GET
— метод (тип запроса)/index.html
— путь к документу, к которому осуществляется запросHTTP/1.1
— версия протоколаназвание : значение
HTTP/1.1 200 OK
Date: Sat, 09 Oct 2010 14:28:02 GMT
...
Content-Length: 2761
Content-type: text-html; charset=utf-8
<!DOCTYPE html...
HTTP/1.1
— версия протокола200
— код ответа сервера, указывающий результат обработкиOK
— краткое текстовое описание кода ответаContent-Length
, содержащий размер документа в символахВ протоколе определены следующие группы кодов ответа
100-199
— Информационные ответы200-299
— Успешные ответы300-399
— Перенаправления400-499
— Ошибки в запросе от клиента500-599
— Ошибки в работе сервера200
— запрос был выполнен успешно301
— перенесён на постоянной основе302
— документ временно перенесён404
— документ не найденВ качестве клиента в HTTP-взаимодействии может выступать любое приложение, которое реализует HTTP-протокол
Зачастую таким клиентом выступает веб-браузер, который получает и отображает веб-страницу для пользователя
Благодаря механизму заголовков можно добавлять новую семантику к запросам и ответам. Достаточно ввести соглашение между клиентом и сервером
В версии HTTP/1.1 ввели заголовок keep-alive
, который говорит серверу, что клиент может в рамках одного TCP-соединения можно выполнить несколько HTTP-запросов
Формируется новый протокол поверх HTTP
Есть более 100 стандартных заголовков запросов и ответов
С точки зрения протокола HTTP каждый запрос отдельный запрос не зависит от других
Клиент должен передать всю необходимую информацию при каждом запросе
Благодаря заголовку Cookies
сервер и браузер могут реализовать механизм сессий:
Задачей первых HTTP-серверов стояла раздача файлов с файловой системы
Таким образом для идентификации документов в качестве основы были взяты пути к файлам на жёстком диске в рамках ОС UNIX
/
Пример пути к файлу: /var/www/site/index.html
Внешнему наблюдателю не надо показывать все файлы, а только файлы в определённом каталоге, например в /var/www/site
Для указания местоположения целевого документа в сети Интернет используется Uniform Resource Identifier, URI
[ схема ":" ] [ // источник ] путь [ "?" запрос ] [ "#" фрагмент ]
Блоки внутри [ ]
являются опциональными и могут быть опущены
http
, ftp
, file
и т.д.Рассмотрим веб-сервер web-app.net
, раздающий документы из файловой системы
Структура ФС:
/var/www/site
├── 404.html
├── assets
│ └── main.css.map
├── feed.xml
├── index.html
├── labs
│ ├── 001-lab-01.html
│ └── 002-lab-02.html
├── labs.html
├── reference
│ ├── books.html
│ ├── editors.html
│ ├── schedule.html
│ └── tasks.html
└── topics.html
http
/var/www/site
Для файла /var/www/site/index.html
URI будет
http://web-app.net/index.html
Для файла /var/www/site/reference/tasks.html
http://web-app.net/reference/tasks.html
В рамках протокола HTTP иерархия соответствует файловой системе с разделителем /
Для указания пути к локальным файлам существует собственная схема — file
file://host/path
file://
localhost
/
C:\my-site\information.html
file://localhost/C:/my-site/information.html
file:///C:/my-site/information.html
/var/www/my-site/index.html
file://localhost/var/ww/my-site/index.html
file:///var/www/my-site/index.html
Ключевая особенность гипертекста — формировать ссылки между документами в сети, связывая их в единую сеть
В рамках последней схемы удобно использовать частичные URI
URI документа = абсолютный URI текущего документа + частичный URI
Частичные URI могут быть относительными или абсолютными
Отличить абсолютный путь от относительного легко — он начинается с символа /
/
├── css
│ └── style.css
├── index.html
└── topic
├── list.html
├── topic-1.html
└── topic-2.html
index.html
: /index.html
style.css
: /css/style.css
Они будут одинаковыми для любого просматриваемого документа на сайте
При формировании относительных частичных путей используется каталог, в котором находится документ с ссылкой
index.html
таким каталогом каталогом является /
topic-2.html
— /topic
При формировании ссылки к документу внутри одного сайта удобно использовать абсолютный путь
Рассмотрим структуру сайта example-app.net
/
├── css
│ └── style.css
├── index.html
└── topic
├── list.html
├── topic-1.html
└── topic-2.html
В рамках сайта у каждого файла будет свой собственный абсолютные частичные URL:
style.css
: /style.css
index.html
: /index.html
list.html
: /topic/list.html
topic-1.html
: /topic/topic-1.html
topic-2.html
: /topic/topic-2.html
Построим относительный путь от файла /index.html
/
├── css
│ └── style.css
├── index.html
└── topic
├── list.html
├── topic-1.html
└── topic-2.html
http://example-app.net/index.html
css/style.css
/
css/style.css
: /css/style.css
http://example-app.net
: http://example-app.net/css/style.css
Рассмотрим построение пути от файла topic-1.html
к файлу /css/style.css
/
├── css
│ └── style.css
├── index.html
└── topic
├── list.html
├── topic-1.html
└── topic-2.html
Для обращения к родительскому каталогу следует использовать специальное название каталога — ..
Этот специальный каталог присутствует и в обычной файловой системе и может быть использован не только в
http://example-app.net/topic/topic-1.html
../css/style.css
/topic/
../css/style.css
:
/topic/../css/style.css
/css/style.css
http://example-app.net
: http://example-app.net/css/style.css
Выделили следующие типы URI:
Возможно использовать только полный URI
Удобно использовать частичный URI, так как:
К плюсам использования относительного частичного URI относят:
http
), так и через уровень файловой системы (схема file
)Ограничения относительных частичных URI:
http4k — это набор инструментов для создания серверных и клиентских HTTP-приложений
Библиотека http4k предоставляет набор функциональных типов, которые позволяют создавать, тестировать и разворачивать HTTP-приложения
Данная структура является неизменяемой и описывает запросы и ответы
Класс содержит следующие свойства
version
— версия протокола HTTPheaders
— список HTTP-заголовковbody
— тело сообщенияКласс предоставляет методы для установки
Для представления этих элементов внутри приложения используются интерфейсы Request и Response, которые унаследованы от интерфейса HttpMessage
Помимо полей интерфейса HttpMessage предоставляет следующие поля
method
— HTTP-метод запроса (GET, PUT, …)source
— сетевая информация об источнике запросаuri
— адрес документа в запросеПомимо полей интерфейса HttpMessage предоставляет следующие поля
status
— статус HTTP-ответаtypealias HttpHandler = (Request) -> Response
Данный функциональный тип моделирует работу входящих и исходящих HTTP-запросов
Библиотека http4k предоставляет обвязку поверх множества существующих клиентов:
val client: HttpHandler = ApacheClient()
Функция может быть связана с сервером с помощью 1 строки кода. Это позволяет отделить бизнес-логику от реализации сервера:
val app: HttpHandler = // ...
val jettyServer = app.asServer(Netty(9000)).start()
Библиотека http4k позволяет использовать множество существующих серверов:
Проект создать можно с помощью:
Файл gradle.properties
содержит версии библиотек и используемых инструментов
junitVersion=5.10.0
http4kVersion=5.8.1.0
kotlinVersion=1.9.10
Файл build.gradle
описывает параметры сборки и запуска приложения
apply plugin: "application"
mainClassName = "su.yarsu.WebApplicationKt"
dependencies {
implementation("org.http4k:http4k-client-okhttp:${http4kVersion}")
implementation("org.http4k:http4k-core:${http4kVersion}")
implementation("org.http4k:http4k-multipart:${http4kVersion}")
implementation("org.http4k:http4k-format-jackson:${http4kVersion}")
implementation("org.http4k:http4k-server-netty:${http4kVersion}")
implementation("org.http4k:http4k-template-pebble:${http4kVersion}")
}
При прохождении мастера по созданию приложения была использована система сборки Gradle, которая будет использована при проверке лабораторных работ
С её помощью приложение можно легко запустить, выполнив команду в командном интерфейсе:
./gradlew run
После выполнения всех необходимых действий по скачиванию зависимостей и сборке приложения, оно будет запущено и веб-сервер будет доступен по http://localhost:9000
Обработчиком HTTP-запроса является является функциональный тип HttpHandler
Он может быть создан либо с использованием лямбда-выражения:
val handler: HttpHandler = { request: Request -> Response(OK) }
Либо с использованием класса, реализующего данный функциональный тип:
class SomeHandler : HttpHandler {
override fun invoke(request: Request) : Response = Response(OK)
}
val handler = SomeHandler()
Для описания HTTP-ответа используется класс Response
Status
, предоставляющий множество константbody
— установить новое тело, строку для ответаheader
— установить новое значение для конкретного заголовкаheaders
— установить новое значение для набора заголовковМетоды класса Response
не изменяют объект, но предоставляют его копию, у которого установлены новые значения. В результате каждый отдельный экземпляр класса Response
является неизменяемым, но очень легко сформировать объект с нужным состоянием
OK
, успешный результат
#
NOT_FOUND
, документ не найден
#
FOUND
, перенаправление
#
Тело ответа передаётся клиенту для дальнейшей обработки. Его можно:
Для установления тела ответа объекты Response предоставляют метод body
:
val response = Response(OK).body("Важное сообщение")
Зачастую информации из запроса недостаточно для формирования ответа:
Для решения этих задач в конструктор обработчика надо передать зависимости:
// Конструктор в виде класса
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("Привет!")
Функция может быть помечена ключевым словом infix
, если
Описание инфиксной функции и её использование
infix fun Int.shl(x: Int): Int { /*...*/ }
1 shl 2
1.shl(2)
Обязательно указывать как получателя, так и параметр
Основная задача сервера — обработка запросов от клиента. В рамках HTTP-протокола клиент формирует запрос к серверу и указывает к документу
При разработке сервера необходимо определить связь между маршрутом и обработчиком
Функция routes
позволяет описать данную связь:
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),
)
handler1
и handler2
являются обработчиками, HttpHandler/first
и /second
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
PathMethod.to
связывает результат работы предыдущего метода с обработчиками запроса, порождая объект типа RoutingHttpHandlerСформированный список объектов RoutingHttpHandler передаётся на вход функции routes
, которая сама возвращает обработчик RoutingHttpHandler
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())
},
)
Request.path
fun Request.path(name: String): String?
String?.orEmpty()
возвращает пустую строку, если ссылка содержит null
Для отображения HTML-документов необходимо предоставить пользователю возможность получить CSS и JavaScript-документы. Для этого используется функция static
Предположим, что базовым пакетом приложения является org.example
, а в рамках ресурсов в пакете org.example.public
находятся статические данные, то для их показа используем метод Classpath
класса ResourceLoader
routes(
static(ResourceLoader.Classpath("/org/example/public")),
)
Если данные находятся на жёстком диске, то необходимо использовать загрузчик с жёсткого диска, метод Directory
routes(
static(ResourceLoader.Directory("../uploads")
)
Под архитектурой подразумевается вопрос грамотного разделения приложения на компоненты, каждый из которых решает чётко поставленную задачу. Т.е. так, чтобы из небольших компонентов составлять полнофункциональное сложное приложение
Компоненты приложения:
Все компоненты соединяются между собой внутри маршрутизатора