Ktor — первая программа
Ktor — первая программа
Эта статья — пошаговый вход в backend на Kotlin — вы поднимете маленький HTTP-сервер, который отвечает на запросы из браузера, curl или мобильного приложения. Код можно повторить без опыта в сетевых протоколах: ниже есть словарь терминов и разбор каждой важной строки.
Ktor — асинхронный фреймворк от JetBrains для HTTP-серверов и HTTP-клиентов. Внутри он опирается на корутины: пока один запрос ждёт сеть или диск, поток JVM может обслуживать другие запросы.
База языка: Первая программа на Kotlin. Экосистема: Экосистема Kotlin. Android и сеть с устройства: мобильный раздел.
Что вы строите
Небольшой REST API для заметок:
| Метод HTTP | Путь | Смысл |
|---|---|---|
GET | /health | "Сервер жив?" — для мониторинга |
GET | /notes | Список всех заметок |
POST | /notes | Создать заметку (тело — JSON) |
DELETE | /notes/{id} | Удалить заметку по номеру |
Данные пока хранятся в памяти (mutableListOf) — после перезапуска сервера список обнулится. Для постоянного хранения позже подключают базу данных; логика маршрутов останется похожей.
Такой формат позволяет сначала понять HTTP-слой и маршрутизацию. После закрепления добавьте репозиторий и БД по статье Работа с базами данных, сохранив те же endpoint'ы.
Словарь терминов
| Термин | Простыми словами |
|---|---|
| HTTP | Протокол обмена: клиент (браузер, приложение) шлёт запрос, сервер возвращает ответ с кодом статуса и телом. |
| REST API | Набор URL ("ресурсов"), к которым обращаются методами GET, POST, DELETE и т.д. Вместо "страниц HTML" часто отдают JSON. |
| Маршрут (route) | Связка "путь + метод" → код обработчика. В Ktor блок routing { get("/health") { ... } }. |
| JSON | Текстовый формат данных: {"id":1,"text":"Привет"}. Удобен для приложений и веба. |
| Сериализация | Превращение объекта Kotlin (Note) в JSON и обратно. Здесь — библиотека kotlinx.serialization. |
| Engine (движок) | Низкоуровневый слой, который слушает порт и принимает TCP-соединения. Netty — популярный выбор на JVM. |
| Плагин (feature) | Подключаемая возможность: разбор JSON, логи, CORS, авторизация. В коде — install(ContentNegotiation). |
call | Контекст одного HTTP-запроса: параметры пути, тело, ответ (call.respond). |
embeddedServer | Запуск встроенного сервера в том же процессе, что и main — без отдельного Tomcat. |
Создание проекта
Вариант 1 — генератор
На start.ktor.io выберите:
- Engine: Netty
- Dependencies:
Routing,Content Negotiation,kotlinx-serialization
Скачайте архив, откройте в IntelliJ IDEA, выполните ./gradlew run.
./gradlew run
Разбор:
./gradlewзапускает Gradle Wrapper из проекта и гарантирует использование согласованной версии Gradle.- Команда
runсобирает проект и стартует приложение черезapplication-плагин. - Такой запуск удобен для локальной разработки: изменения кода быстро проверяются без ручной сборки JAR.
Вариант 2 — Gradle вручную
Файл build.gradle.kts в корне проекта:
Код ITЗагрузка примера кода…
Разбор:
- Блок
pluginsподключает компилятор Kotlin, сериализацию и поддержку запуска приложения. repositories { mavenCentral() }указывает основной источник зависимостей JVM-экосистемы.- В
dependenciesперечислены серверный core Ktor, движок Netty, JSON-плагин и сериализатор. logback-classicдобавляет конфигурируемое логирование запросов и внутренних событий приложения.application { mainClass.set(...) }задаёт точку входа, которую используетgradlew run.
Разбор фрагмента:
kotlin("plugin.serialization")— компилятор генерирует код для@Serializable.ktor-server-netty— движок Netty.content-negotiation+serialization-kotlinx-json— автоматический JSON в запросах и ответах.logback— логи в консоль (кто к какому URL обратился).mainClass— класс с функциейmain(для файлаApplication.ktимя станетApplicationKt).
Модели данных
@Serializable
data class Note(val id: Int, val text: String)
@Serializable
data class NoteCreate(val text: String)
Разбор:
@Serializableвключает генерацию кода сериализации для преобразования моделей в JSON и обратно.data class Noteописывает сущность ответа API с обязательнымиidиtext.data class NoteCreateмоделирует входное тело запроса при создании заметки.- Разделение входной и выходной моделей упрощает валидацию и развитие контракта API. См. Проверка и валидация.
| Элемент | Зачем |
|---|---|
@Serializable | Помечает класс для kotlinx.serialization — из него можно сделать JSON. |
data class | Автоматические equals, copy, удобная печать. |
Note | Полная заметка с id — то, что сервер отдаёт клиенту. |
NoteCreate | Только текст при создании — клиент ещё не знает id, его выдаёт сервер. |
Полный код Application.kt
Код ITЗагрузка примера кода…
Разбор:
- Импорты
io.ktor.server.*подключают API запуска сервера, маршрутов, чтения запросов и отправки ответов. main()поднимает сервер на порту8080и регистрирует функциюApplication::moduleкак конфигурацию.install(ContentNegotiation) { json() }включает автоматическую JSON-сериализацию для тел запросов и ответов.notesиnextIdхранят состояние in-memory и выдают идентификаторы новым заметкам.- В
routing { ... }объявлены endpoint'ыGET,POST,DELETEи их логика обработки HTTP-вызовов.
Построчный разбор main и модуля
fun main() {
embeddedServer(Netty, port = 8080, module = Application::module).start(wait = true)
}
Разбор:
-
embeddedServer(...)создаёт HTTP-сервер внутри текущего процесса JVM. -
Nettyвыбирается как сетевой engine для приёма соединений и обработки запросов. -
module = Application::moduleпередаёт ссылку на функцию конфигурации приложения. -
start(wait = true)удерживает главный поток активным, пока сервер не остановлен. -
embeddedServer— создаёт сервер в текущем JVM-процессе. -
Netty— конкретный движок. -
port = 8080— слушатьhttp://127.0.0.1:8080. -
module = Application::module— при старте вызовется ваша функцияApplication.module(). -
mainне завершится, пока сервер работает (удобно для./gradlew run).
start(wait = true)
Разбор:
- Параметр
wait = trueпереводит запуск в блокирующий режим: приложение остаётся в рабочем состоянии. - Для CLI-сценариев это стандартный режим, чтобы процесс не завершился сразу после инициализации.
- При
wait = falseсервер стартует асинхронно и управление возвращается вызывающему коду.
fun Application.module() {
install(ContentNegotiation) { json() }
Разбор:
- Расширение
Application.module()объединяет конфигурацию плагинов и маршрутов в одной точке. install(ContentNegotiation)подключает механизм выбора формата данных по HTTP-заголовкам.json()регистрирует JSON-конвертер, чтобыcall.receive<T>()иcall.respond(...)работали с объектами Kotlin.
Расширение Application.module — идиоматичный стиль Ktor: настройка привязана к жизненному циклу приложения. Плагин Content Negotiation смотрит заголовок Content-Type и превращает тело запроса/ответа в JSON и обратно.
val notes = mutableListOf<Note>()
var nextId = 1
Разбор:
mutableListOf<Note>()создаёт изменяемую коллекцию для хранения заметок в оперативной памяти.var nextId = 1хранит счётчик идентификаторов, который увеличивается при каждомPOST.- Такой подход удобен для учебного API без подключения БД, миграций и репозиториев.
"База данных" в памяти. nextId++ выдаёт уникальный номер каждой новой заметке (сначала 1, потом 2, …).
Маршруты
GET /health
get("/health") {
call.respond(mapOf("status" to "ok"))
}
Разбор:
get("/health")регистрирует маршрут проверки доступности сервиса.call.respond(...)отправляет HTTP-ответ клиенту с телом JSON-объекта.mapOf("status" to "ok")формирует компактный payload, подходящий для мониторинга и probe-проверок.
Клиент получит JSON {"status":"ok"} и код 200 OK. Так проверяют, что процесс запущен (оркестраторы, балансировщики).
GET /notes
call.respond(notes)
Разбор:
notesсодержит текущее состояние коллекции заметок в памяти.call.respond(notes)сериализует списокList<Note>в JSON-массив автоматически черезContentNegotiation.- Endpoint предоставляет read-модель API, которую клиент может отобразить или кэшировать.
Список сериализуется в JSON-массив: [{"id":1,"text":"..."}, ...].
POST /notes
val body = call.receive<NoteCreate>()
val note = Note(nextId++, body.text)
notes += note
call.respond(note)
Разбор:
call.receive<NoteCreate>()десериализует JSON-тело входящего запроса в типобезопасную модель.Note(nextId++, body.text)создаёт новую сущность и одновременно увеличивает счётчик ID.notes += noteдобавляет запись в изменяемую коллекцию приложения.call.respond(note)возвращает созданный объект клиенту как подтверждение результата операции.
receive<NoteCreate>()— прочитать тело запроса как JSON в объект Kotlin.- Собрать
Noteс новымid. - Добавить в список и вернуть созданную заметку клиенту (обычно код 200).
DELETE /notes/{id}
{id}в пути — параметр маршрута. Для/notes/5значение"5"лежит вcall.parameters["id"].toIntOrNull()— безопасное преобразование; при мусоре в URL вернём 400 Bad Request.removeIf { it.id == id }— удалить элемент из списка.- 204 No Content — успех без тела; 404 — заметки с таким
idне было.
Запуск
./gradlew run
Разбор:
- Команда используется для быстрого локального старта сервера из корня проекта.
- При запуске Gradle сначала компилирует Kotlin-код и разрешает зависимости.
- После успешной сборки запускается JVM-процесс с Ktor-приложением и логами в консоль.
В логах появится строка вроде Responding at http://0.0.0.0:8080. Остановка — Ctrl+C в терминале.
Проверка вручную
curl http://127.0.0.1:8080/health
curl http://127.0.0.1:8080/notes
curl -X POST http://127.0.0.1:8080/notes \
-H "Content-Type: application/json" \
-d '{"text":"Изучить Ktor"}'
curl -X DELETE http://127.0.0.1:8080/notes/1
Разбор:
- Первые два вызова
curlпроверяют здоровье сервиса и чтение списка заметок (GET-маршруты). -X POSTвместе с-H "Content-Type: application/json"отправляет JSON в endpoint создания заметки.- Параметр
-d '{...}'передаёт тело запроса, которое сервер читает черезreceive<NoteCreate>(). -X DELETEудаляет конкретный ресурс по ID и помогает проверить корректность статусов204/404.
| Команда | Что проверяет |
|---|---|
| Первая | Жив ли сервер |
| Вторая | Пустой или полный список |
| Третья | Создание с JSON-телом |
| Четвёртая | Удаление по id |
Заголовок Content-Type: application/json говорит серверу: тело запроса — JSON. Без него receive<NoteCreate>() может не сработать.
Как запрос проходит через Ktor
Разбор:
- Диаграмма показывает полный путь запроса: от клиента через Netty и роутер до конкретного обработчика.
- Узел
Routingвыбирает нужный endpoint на основе HTTP-метода и URL. - Внутри
Handlerвыполняется бизнес-логика — чтение тела, создание заметки, изменение состояния. - Завершающий шаг формирует HTTP-ответ с кодом и JSON, который возвращается клиенту.
Плагины (следующий шаг)
| Плагин | Назначение |
|---|---|
Authentication | JWT, Basic, OAuth |
CORS | Запросы из браузера с другого домена |
StatusPages | Единый формат ошибок вместо разрозненных respondText |
CallLogging | Лог каждого запроса (метод, путь, время) |
Подключение похоже на install(ContentNegotiation): объявляете в Application.module() до или после routing.
Ktor и Spring
| Стек | Когда уместен |
|---|---|
| Ktor | Kotlin-first, явные маршруты, лёгкий старт, корутины в обработчиках |
| Spring Boot | Большая экосистема (Security, Data, Cloud), команда уже на Spring |
Spring на Kotlin: Spring Boot на Kotlin — первая программа. Java-обзор: Spring для Kotlin/Java.
Частые ошибки
| Симптом | Причина |
|---|---|
Connection refused | Сервер не запущен или другой порт |
| 415 / пустое тело при POST | Нет заголовка Content-Type: application/json |
@Serializable не найден | Не подключён плагин kotlin("plugin.serialization") |
| Порт занят | Уже работает другой процесс на 8080 |
Что попробовать
- Добавить
PUT /notes/{id}для редактирования текста. - Подключить
CallLoggingи посмотреть логи приcurl. - Написать клиент на Ktor Client к этому же серверу.
Мини-чеклист качества API
Перед тем как считать первый сервер "готовым", проверьте базовые инженерные пункты:
- коды ответов согласованы (
200/201/204/400/404); - ошибки возвращаются в одном формате JSON;
- есть
GET /healthи минимум один тест вtestApplication; - входные данные валидируются до бизнес-логики;
- логируются метод, путь, статус и время запроса.
Этот чеклист пригодится и для следующей статьи по тестированию — Тестирование на Kotlin.
Связанные материалы
- Ktor Client — HTTP-запросы
- Корутины
- DSL и функции с получателем — почему
routing { }читается как мини-язык - Тестирование
Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.
В подборках
Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:
Первые шаги (маршрут подборки) — Первая программа на Symfony, Compose Multiplatform — первая программа, Первая программа на Laravel, Spring Boot на Kotlin — первая программа, Qt Quick — первая программа на QML, Первая программа на Gin.