Что такое Ktor и зачем он нужен?
Ktor — это асинхронный веб-фреймворк, написанный на языке Kotlin компанией JetBrains (создатели IntelliJ IDEA и самого Kotlin). Он предназначен для создания серверных приложений, HTTP-клиентов и микросервисов с использованием мощностей корутин Kotlin. Ktor полностью построен на корутинах, что делает его неблокирующим, легковесным и невероятно производительным.
Зачем использовать Ktor? Во-первых, это нативный Kotlin-фреймворк, который идеально интегрируется с экосистемой языка. Во-вторых, Ktor модульный: вы подключаете только те компоненты, которые вам нужны (маршрутизация, сериализация, аутентификация, WebSockets и т.д.). В-третьих, он поддерживает мультиплатформенность: один и тот же код клиента может работать на JVM, Android, iOS и JavaScript. Ktor идеально подходит для создания REST API, микросервисов, а также для написания HTTP-клиентов, которые могут работать на любых платформах.
Установка Ktor
Ktor — это не одна библиотека, а набор Gradle-зависимостей. Установка происходит через систему сборки Gradle (или Maven). Для начала создайте новый проект Kotlin и добавьте следующие зависимости в файл build.gradle.kts:
// build.gradle.kts
val ktorVersion = "2.3.12" // Актуальная версия на момент написания
plugins {
kotlin("jvm") version "1.9.22"
application
}
dependencies {
// Сервер Ktor
implementation("io.ktor:ktor-server-core:$ktorVersion")
implementation("io.ktor:ktor-server-netty:$ktorVersion") // Движок Netty
implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") // Для JSON
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") // Сериализация JSON
// Клиент Ktor (если нужен)
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion") // Движок CIO
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
// Логирование
implementation("ch.qos.logback:logback-classic:1.4.14")
}
application {
mainClass.set("ApplicationKt") // Точка входа
}
После этого выполните команду ./gradlew build для загрузки зависимостей. Если вы используете Maven, аналогичные зависимости можно найти на официальном сайте Ktor. Для быстрого старта также можно использовать генератор проектов на start.ktor.io.
Быстрый старт с примером кода
Давайте создадим простой HTTP-сервер, который отвечает "Hello, World!" на GET-запрос к корневому пути.
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun main() {
embeddedServer(Netty, port = 8080) {
routing {
get("/") {
call.respondText("Hello, World!")
}
}
}.start(wait = true)
}
Запустите этот код. Теперь откройте браузер и перейдите по адресу http://localhost:8080. Вы увидите текст "Hello, World!". Поздравляю, ваш первый Ktor-сервер запущен!
Основные методы и функции Ktor
Ниже представлены 10 ключевых методов/функций Ktor, которые вы будете использовать чаще всего.
1. embeddedServer
Что делает: Создает и запускает встроенный HTTP-сервер. Это основная точка входа для любого Ktor-приложения.
Параметры:
engine— движок сервера (Netty, CIO, Jetty, Tomcat).port— порт для прослушивания (по умолчанию 80).host— хост (по умолчанию 0.0.0.0).module— лямбда-функция, в которой настраивается приложение (маршруты, плагины).
Пример:
embeddedServer(Netty, port = 9000, host = "127.0.0.1") {
// Настройка приложения
}.start(wait = true)
2. routing
Что делает: Блок, внутри которого определяются маршруты (endpoints) вашего API. Это функция-расширение для Application.
Параметры: Не принимает параметров, но внутри используются методы get, post, put, delete и т.д.
Пример:
routing {
get("/api/users") { /* обработка */ }
post("/api/users") { /* обработка */ }
}
3. get / post / put / delete
Что делает: Определяют обработчик для соответствующего HTTP-метода и пути.
Параметры:
path— строка пути (например, "/user/{id}").body— лямбда-функция (PipelineContext<Unit, ApplicationCall>), где доступен объектcall.
Пример:
get("/hello") {
call.respondText("Hello")
}
post("/data") {
val body = call.receive<String>()
call.respondText("Received: $body")
}
4. call.respond / call.respondText
Что делает: Отправляет ответ клиенту. respondText отправляет простой текст, respond может отправлять любые объекты (с использованием Content Negotiation).
Параметры:
respondText:text(String),status(HttpStatusCode, опционально).respond:body(любой объект),status(опционально).
Пример:
get("/json") {
val user = User("Alice", 30)
call.respond(user) // Требует плагин ContentNegotiation
}
get("/text") {
call.respondText("Plain text", status = HttpStatusCode.OK)
}
5. call.receive
Что делает: Получает тело запроса и десериализует его в указанный тип. Работает в паре с плагином Content Negotiation.
Параметры:
type— тип, в который нужно преобразовать тело запроса (указывается в угловых скобках).
Пример:
data class User(val name: String, val age: Int)
post("/user") {
val user = call.receive<User>()
println("Received user: ${user.name}")
call.respondText("User created")
}
6. install (Плагины)
Что делает: Устанавливает плагин (модуль) в приложение. Плагины добавляют функциональность: сериализация, аутентификация, сжатие, CORS и т.д.
Параметры:
plugin— объект плагина (например,ContentNegotiation).configure— лямбда для настройки плагина.
Пример:
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
install(CORS) {
allowHost("example.com")
}
7. call.parameters
Что делает: Предоставляет доступ к параметрам пути (path parameters) и query-параметрам из URL.
Параметры: Не принимает, это свойство.
Пример:
get("/user/{id}") {
val userId = call.parameters["id"] ?: return@get call.respondText("Missing id", status = HttpStatusCode.BadRequest)
call.respondText("User ID: $userId")
}
get("/search") {
val query = call.request.queryParameters["q"] ?: "default"
call.respondText("Search query: $query")
}
8. call.request
Что делает: Предоставляет доступ к объекту запроса: заголовки, метод, URI, cookies и т.д.
Параметры: Свойство, не принимает параметров.
Пример:
get("/info") {
val method = call.request.httpMethod.value
val uri = call.request.uri
val userAgent = call.request.headers["User-Agent"]
call.respondText("Method: $method, URI: $uri, UA: $userAgent")
}
9. HttpClient (Клиентская часть)
Что делает: Создает HTTP-клиент для выполнения исходящих запросов. Ktor предоставляет мощный асинхронный клиент.
Параметры:
engine— движок (CIO, Apache, OkHttp, Android).block— лямбда для настройки клиента.
Пример:
suspend fun fetchData(): String {
val client = HttpClient(CIO) {
install(ContentNegotiation) {
json()
}
}
val response: String = client.get("https://api.example.com/data").body()
client.close()
return response
}
10. client.get / client.post (Методы клиента)
Что делает: Выполняет HTTP-запрос указанного метода. Возвращает HttpResponse, из которого можно извлечь тело.
Параметры:
url— строка URL.block— лямбда для настройки запроса (заголовки, тело).
Пример:
suspend fun postJson() {
val client = HttpClient()
val response = client.post("https://httpbin.org/post") {
contentType(ContentType.Application.Json)
setBody(mapOf("key" to "value"))
}
println(response.bodyAsText())
client.close()
}
Реальный практический пример использования
Давайте создадим полноценный REST API для управления списком задач (To-Do List) с использованием Ktor и хранением данных в памяти.
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
@Serializable
data class Task(val id: String = UUID.randomUUID().toString(), val title: String, val completed: Boolean = false)
@Serializable
data class CreateTaskRequest(val title: String)
// Хранилище в памяти (потокобезопасное)
val taskStorage = ConcurrentHashMap<String, Task>()
fun main() {
embeddedServer(Netty, port = 8080) {
// Устанавливаем плагин для работы с JSON
install(ContentNegotiation) {
json()
}
routing {
// Получить все задачи
get("/tasks") {
call.respond(taskStorage.values.toList())
}
// Получить задачу по ID
get("/tasks/{id}") {
val id = call.parameters