Разбор тела ошибки в 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
}