'Handling Exception in HttpClient Ktor

I have written a common code in a common module as below and tested in JS environment

val response = client.post<HttpResponse>(url) {
    body = TextContent("""{"a":1,"b":2}""", ContentType.Application.Json)
}
if (response.status != HttpStatusCode.OK) {
    logger.error("Error, this one failed bad?")
}

But my code ends at client.post with a canceled corutineException on no network. How do I handle this and any other exception? If there is an internet connection. Nothing fails, I want to be able to handle the exceptions. How?

Note: try, catch doesn't work



Solution 1:[1]

well after asking here and there, I got help from github issues and came to this work arround

try {
    val response = client.post<HttpResponse>(url) {
        body = TextContent("""{"a":1,"b":2}""", ContentType.Application.Json)
    }
    if (response.status != HttpStatusCode.OK) {
        logger.error("Error, this one failed bad?")
    }
} catch (cause: Throwable) {
    logger.error("Catch your error here")
}

don't confuse to catch (c: Throwable) with catch (e: Exception)

hope this helps

Solution 2:[2]

Not adding much to the current answer but in response to CVS's comment I've been using the following to add ktor client error handling in my applications. It makes use of the Result API. runCatching {} catches all Throwables and you can tweak the behaviour of the getOrElse block to capture the exceptions you're interested in.

suspend fun <T> HttpClient.requestAndCatch(
    block: suspend HttpClient.() -> T,
    errorHandler: suspend ResponseException.() -> T
): T = runCatching { block() }
    .getOrElse {
        when (it) {
            is ResponseException -> it.errorHandler()
            else -> throw it
        }
    }

// Example call
client.requestAndCatch(
    { get<String>("/") },
    {
        when (response.status) {
            HttpStatusCode.BadRequest -> {} // Throw errors or transform to T 
            HttpStatusCode.Conflict -> {}
            else -> throw this
        }
    }
)

I'm sure it could be made tidier but it's the best I've come up with so far.

Solution 3:[3]

We can handle it in a central way using this extension function that wraps Success and error cases. We can have Network Error, Serialisation Error or Server Errors which may expose error bodies/messages for the UI and http status code.

suspend inline fun <reified T, reified E> HttpClient.safeRequest(
    block: HttpRequestBuilder.() -> Unit,
): ApiResponse<T, E> =
    try {
        val response = request { block() }
        ApiResponse.Success(response.body())
    } catch (e: ClientRequestException) {
        ApiResponse.Error.HttpError(e.response.status.value, e.errorBody())
    } catch (e: ServerResponseException) {
        ApiResponse.Error.HttpError(e.response.status.value, e.errorBody())
    } catch (e: IOException) {
        ApiResponse.Error.NetworkError
    } catch (e: SerializationException) {
        ApiResponse.Error.SerializationError
    }

suspend inline fun <reified E> ResponseException.errorBody(): E? =
    try {
        response.body()
    } catch (e: SerializationException) {
        null
    }

Here's the ApiResponse defined below so there's working code:

sealed class ApiResponse<out T, out E> {
    /**
     * Represents successful network responses (2xx).
     */
    data class Success<T>(val body: T) : ApiResponse<T, Nothing>()

    sealed class Error<E> : ApiResponse<Nothing, E>() {
        /**
         * Represents server (50x) and client (40x) errors.
         */
        data class HttpError<E>(val code: Int, val errorBody: E?) : Error<E>()

        /**
         * Represent IOExceptions and connectivity issues.
         */
        object NetworkError : Error<Nothing>()

        /**
         * Represent SerializationExceptions.
         */
        object SerializationError : Error<Nothing>()
    }
}

I found ApiResponse works well for my case but you don't necessarily have to model your api responses as this if you have other/special cases, you can also use kotlin-result or Arrow's Either.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2
Solution 3