KtorJuniorTechnical

В чём разница между 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» в реальном сервисе.

Sources

Related topics