Setiap permintaan HTTP di Ktor diwakili oleh satu objek ApplicationCall yang merangkum request dan response. Handler route menerima ApplicationCall dan memanipulasinya untuk membaca input, menulis output, serta mengatur status dan header. Memahami API ApplicationCall adalah prasyarat sebelum membahas content negotiation, validation, dan error handling. Pada chapter ini kita akan membedah call.respondText, call.receive, status code, dan header melalui model yang dapat dijalankan langsung.
ApplicationCall #
ApplicationCall adalah jembatan antara handler Kotlin dan request/response HTTP. Objek ini memiliki dua sisi: call.request untuk membaca input dan call.response untuk menulis output. Ktor juga menyediakan helper singkat seperti call.receiveText() untuk membaca body mentah dan call.respondText() untuk menulis body dengan content type eksplisit.
Handler Ktor biasanya berbentuk suspend fun ApplicationCall.handleFoo() — extension function pada ApplicationCall. Karena suspend, handler dapat memanggil operasi asinkron seperti query database tanpa memblok thread.
API Inti ApplicationCall #
| API | Peran |
|---|---|
call.receiveText() | Membaca body mentah sebagai String |
call.receive<T>() | Membaca body sebagai objek T (perlu ContentNegotiation) |
call.respondText(body, contentType, status) | Menulis body teks dengan content type dan status |
call.respond(value) | Menulis objek (perlu ContentNegotiation) |
call.response.status(HttpStatusCode) | Mengatur status code |
call.request.headers["Authorization"] | Membaca header request |
Membaca Body Request #
Untuk endpoint POST atau PUT, body request biasanya berisi data yang akan disimpan. call.receiveText() mengembalikan seluruh body sebagai String — cocok untuk payload kecil atau logging. Untuk payload terstruktur, call.receive<T>() mengonversi body ke objek Kotlin lewat plugin ContentNegotiation yang akan dibahas pada chapter berikutnya.
Validasi body wajib dilakukan oleh handler. Ktor tidak menolak body kosong secara otomatis — handler harus memeriksa body.isBlank() dan mengembalikan status 400 bila invalid.
Menulis Respons #
call.respondText() adalah API paling dasar untuk menulis respons. Fungsi ini menerima body, content type opsional, dan status code opsional (default 200). Untuk respons terstruktur seperti JSON, handler memakai call.respond(objek) yang memicu ContentNegotiation menserialisasi objek ke JSON otomatis.
Status Code Umum #
| Status | Arti | Kapan dipakai |
|---|---|---|
| 200 OK | Permintaan berhasil | GET berhasil |
| 201 Created | Resource baru dibuat | POST berhasil |
| 400 Bad Request | Input invalid | Body kosong, format salah |
| 404 Not Found | Resource tidak ada | ID tidak ditemukan |
| 500 Internal Error | Kesalahan server | Exception tidak tertangani |
Demo Request dan Response #
Agar API ApplicationCall dapat dijalankan tanpa Ktor nyata, kita akan memodelkannya sebagai class biasa dengan field requestHeaders, requestBody, responseStatus, responseBody, dan responseHeaders. Handler ditulis sebagai function biasa yang menerima ApplicationCall.
// ktor_request_dan_response.kt
// Mendemonstrasikan konsep ApplicationCall, call.respondText, call.receive,
// status code, dan headers pada Ktor.
// Mock ApplicationCall dengan header dan body
class ApplicationCall(
val method: String,
val path: String,
val requestHeaders: Map<String, String> = emptyMap(),
val requestBody: String = ""
) {
var responseStatus: Int = 200
var responseHeaders: MutableMap<String, String> = mutableMapOf()
var responseBody: String = ""
fun receiveText(): String = requestBody
inline fun <reified T> receive(): T {
// Dalam Ktor nyata, ContentNegotiation plugin mengonversi JSON ke objek.
// Di sini kita mock untuk String dan Int.
return when (T::class) {
String::class -> requestBody as T
else -> throw IllegalArgumentException("Tipe ${T::class} tidak didukung")
}
}
fun respondText(
body: String,
contentType: String = "text/plain",
status: Int = 200
) {
responseBody = body
responseHeaders["Content-Type"] = contentType
responseStatus = status
}
fun respond(body: String, status: Int = 200) {
responseBody = body
responseStatus = status
}
}
// Handler Ktor biasanya: suspend fun ApplicationCall.handleFoo()
fun handleRoot(call: ApplicationCall) {
call.respondText("Halo dari Ktor", contentType = "text/plain")
}
fun handleUserById(call: ApplicationCall, id: Int) {
if (id <= 0) {
call.respondText("Bad Request: id tidak valid", status = 400)
return
}
call.respondText("""{"id":$id,"nama":"Budi"}""", contentType = "application/json")
}
fun handleCreateUser(call: ApplicationCall) {
val body = call.receiveText()
if (body.isBlank()) {
call.respondText("Body tidak boleh kosong", status = 400)
return
}
call.respondText("Created: $body", contentType = "application/json", status = 201)
}
fun main() {
println("=== GET / ===")
val c1 = ApplicationCall("GET", "/")
handleRoot(c1)
println("Status: ${c1.responseStatus}, Body: ${c1.responseBody}, Headers: ${c1.responseHeaders}")
println("\n=== GET /users/42 ===")
val c2 = ApplicationCall("GET", "/users/42")
handleUserById(c2, 42)
println("Status: ${c2.responseStatus}, Body: ${c2.responseBody}")
println("\n=== GET /users/0 (invalid) ===")
val c3 = ApplicationCall("GET", "/users/0")
handleUserById(c3, 0)
println("Status: ${c3.responseStatus}, Body: ${c3.responseBody}")
println("\n=== POST /users (valid) ===")
val c4 = ApplicationCall("POST", "/users", requestBody = """{"nama":"Siti"}""")
handleCreateUser(c4)
println("Status: ${c4.responseStatus}, Body: ${c4.responseBody}")
println("\n=== POST /users (kosong) ===")
val c5 = ApplicationCall("POST", "/users", requestBody = "")
handleCreateUser(c5)
println("Status: ${c5.responseStatus}, Body: ${c5.responseBody}")
}Output yang Dihasilkan:
=== GET / ===
Status: 200, Body: Halo dari Ktor, Headers: {Content-Type=text/plain}
=== GET /users/42 ===
Status: 200, Body: {"id":42,"nama":"Budi"}
=== GET /users/0 (invalid) ===
Status: 400, Body: Bad Request: id tidak valid
=== POST /users (valid) ===
Status: 201, Body: Created: {"nama":"Siti"}
=== POST /users (kosong) ===
Status: 400, Body: Body tidak boleh kosongMari kita bedah bagian penting kode di atas:
class ApplicationCall(val method, val path, val requestHeaders, val requestBody): model dengan field request dan response terpisah. Fieldvaluntuk input (tidak berubah),varuntuk output (diisi handler).fun respondText(body, contentType, status): menulis body, mengatur headerContent-Type, dan status code sekaligus. API ini menirucall.respondTextKtor nyata.handleUserById(call, id): handler memvalidasiidterlebih dahulu. Bilaid <= 0, kembalikan status 400 dan hentikan denganreturnawal — pola fail-fast yang lazim di handler.handleCreateUser(call): membaca body viareceiveText(), memeriksaisBlank(), dan mengembalikan 400 bila kosong. Bila valid, kembalikan 201 Created dengan body konfirmasi.val c4 = ApplicationCall("POST", "/users", requestBody = ...): simulasi permintaan POST dengan body. Named parameter meningkatkan keterbacaan konstruktor dengan banyak argumen.
Referensi Lanjutan #
- Ktor requests — panduan resmi membaca parameter query, header, dan body request di Ktor
- Ktor responses
— panduan resmi mengembalikan
respond(), status code, dan header di Ktor
Selanjutnya #
Setelah memahami baca-tulis dasar, langkah berikutnya adalah otomatisasi konversi JSON. Chapter Ktor Content Negotiation akan membahas plugin ContentNegotiation yang mengubah objek Kotlin menjadi JSON dan sebaliknya tanpa kode serialisasi manual.