В чём разница между respondText, respond и call.respond() в Ktor?
respondText() — удобная обёртка для ответа строкой с Content-Type text/plain. respond() — универсальный метод для любого объекта с сериализацией или ApplicationResponse. call.respond() — то же самое, вызывается через ApplicationCall.
Три способа отправить ответ
В Ktor все три функции доступны внутри обработчика маршрута через объект call: ApplicationCall. Разница — в уровне абстракции и типе содержимого.
respondText
Специализированная функция для отправки текста. Устанавливает Content-Type: text/plain (или другой, если указать явно):
get("/hello") {
call.respondText("Hello, World!") // 200, text/plain; charset=UTF-8
}
get("/html") {
call.respondText(
text = "<h1>Hi</h1>",
contentType = ContentType.Text.Html,
status = HttpStatusCode.OK
)
}
get("/csv") {
call.respondText(
contentType = ContentType.Text.CSV,
status = HttpStatusCode.OK
) {
// Производящий лямбда — строка генерируется лениво
buildString {
appendLine("id,name")
appendLine("1,Alice")
}
}
}
respond
call.respond() — универсальный метод. Принимает любой объект; если установлен плагин ContentNegotiation, объект будет сериализован в JSON/XML/etc. согласно заголовку Accept клиента:
import io.ktor.server.response.*
import io.ktor.http.*
import kotlinx.serialization.Serializable
@Serializable
data class User(val id: Int, val name: String)
get("/user") {
// Ответ JSON (если ContentNegotiation с json() установлен)
call.respond(User(1, "Alice"))
}
get("/no-content") {
call.respond(HttpStatusCode.NoContent) // 204 без тела
}
get("/created") {
call.respond(HttpStatusCode.Created, User(2, "Bob")) // 201 + JSON
}
get("/bytes") {
val data = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47) // PNG header
call.respond(ByteArrayContent(data, ContentType.Image.PNG))
}
Разница между respond и call.respond
Фактически это одно и то же: внутри блока маршрута this — это PipelineContext, а call — свойство этого контекста. Обе записи компилируются в одно:
get("/same") {
respond("Using extension") // то же самое
call.respond("Using call property") // что и это
// Но вызывать оба нельзя! Ответ уже отправлен после первого.
}
respondBytes и respondFile
get("/image") {
val bytes = File("logo.png").readBytes()
call.respondBytes(bytes, ContentType.Image.PNG)
}
get("/download") {
val file = File("report.pdf")
call.response.header(
HttpHeaders.ContentDisposition,
ContentDisposition.Attachment.withParameter(
ContentDisposition.Parameters.FileName, "report.pdf"
).toString()
)
call.respondFile(file)
}
Подводные камни
- После вызова любой функции
respond*()обработчик должен завершиться — повторный вызов вызовет исключениеIOException: Response has already been sent. call.respond(someObject)без установленногоContentNegotiationвыбросит исключение — объект не будет знать, как себя сериализовать.respondTextвсегда возвращаетtext/plainесли не указать contentType явно; клиент может неверно интерпретировать HTML-строку без явного указанияContentType.Text.Html.- Используйте
return@get(илиreturn@post) послеrespondвнутри условных веток, иначе выполнение продолжится до следующегоrespondи упадёт. - Статус-код по умолчанию — 200; для ошибок нужно явно передавать
HttpStatusCode.BadRequestи т.д., иначе клиент получит 200 с телом ошибки.
Common mistakes
- Путать термин «respond respondtext callrespond» с соседним механизмом Ktor.
- Не называть границу lifecycle, transaction, thread или request для «respond respondtext callrespond».
- Игнорировать production-эффекты «respond respondtext callrespond»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «respond respondtext callrespond» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «respond respondtext callrespond».
- Уточнить, какие настройки или API меняют «respond respondtext callrespond» в реальном сервисе.