Ktor客户端:HttpClient配置、请求与响应、序列化、WebSocket
网络请求,几乎是每个App都绕不开的坎。Ktor这个框架,我用了好几年了。说实话,刚开始接触它的时候,我还觉得「这玩意儿能比OkHttp好用?」。结果用了一段时间后,嗯,真香。它最大的特点就是——轻量、纯Kotlin、协程原生。今天我就带你把这几个核心模块捋一遍。
1. HttpClient的配置
创建一个HttpClient很简单,但配置才是关键。我个人习惯把配置抽成一个独立函数,方便复用。
val client = HttpClient(CIO) {
// 连接超时
connectTimeout = 5_000
// 请求超时
socketTimeout = 10_000
// 安装日志插件
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.HEADERS
}
// 安装内容协商(序列化)
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
prettyPrint = false
})
}
// 默认请求配置
defaultRequest {
url("https://api.example.com/")
contentType(ContentType.Application.Json)
}
}
这里我用了CIO引擎,它是Ktor自带的纯Kotlin实现。如果你在Android上用,也可以换成OkHttp引擎。我在项目中遇到过一个问题:用CIO在Android低版本上偶尔会报SSL错误,换成OkHttp引擎就稳了。
2. 请求与响应
Ktor的请求API非常简洁。说白了,就是一行代码的事。
// GET请求
val response: HttpResponse = client.get("users/123")
val user: User = response.body()
// POST请求
val newUser = client.post("users") {
setBody(UserCreate(name = "张三", age = 28))
}.body<User>()
// 带查询参数
val searchResult = client.get("search") {
parameter("q", "Kotlin")
parameter("page", 1)
}.body<SearchResponse>()
你想想看,用协程写网络请求,是不是比回调舒服多了?我记得以前用Retrofit的时候,还得定义一堆接口。Ktor直接链式调用,省事不少。
3. 序列化
序列化这块,Ktor用的是kotlinx.serialization。我刚开始用的时候,觉得它比Gson麻烦,因为要加@Serializable注解。但用久了发现,它更安全——不会出现运行时类型转换异常。
@Serializable
data class User(
val id: Int,
val name: String,
@SerialName("avatar_url")
val avatarUrl: String? = null
)
// 自定义序列化器
@Serializable
data class ApiResponse<T>(
val code: Int,
val message: String,
val data: T? = null
)
配置ContentNegotiation的时候,我建议把ignoreUnknownKeys设为true。为什么?因为后端接口经常会在响应里加新字段,你不设置这个,解析就会抛异常。我曾经因为这个被线上bug坑过一次,后来就学乖了。
| 配置项 | 作用 | 建议值 |
|---|---|---|
| ignoreUnknownKeys | 忽略未知字段 | true |
| isLenient | 宽松解析(允许尾随逗号等) | true |
| prettyPrint | 格式化输出JSON | false(生产环境) |
| coerceInputValues | 强制转换默认值 | true(推荐) |
4. WebSocket
WebSocket在Ktor里实现起来特别优雅。它不像OkHttp那样需要自己管理连接状态,Ktor直接帮你封装好了。
client.webSocket("wss://echo.websocket.org") {
// 发送消息
send(Frame.Text("Hello, Ktor!"))
// 接收消息
for (frame in incoming) {
when (frame) {
is Frame.Text -> {
val text = frame.readText()
println("收到消息: $text")
}
is Frame.Close -> {
println("连接关闭")
break
}
else -> {
// 忽略其他类型
}
}
}
}
这里有个坑,我必须要说。WebSocket的incoming是一个Flow,如果你在循环里不做break或cancel,它会一直等下去。我曾经写过一个聊天室功能,忘记处理关闭帧,结果协程泄漏了,内存一直涨。嗯,后来加了个超时机制才解决。
withTimeout或协程的Job.cancel()来管理生命周期。
知识体系总览
下面这张图,是我自己整理的Ktor客户端核心模块关系。你看一眼,心里就有数了。
这张图把Ktor客户端的四个核心模块串起来了。配置是基础,请求响应是日常,序列化是桥梁,WebSocket是进阶。你把这四个点吃透,大部分网络需求都能搞定。
公众号:蓝海资料掘金营,微信deep3321