Разбор тела ошибки в ktor HTTPClient

У меня есть api, который возвращает тело ошибки с правильной информацией об ошибке при отправке неверного запроса. Например, я получаю код состояния 400 и следующее тело -

{
  "errorCode": 1011,
  "errorMessage": "Unable to get Child information"
}

Теперь, когда я пишу для этого клиент ktor в многоплатформенном модуле, я улавливаю это в валидаторе ответа, например:

 HttpResponseValidator {
            validateResponse {
                val statusCode = it.status.value
                when (statusCode) {
                    in 300..399 -> print(it.content.toString())
                    in 400..499 -> {
                        print(it.content.toString())
                        throw ClientRequestException(it)
                    }
                    in 500..599 -> print(it.content.toString())
                }
            }
            handleResponseException {
                print(it.message)
            }
        }

Мой запрос здесь: я не могу получить доступ к телу ошибки ответа ни в validateResponse или handleResponseException. Есть ли способ поймать и проанализировать это, чтобы получить фактическую ошибку, отправленную сервером?

4 ответа

Решение

Вы можете объявить класс данных Error, чтобы представить ожидаемый ответ об ошибке.

import kotlinx.serialization.Serializable

@Serializable
data class Error(
    val errorCode: Int,   //if by errorCode you mean the http status code is not really necessary to include here as you already know it from the validateResponse
    val errorMessage: String
)

вы можете приостановить работу, чтобы проанализировать ответ и использовать его как экземпляр класса данных Error

 suspend fun getError(responseContent: ByteReadChannel): Error {
    responseContent.readUTF8Line()?.let {
        return Json(JsonConfiguration.Stable).parse(Error.serializer(), it)
    }
    throw IllegalArgumentException("not a parsable error")
}

затем внутри handleResponseException

handleResponseException { cause -> 
            val error = when (cause) {
                is ClientRequestException -> exceptionHandler.getError(cause.response.content)
// other cases here 

                else -> // throw Exception() do whatever you need 
            }
//resume with the error 
        }

вы можете реализовать некоторую логику на основе ошибки, которую вы получаете, чтобы выбросить исключение и перехватить его где-нибудь в другом месте вашего кода, например

when (error.errorCode) {
        1-> throw MyCustomException(error.errorMessage)
        else -> throw Exception(error.errorMessage)
    }

Я надеюсь, что это помогает

На всякий случай, если это поможет кому-то другому, ищущему в этом пространстве, в моем сервис-дизайне ktor response.readText имело смысл в моем случае:

       try {

  httpClient.post...   

} catch(cre: ClientRequestException){

  runBlocking {

    val content = cre.response?.readText(Charset.defaultCharset())

    val cfResponse = Gson().fromJson(content, CfResponse::class.java)

    ...

  }

}

Потратив несколько часов, я получил тело ошибки, выполнив следующие действия.

1. Определите класс вашей модели на предмет ошибки. В моем случае это было что-то вроде

      @Serializable
data class MyException(
    val message : String,
    val code : String,
    val type : String,
    val status_code : Int
) : RuntimeException()

Вы можете видеть, что я также расширил собственный класс до RuntimeException, потому что я хочу, чтобы мой класс вел себя как класс исключения.

2. Вызов API

      try {
     val mClient = KtorClientFactory().build()

     val res = mClient.post<MemberResponse>("${baseURL}user/login/") {
                //.....
               }
            
     emit(DataState.Success(res))

} catch (ex: Exception) {
    if (ex is ClientRequestException) {
        
        val res = ex.response.readText(Charsets.UTF_8)
        
        try {
            val myException = Json { ignoreUnknownKeys = true }
                              .decodeFromString(MyException.serializer(), res)

            emit(DataState.Error(myException))

         } catch (ex: Exception) {
              ex.printStackTrace()
           }
    } else
         emit(DataState.Error(ex))
}

Вот и все. Вы разобрали тело ошибки.

Чтобы понять это вкратце, вам просто нужно сосредоточиться на двух шагах.

1. val res = ex.response.readText(Charsets.UTF_8)

2. val myException = Json {ignoreUnknownKeys = true} .decodeFromString(MyException.serializer(), res)

Как указывали другие, вы можете объявить класс данных вашего объекта Error, и, поскольку Ktor уже настроен с сериализатором, мы можем получить тело ответа из ResponseException.

Вот функция расширения для простоты использования:

      suspend inline fun <reified E> ResponseException.errorBody(): E? =
    try {
        response.body()
    } catch (e: SerializationException) {
        null
    }
Другие вопросы по тегам