Kotlin Multiplatform - Handle response http code and exceptions with Ktor

Viewed 4723

I Just started to explore KMM, which seems really nice so far.

Basically, what I'm trying to to is to handle all http error code (like 401, 404, 500) globally, in one place.

However, I'm not sure how to interact with the response.

I installed ResponseObserver in HttpClient, but it only goes there if http status is 200.

I also tried to use HttpResponseValidator, but it never goes there.

Here's the code:

class ApiImpl : Api {

    private val httpClient = HttpClient {

        install(JsonFeature) {

            val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true }

            serializer = KotlinxSerializer(json)
        }

        install(ResponseObserver) {

            onResponse { response ->

                //*** We get here only if http status code is 200 ***/

                println("HTTP status: ${response.status.value}")
            }
        }

        HttpResponseValidator {

            validateResponse { response: HttpResponse ->

                val statusCode = response.status.value

                //*** We never get here ***/

                println("HTTP status: $statusCode")

                when (statusCode) {

                    in 300..399 -> throw RedirectResponseException(response)
                    in 400..499 -> throw ClientRequestException(response)
                    in 500..599 -> throw ServerResponseException(response)
                }

                if (statusCode >= 600) {
                    throw ResponseException(response)
                }
            }

            handleResponseException { cause: Throwable ->

                throw cause
            }
        }
    }

    override suspend fun getUsers(): List<User> {

        try {

            return httpClient.get("some url....")

        } catch (e: Exception) {

            //*** We get here in case of 404 (for example), but I don't want to repeat it in every request ***/

            println(e.message)
        }

        return emptyList()
    }
}
1 Answers

I've tested your scenario with this sample class (got and modified from official ktor github rep) and I can hit all breakpoints where you'd like to get error codes (e.g. 500)

package com.example.kmmtest001.shared

import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.observer.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.*

internal expect val ApplicationDispatcher: CoroutineDispatcher

class ApplicationApi {
    private val client = HttpClient {
        install(JsonFeature) {
            val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true }
            serializer = KotlinxSerializer(json)
        }

        install(ResponseObserver) {
            onResponse { response ->
                println("HTTP status: ${response.status.value}")
            }
        }

        HttpResponseValidator {
            validateResponse { response: HttpResponse ->
                val statusCode = response.status.value

                println("HTTP status: $statusCode")

                when (statusCode) {
                    in 300..399 -> throw RedirectResponseException(response)
                    in 400..499 -> throw ClientRequestException(response)
                    in 500..599 -> throw ServerResponseException(response)
                }

                if (statusCode >= 600) {
                    throw ResponseException(response)
                }
            }

            handleResponseException { cause: Throwable ->
                throw cause
            }
        }
    }

    var address = Url("https://tools.ietf.org/rfc/rfc1866.txt")

    fun about(callback: (String) -> Unit) {
        GlobalScope.apply {
            launch(ApplicationDispatcher) {
                val result: String = client.get {
                    url(this@ApplicationApi.address.toString())
                }

                callback(result)
            }
        }
    }

    fun get500ServerError(callback: (String) -> Unit) {
        GlobalScope.apply {
            launch(ApplicationDispatcher) {
                try {
                    val result: String = client.get {
                        url("https://www.versionestabile.it/_sw/so/63812313/index500.php")
                    }

                    callback(result)
                } catch (se: ServerResponseException) {
                    val statusCode = se.response?.status?.value
                    callback("HTTP status code: $statusCode --> ${se.message}")
                } catch (ce: ClientRequestException) {
                    val statusCode = ce.response?.status?.value
                    callback("HTTP status code: $statusCode --> ${ce.message}")
                }
            }
        }
    }
}

enter image description here

enter image description here

I've also tested 401 and 404 errors.

enter image description here

enter image description here

Related