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引擎就稳了。

小提示:如果你不确定用哪个引擎,Android项目优先选OkHttp,后端服务选CIO或Netty。

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直接链式调用,省事不少。

注意:response.body() 是挂起函数,必须在协程或suspend函数里调用。别在UI线程直接调,会崩。

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,如果你在循环里不做breakcancel,它会一直等下去。我曾经写过一个聊天室功能,忘记处理关闭帧,结果协程泄漏了,内存一直涨。嗯,后来加了个超时机制才解决。

核心要点:WebSocket连接一定要在合适的时机关闭,比如页面销毁或用户退出。可以用withTimeout或协程的Job.cancel()来管理生命周期。

知识体系总览

下面这张图,是我自己整理的Ktor客户端核心模块关系。你看一眼,心里就有数了。

Ktor HttpClient 配置 引擎、超时、插件 请求与响应 GET/POST、参数、body 序列化 kotlinx.serialization WebSocket 全双工通信、Frame 协程原生 · 纯Kotlin · 轻量级 适合Android、后端、多平台项目

这张图把Ktor客户端的四个核心模块串起来了。配置是基础,请求响应是日常,序列化是桥梁,WebSocket是进阶。你把这四个点吃透,大部分网络需求都能搞定。

我的建议:刚开始别贪多,先从配置和GET请求入手。跑通一个接口后,再慢慢加序列化、WebSocket。一口吃不成胖子,代码也是一样。

公众号:蓝海资料掘金营,微信deep3321