跳转到内容

Ktor — 用 Kotlin DSL 拼出来的异步 Web 框架

是什么

Ktor 是 JetBrains 给 Kotlin 出的一套异步 Web 框架,server 和 client 都管。日常类比:像乐高——只给你”地板(HTTP 引擎)“和一堆零件(认证、JSON、压缩),你自己挑要装哪些、按什么顺序装。

它不像 Spring Boot 那样”一开箱什么都给你配好”——Spring 是带说明书的成品玩具屋;Ktor 是一袋散件。新手会觉得”怎么连 JSON 解析都要我自己 install”,但反过来你也不会被它绑死在某种日志或 ORM 上。

最小启动代码长这样:

fun main() {
embeddedServer(Netty, port = 8080) {
routing {
get("/") { call.respondText("Hello, Ktor") }
}
}.start(wait = true)
}

这段代码做了三件事:选一个 HTTP 引擎(Netty)、起一个端口、声明一条路由。全是 Kotlin 函数调用,没有注解,没有 XML,没有 application.properties。

为什么重要

不理解 Ktor,下面这些事都没法解释:

  • 为什么 Kotlin 后端社区不直接用 Spring Boot——因为 Spring 是给 Java 的注解世界设计的,Kotlin 用着别扭
  • 为什么 Android 应用越来越多用 Ktor 而不是 Retrofit/OkHttp——因为 Ktor client 是 Kotlin Multiplatform,iOS 能复用同一份代码
  • 为什么”unopinionated”是把双刃剑——给你自由也意味着没人替你做选型决定
  • 为什么 install(X) {…} 这种写法这么火——它是 DSL + 插件系统的教科书例子

核心要点

Ktor 的设计可以拆成 三个支柱

  1. Engine 抽象:HTTP 接收/发送由独立 engine 处理(Netty / Jetty / CIO / Tomcat)。类比:你写菜单,但厨房用煤气还是电磁炉随便换。生产推荐 Netty,纯 Kotlin 玩具项目可以用 CIO。

  2. Plugin(旧名 Feature)+ Pipeline:每个能力都是 plugin,通过 install(X) {...} 装到 pipeline 上。Pipeline 是一条流水线,请求从一头进来,依次过 plugin(认证 → 日志 → 路由 → 序列化 → 响应)。装的顺序 = 拦截的顺序。

  3. Coroutine 原生:每个 handler 都是 suspend 函数。你写 delay(100)httpClient.get(...) 不会阻塞线程——线程被让给别的请求用,等 IO 回来再继续。这点和 Express、Spring MVC(非 WebFlux)有本质差别。

三件事合起来:DSL 装配 + 插件流水线 + 协程并发。

实践案例

案例 1:带 JWT 鉴权的 REST API

install(Authentication) {
jwt("auth-jwt") {
verifier(JWT.require(Algorithm.HMAC256("secret")).build())
validate { cred -> if (cred.payload.getClaim("uid").asString() != null)
JWTPrincipal(cred.payload) else null }
}
}
routing {
authenticate("auth-jwt") {
get("/me") {
val uid = call.principal<JWTPrincipal>()!!.payload.getClaim("uid").asString()
call.respondText("hello, user $uid")
}
}
}

install 把 JWT 校验装到 pipeline,authenticate { ... } 是一个作用域块——块内的路由都会走这个校验,块外不走。

案例 2:WebSocket 实时推送

install(WebSockets)
routing {
webSocket("/ws") {
for (frame in incoming) {
if (frame is Frame.Text) outgoing.send(Frame.Text("echo: ${frame.readText()}"))
}
}
}

incomingoutgoing 都是 Kotlin Channel——你 for 循环消费就行,底层异步 IO 由协程接管。换 Express 写 WebSocket,得自己 on(‘message’)、on(‘close’)、自己管状态。

案例 3:Multiplatform HTTP client

val client = HttpClient(CIO) { install(ContentNegotiation) { json() } }
val users: List<User> = client.get("https://api.example.com/users").body()

这段代码 同一份可以编译进 Android、iOS、JVM 后端、JS 浏览器——这是 Ktor 相对 Retrofit 的最大杀手锏。client 拆掉 server 也能单独用,跨端项目几乎是默认选择。

踩过的坑

  1. install 顺序就是拦截顺序:把 Authentication 装在 CallLogging 后面,日志会先出现再被 401 拒绝;反过来才是常规打法。新手装错了会发现”日志全打出来了但接口仍然被拒”很难定位。

  2. CIO 引擎在生产被打爆:CIO 是纯 Kotlin 实现,写 demo 很快,但成熟度不如 Netty。压测一上量经常出现 socket close 异常。生产默认 Netty。

  3. HOCON 配置 vs 代码配置覆盖application.conf 写了 port = 8080,代码里 embeddedServer(Netty, port = 9090) 也起得来,最终监听 9090——配置文件被静默忽略,团队协作时极难排查。统一一种风格。

  4. ContentNegotiation 没装就报奇怪错:忘了 install(ContentNegotiation) { json() } 的话,call.receive<MyDto>() 会抛 Cannot transform this request's content 而不是友好的”你忘了装 JSON 解析器”。

适用 vs 不适用场景

适用

  • Kotlin 后端项目,特别是中小型微服务和 BFF(Backend for Frontend)
  • 跨端项目(Android/iOS/Web 共用一套 client)
  • 团队有 Kotlin 经验、想要 DSL 风格、不想被 Spring 注解绑死
  • 实时类应用:WebSocket、SSE、长连接

不适用

  • Java 主导的老项目——Ktor 在 Java 里写出来很难看,Spring Boot 仍是更顺手的选择
  • 需要”开箱即有的”完整生态(事务、ORM、安全、批处理)→ 选 Spring Boot 或 Quarkus
  • 团队全是 Spring 经验、不想学 DSL 思维 → 强行上 Ktor 会卡在选型上
  • 需要稳定的 servlet 生态兼容(老 J2EE 项目)→ 用传统 servlet 容器

历史小故事(可跳过)

  • 2018 年:JetBrains 把 Ktor 1.0 正式开源,灵感来自之前几个 Kotlin 社区的废弃 web 框架(Wasabi、Kara),目标是给 Kotlin 一个”原生味道”的后端框架
  • 2020 年:1.x 系列稳定下来,Plugin 还叫 Feature,社区开始有人在生产用
  • 2022 年:2.0 大版本,client 被重写、API 整理、命名从 Feature 改成 Plugin
  • 2024 年:3.0 进一步整理 plugin API,去掉一些过渡期遗留
  • 2026 年:当前 stable 3.5.0,13k+ stars,仍由 JetBrains 官方维护

40 年后回头看后端框架的发展,从 Servlet → Spring → Spring Boot → Ktor,每一代都是”减少样板代码”的努力。Ktor 是 Kotlin 时代这条路线的代表。

学到什么

  1. DSL + 插件 + 协程是 Ktor 的三件套,理解了就理解了 Kotlin 后端这一代设计哲学
  2. Unopinionated 不等于”什么都没”,它等于”什么都能换”——代价是你得自己做选型
  3. Pipeline 顺序 = install 顺序,这是 Ktor 模型里最反直觉但最关键的一点
  4. Multiplatform client 是真正的差异化,跨端项目几乎是默认答案

延伸阅读

  • 官方文档:ktor.io(教程清晰,但深度内容要翻 issues)
  • 视频:Hadi Hariri — Ktor: Kotlin Web Framework(JetBrains 官方布道,1 小时入门)
  • spring-boot —— 对照阵营:注解驱动 vs DSL 驱动
  • vertx —— 同样异步、跨语言,但是 Java 优先
  • micronaut —— Java 系的”轻量 Spring”,编译期 DI 是它的差异点

关联

  • spring-boot —— Java/Kotlin 后端的另一极:开箱即用 vs 你自己拼
  • express —— Node 世界类似的”轻量 + 中间件”模型,但没协程
  • fastapi —— Python 类似定位,靠类型注解而非 DSL
  • vertx —— JVM 异步老兵,事件循环模型 vs Ktor 协程模型
  • micronaut —— Kotlin/Java 都能写,编译期 DI vs Ktor 运行期装配
  • axum —— Rust 里类似的”组合式中间件”思路
  • koa —— Node 里的洋葱式 middleware,思路同源

反向链接

  • aspnetcore —— ASP.NET Core — 微软跨平台 web 框架
  • axum —— axum — 用 Rust 类型系统当『路由参数表』的 Web 框架
  • dropwizard —— Dropwizard — Java 微服务的”开箱即用 12-factor 起步包”
  • express —— Express — Node.js 最经典的 Web 框架
  • fastapi —— FastAPI — 用 Python 类型注解写 API
  • hanami —— Hanami — Ruby 里既不是 Rails 也不是 Sinatra 的第三选择
  • koa —— Koa — async/await + ctx 对象 + 洋葱模型 的极简 Node.js web 框架
  • micronaut —— Micronaut — 编译期搞定 DI 的 JVM 云原生框架
  • sinatra —— Sinatra — 用 Ruby 三行代码起一个 web 服务
  • spring-boot —— Spring Boot — 用 Auto-configuration 把 Java 后端从 XML 地狱里救出来的事实标准框架
  • vertx —— Vert.x — Eclipse 出品的 polyglot reactive JVM toolkit,用事件总线 + verticle 把 Node.js 那套搬到多语言