13 - Rest Api - Ktor
Ktor
Ktor is a framework for building asynchronous server-side and client-side applications with ease.
Multiplatform asynchronous HTTP client, which allows to make requests and handle responses, extend its functionality with plugins, such as authentication, JSON serialization, and more.
Setup
build.gradle.kts - app
dependencies {
val ktorVersion = "3.0.1"
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
implementation("io.ktor:ktor-client-resources:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("org.slf4j:slf4j-android:1.7.36")
}
And serialization plugin
build.gradle.kts
// project
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.21" apply false
id("com.github.ben-manes.versions") version "0.51.0" apply false
}
// app
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("org.jetbrains.kotlin.plugin.serialization")
id("com.github.ben-manes.versions")
}
And configure client in code - use singleton
object BackendApiKtorSingleton {
private const val TAG = "BackendApiKtorSingleton"
private val client = HttpClient(OkHttp) {
install(HttpTimeout)
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
install(DefaultRequest) {
header(HttpHeaders.ContentType, ContentType.Application.Json)
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
filter { request ->
request.url.host.contains("ktor.io")
}
sanitizeHeader { header -> header == HttpHeaders.Authorization }
}
}
}
Serialization
Use @Serializable
attribute on top of your data classes.
@Serializable
data class Customer(
val id: Int,
val firstName: String,
val lastName: String
)
import kotlinx.serialization.json.Json
val jsonStr = Json.encodeToString(customer)
val customer: Customer = Json.decodeFromString(jsonStr)
Basic GET
val response: HttpResponse = client.get("https://ktor.io") {
// add query string
url {
parameters.append("token", "abc123")
}
// add headers
headers {
append(HttpHeaders.Accept, "text/html")
append(HttpHeaders.Authorization, "abc123")
append(HttpHeaders.UserAgent, "ktor client")
}
}
Deserialize response
val response = client.get(url)
if (!response.status.isSuccess()) {
Log.e(TAG, "response: " + response.bodyAsText())
return ArrayList()
} else {
Log.d(TAG, "response: " + response.bodyAsText())
}
val data: List<Customer> = response.body()
return data
Basic POST
val response: HttpResponse = client.post("http://localhost:8080/post") {
setBody("Body content")
}
val response: HttpResponse = client.post("http://localhost:8080/customer") {
contentType(ContentType.Application.Json)
setBody(Customer(3, "Jet", "Brains"))
}
val response: HttpResponse = client.submitForm(
url = "http://localhost:8080/signup",
formParameters = parameters {
append("username", "JetBrains")
append("email", "example@jetbrains.com")
append("password", "foobar")
append("confirmation", "foobar")
}
)
Plugins
Ktor provides several plugins to extend basic functionality.
ContentNegotiation
Negotiating media types between the client and server. For this, it uses the Accept and Content-Type headers.
Serializing/deserializing the content in a specific format when sending requests and receiving responses. Ktor supports the following formats out-of-the-box: JSON, XML (on jvm), CBOR, and ProtoBuf.
val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
}
HttpTimeout
The HttpTimeout plugin allows you to configure the following timeouts:
- request timeout — a time period required to process an HTTP call: from sending a request to receiving a response.
- connection timeout — a time period in which a client should establish a connection with a server.
- socket timeout — a maximum time of inactivity between two data packets when exchanging data with a server.
You can specify these timeouts for all requests or only specific ones.
val client = HttpClient(CIO) {
install(HttpTimeout) {
requestTimeoutMillis = 1000
}
}
val response: HttpResponse = client.get("http://0.0.0.0:8080/path1") {
timeout {
requestTimeoutMillis = 3000
}
}
DefaultRequest
The DefaultRequest plugin allows you to configure default parameters for all requests: specify a base URL, add headers, configure query parameters, and so on.
install(DefaultRequest) {
header(HttpHeaders.ContentType, ContentType.Application.Json)
}
Logging
On JVM, Ktor uses the Simple Logging Facade for Java (SLF4J) as an abstraction layer for logging. SLF4J decouples the logging API from the underlying logging implementation, allowing you to integrate the logging framework that best suits your application's requirements. Common choices include Logback or Log4j. If no framework is provided, SLF4J will default to a no-operation (NOP) implementation, which essentially disables logging.
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
Paraller requests
runBlocking {
// Parallel requests
val firstRequest: Deferred<String> = async { client.get("http://localhost:8080/path1").bodyAsText() }
val secondRequest: Deferred<String> = async { client.get("http://localhost:8080/path2").bodyAsText() }
val firstRequestContent = firstRequest.await()
val secondRequestContent = secondRequest.await()
}
Cancel a request
import kotlinx.coroutines.*
val client = HttpClient(CIO)
val job = launch {
val requestContent: String = client.get("http://localhost:8080")
}
job.cancel()