在 Ktor 中处理 Api 响应
Handle Api response in Ktor
嘿,我正在努力为我的 api 请求学习 ktor。我正在阅读 Response validation 文档。
ApiResponse.kt
sealed class ApiResponse<out T : Any> {
data class Success<out T : Any>(
val data: T?
) : ApiResponse<T>()
data class Error(
val exception: Throwable? = null,
val responseCode: Int = -1
) : ApiResponse<Nothing>()
fun handleResult(onSuccess: ((responseData: T?) -> Unit)?, onError: ((error: Error) -> Unit)?) {
when (this) {
is Success -> {
onSuccess?.invoke(this.data)
}
is Error -> {
onError?.invoke(this)
}
}
}
}
@Serializable
data class ErrorResponse(
var errorCode: Int = 1,
val errorMessage: String = "Something went wrong"
)
我有这个 ApiResponse class,我想通过这个 post 处理 api 响应。正如您在 link 中看到的,有一个名为 fetchResult
的函数,里面的代码每次都会检查 response.code()
并根据特定域进行路由 Success或 错误 正文。有没有更好的方法让它自动在特定域上路由而不是每次都在 ktor 中检查。
actual fun httpClient(config: HttpClientConfig<*>.() -> Unit) = HttpClient(OkHttp) {
config(this)
install(Logging) {
logger = Logger.SIMPLE
level = LogLevel.BODY
}
expectSuccess = false
HttpResponseValidator {
handleResponseExceptionWithRequest { exception, _ ->
val errorResponse: ErrorResponse
when (exception) {
is ResponseException -> {
errorResponse = exception.response.body()
errorResponse.errorCode = exception.response.status.value
}
}
}
}
}
KtorApi.kt
class KtorApi(private val httpClient: HttpClient) {
suspend fun getAbc(): Flow<KtorResponse> {
return httpClient.get {
url("abc")
}.body()
}
}
您可以通过在 Transform
阶段拦截管道来推送 HttpResponsePipeline
类型的对象 ApiResponse
。在拦截器中,您可以使用 ContentConverter
将响应主体反序列化为泛型对象 (out T : Any
)。请注意,此解决方案不允许您捕获网络异常。如果你想处理网络或其他异常,你必须编写一个函数,它将 return 一个 ApiResponse
类型的对象,如文章中所述。这是一个完整的例子:
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.apache.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.serialization.*
import io.ktor.serialization.kotlinx.*
import io.ktor.util.reflect.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json
import kotlin.reflect.KClass
fun main(): Unit = runBlocking {
val client = HttpClient(Apache)
val converter = KotlinxSerializationConverter(Json { ignoreUnknownKeys = true })
client.responsePipeline.intercept(HttpResponsePipeline.Transform) { (info, body) ->
if (body !is ByteReadChannel) return@intercept
val response = context.response
val apiResponse = if (response.status.value in 200..299) {
ApiResponse.Success(
converter.deserialize(context.request.headers.suitableCharset(), info.ofInnerClassParameter(), body)
)
} else {
ApiResponse.Error(responseCode = response.status.value)
}
proceedWith(HttpResponseContainer(info, apiResponse))
}
val r: ApiResponse<HttpBin> = client.get("https://httpbin.org/get").body()
r.handleResult({ data ->
println(data?.origin)
}) { error ->
println(error.responseCode)
}
}
fun TypeInfo.ofInnerClassParameter(): TypeInfo {
// Error handling is needed here
val typeProjection = kotlinType?.arguments?.get(0)
val kType = typeProjection!!.type!!
return TypeInfo(kType.classifier as KClass<*>, kType.platformType)
}
@kotlinx.serialization.Serializable
data class HttpBin(val origin: String)
sealed class ApiResponse<out T : Any> {
data class Success<out T : Any>(
val data: T?
) : ApiResponse<T>()
data class Error(
val exception: Throwable? = null,
val responseCode: Int = -1
) : ApiResponse<Nothing>()
fun handleResult(onSuccess: ((responseData: T?) -> Unit)?, onError: ((error: Error) -> Unit)?) {
when (this) {
is Success -> {
onSuccess?.invoke(this.data)
}
is Error -> {
onError?.invoke(this)
}
}
}
}
嘿,我正在努力为我的 api 请求学习 ktor。我正在阅读 Response validation 文档。
ApiResponse.kt
sealed class ApiResponse<out T : Any> {
data class Success<out T : Any>(
val data: T?
) : ApiResponse<T>()
data class Error(
val exception: Throwable? = null,
val responseCode: Int = -1
) : ApiResponse<Nothing>()
fun handleResult(onSuccess: ((responseData: T?) -> Unit)?, onError: ((error: Error) -> Unit)?) {
when (this) {
is Success -> {
onSuccess?.invoke(this.data)
}
is Error -> {
onError?.invoke(this)
}
}
}
}
@Serializable
data class ErrorResponse(
var errorCode: Int = 1,
val errorMessage: String = "Something went wrong"
)
我有这个 ApiResponse class,我想通过这个 post 处理 api 响应。正如您在 link 中看到的,有一个名为 fetchResult
的函数,里面的代码每次都会检查 response.code()
并根据特定域进行路由 Success或 错误 正文。有没有更好的方法让它自动在特定域上路由而不是每次都在 ktor 中检查。
actual fun httpClient(config: HttpClientConfig<*>.() -> Unit) = HttpClient(OkHttp) {
config(this)
install(Logging) {
logger = Logger.SIMPLE
level = LogLevel.BODY
}
expectSuccess = false
HttpResponseValidator {
handleResponseExceptionWithRequest { exception, _ ->
val errorResponse: ErrorResponse
when (exception) {
is ResponseException -> {
errorResponse = exception.response.body()
errorResponse.errorCode = exception.response.status.value
}
}
}
}
}
KtorApi.kt
class KtorApi(private val httpClient: HttpClient) {
suspend fun getAbc(): Flow<KtorResponse> {
return httpClient.get {
url("abc")
}.body()
}
}
您可以通过在 Transform
阶段拦截管道来推送 HttpResponsePipeline
类型的对象 ApiResponse
。在拦截器中,您可以使用 ContentConverter
将响应主体反序列化为泛型对象 (out T : Any
)。请注意,此解决方案不允许您捕获网络异常。如果你想处理网络或其他异常,你必须编写一个函数,它将 return 一个 ApiResponse
类型的对象,如文章中所述。这是一个完整的例子:
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.apache.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.serialization.*
import io.ktor.serialization.kotlinx.*
import io.ktor.util.reflect.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json
import kotlin.reflect.KClass
fun main(): Unit = runBlocking {
val client = HttpClient(Apache)
val converter = KotlinxSerializationConverter(Json { ignoreUnknownKeys = true })
client.responsePipeline.intercept(HttpResponsePipeline.Transform) { (info, body) ->
if (body !is ByteReadChannel) return@intercept
val response = context.response
val apiResponse = if (response.status.value in 200..299) {
ApiResponse.Success(
converter.deserialize(context.request.headers.suitableCharset(), info.ofInnerClassParameter(), body)
)
} else {
ApiResponse.Error(responseCode = response.status.value)
}
proceedWith(HttpResponseContainer(info, apiResponse))
}
val r: ApiResponse<HttpBin> = client.get("https://httpbin.org/get").body()
r.handleResult({ data ->
println(data?.origin)
}) { error ->
println(error.responseCode)
}
}
fun TypeInfo.ofInnerClassParameter(): TypeInfo {
// Error handling is needed here
val typeProjection = kotlinType?.arguments?.get(0)
val kType = typeProjection!!.type!!
return TypeInfo(kType.classifier as KClass<*>, kType.platformType)
}
@kotlinx.serialization.Serializable
data class HttpBin(val origin: String)
sealed class ApiResponse<out T : Any> {
data class Success<out T : Any>(
val data: T?
) : ApiResponse<T>()
data class Error(
val exception: Throwable? = null,
val responseCode: Int = -1
) : ApiResponse<Nothing>()
fun handleResult(onSuccess: ((responseData: T?) -> Unit)?, onError: ((error: Error) -> Unit)?) {
when (this) {
is Success -> {
onSuccess?.invoke(this.data)
}
is Error -> {
onError?.invoke(this)
}
}
}
}