Что такое объект ApplicationCall в Ktor?
ApplicationCall — центральный объект одного HTTP-обмена в Ktor: через него читают запрос (call.request), отправляют ответ (call.respond) и передают данные между плагинами через call.attributes.
Что такое ApplicationCall
ApplicationCall — это интерфейс в Ktor, представляющий единственный HTTP-обмен «запрос — ответ». Каждый входящий запрос порождает новый экземпляр ApplicationCall; он живёт ровно столько, сколько обрабатывается соответствующий запрос, и уничтожается после отправки ответа.
Интерфейс объявлен в пакете io.ktor.server.application и содержит три ключевых свойства:
call.request: ApplicationRequest— входящий запрос: метод, URI, заголовки (headers["Authorization"]), query-параметры (queryParameters["page"]), тело (receiveText(),receive<T>()).call.response: ApplicationResponse— исходящий ответ: установка заголовков, статуса. Обычно управляется черезcall.respond().call.attributes: Attributes— типизированный key-value контейнер для передачи данных между плагинами и route-обработчиками без каста через Any.
Жизненный цикл
Ktor обрабатывает каждый запрос в coroutine. Конвейер плагинов (ApplicationCallPipeline) проходит через фазы Setup, Monitoring, Features, Call, Fallback. На каждой фазе плагин получает текущий ApplicationCall через PipelineContext<Unit, ApplicationCall>. После вызова call.respond() ответ считается «завершённым» — повторный вызов бросает ResponseAlreadySentException.
Передача данных через Attributes
Атрибуты — рекомендованный способ передавать контекст (например, аутентифицированного пользователя) из плагина в обработчик без глобального состояния:
// Определяем ключ один раз
val UserIdKey = AttributeKey<Long>("UserId")
// Плагин аутентификации устанавливает атрибут
val authPlugin = createApplicationPlugin("AuthPlugin") {
onCall { call ->
val token = call.request.headers["Authorization"] ?: run {
call.respond(HttpStatusCode.Unauthorized)
return@onCall
}
val userId = verifyToken(token) // ваша логика
call.attributes.put(UserIdKey, userId)
}
}
// Route-обработчик читает атрибут
fun Application.module() {
install(authPlugin)
routing {
get("/profile") {
val userId = call.attributes[UserIdKey]
call.respond(fetchProfile(userId))
}
}
}
Чтение тела и сериализация
@Serializable
data class CreateUserRequest(val name: String, val email: String)
fun Application.module() {
install(ContentNegotiation) { json() }
routing {
post("/users") {
// call.receive<T>() использует установленный ContentNegotiation
val body = call.receive<CreateUserRequest>()
val id = userService.create(body.name, body.email)
call.respond(HttpStatusCode.Created, mapOf("id" to id))
}
}
}
Подводные камни
- Двойной respond. Второй вызов
call.respond()в одном обработчике бросаетResponseAlreadySentException. Нужно явно завершать ветку черезreturnпослеrespond(). - Тело можно прочитать только один раз.
receiveChannel()илиreceive<T>()потребляют поток байт; повторное чтение вернёт пустые данные или ошибку без явного кэширования (receiveText()+ разбор вручную). - Thread safety атрибутов.
call.attributesне thread-safe. Не обращайтесь к ним из параллельных coroutine-контекстов без внешней синхронизации. - Смешение ApplicationCall с тестами. В
testApplication { }используетсяTestApplicationCall; некоторые расширения (например, WebSocket-специфичные) недоступны в тестовом движке. - Неправильный статус по умолчанию. Если вызвать
call.respond(someObject)без явногоHttpStatusCode, Ktor ответит 200. Для ошибочных кейсов всегда передавайте статус явно. - Blocking в coroutine. Если внутри обработчика вызвать blocking IO (JDBC без
withContext(Dispatchers.IO)), это заблокирует event loop и резко снизит throughput. - Отсутствие ContentNegotiation. Вызов
call.receive<T>()без установленного плагинаContentNegotiationбросаетUnsupportedMediaTypeExceptionв рантайме, а не на старте. - call.respond vs respondText vs respondBytes. Неверный выбор метода приводит к лишней сериализации или неправильному Content-Type в ответе.
Common mistakes
- Путать термин «application call» с соседним механизмом Ktor.
- Не называть границу lifecycle, transaction, thread или request для «application call».
- Игнорировать production-эффекты «application call»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «application call» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «application call».
- Уточнить, какие настройки или API меняют «application call» в реальном сервисе.