47、Ktor服务端:路由配置、内容协商、静态文件服务、Session管理
Ktor 这个框架,说实话,我第一次接触时觉得它太「轻」了。跟 Spring Boot 那种全家桶比起来,Ktor 就像一把瑞士军刀——小巧、灵活、该有的都有。今天我们就来聊聊 Ktor 服务端的四个核心模块:路由、内容协商、静态文件、Session 管理。
我个人习惯把 Ktor 的项目结构分成三层:路由层、业务层、数据层。路由层只负责「谁来、去哪」,业务层处理逻辑,数据层管持久化。这样分工清晰,后期维护也省心。
一、路由配置:你的 API 地图
路由说白了就是「用户访问哪个路径,执行哪段代码」。Ktor 的路由 DSL 写起来很舒服,像在画一棵树。
fun Application.module() {
install(Routing) {
get("/") {
call.respondText("Hello, Ktor!")
}
get("/user/{id}") {
val userId = call.parameters["id"]
call.respondText("User ID: $userId")
}
post("/user") {
// 创建用户
}
}
}
我在项目中遇到过一个问题:路由层级一多,代码全挤在 module() 里,看着就头疼。后来我改用扩展函数拆分:
fun Application.configureRouting() {
routing {
userRoutes()
productRoutes()
orderRoutes()
}
}
fun Route.userRoutes() {
route("/user") {
get("/{id}") { /* ... */ }
post { /* ... */ }
}
}
嗯,这样每个模块独立一个文件,找起来方便多了。
{id}。Ktor 会自动解析成 call.parameters["id"]。注意参数名要一致。
二、内容协商:让数据「说人话」
内容协商是什么?就是客户端说「我要 JSON」,服务端就返回 JSON;客户端说「我要 XML」,服务端就返回 XML。Ktor 通过 ContentNegotiation 插件实现。
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
配置好之后,你的数据类就能自动序列化了:
@Serializable
data class User(val id: Int, val name: String, val email: String)
get("/user/1") {
val user = User(1, "张三", "zhangsan@example.com")
call.respond(user) // 自动转 JSON
}
我曾经踩过一个坑:客户端传过来的 JSON 字段名是下划线风格(user_name),而我服务端用的是驼峰(userName)。结果解析出来全是 null。后来在 Json 配置里加了 @SerialName 注解才搞定。
Accept 请求头。如果客户端没传这个头,Ktor 默认用第一个注册的格式。建议客户端显式指定 Accept: application/json。
三、静态文件服务:图片、CSS、JS 一站搞定
Ktor 可以像 Nginx 一样托管静态文件。你只需要指定一个目录,它就能把里面的文件直接返回给浏览器。
install(Routing) {
static("/static") {
resources("static") // 从 classpath 加载
file("images", "images") // 从文件系统加载
}
}
这样访问 /static/images/logo.png 就能拿到图片了。我个人习惯把前端打包后的 dist 目录直接扔进 resources 里,Ktor 自动托管,省得再配一个 Nginx。
你想想看,如果项目里既有 API 又有前端页面,Ktor 一个进程全搞定,多省事。
四、Session 管理:记住你是谁
Session 用来跟踪用户状态。Ktor 支持多种存储方式:内存、文件、数据库、客户端 Cookie。我一般用 Cookie Session,简单又安全。
install(Sessions) {
cookie<UserSession>("user_session") {
transform(SessionTransportTransformerEncrypt("my-secret-key"))
}
}
data class UserSession(val userId: Int, val userName: String)
登录成功后,把 Session 存进去:
post("/login") {
val user = authenticateUser(/* ... */)
if (user != null) {
call.sessions.set(UserSession(user.id, user.name))
call.respondRedirect("/dashboard")
} else {
call.respondText("登录失败", status = HttpStatusCode.Unauthorized)
}
}
在需要鉴权的路由里,读取 Session:
get("/dashboard") {
val session = call.sessions.get<UserSession>()
if (session != null) {
call.respondText("欢迎回来,${session.userName}")
} else {
call.respondRedirect("/login")
}
}
我曾经犯过一个低级错误:Session 的加密密钥写死在代码里,结果代码上传到 GitHub 被公开了。嗯,后来我改成从环境变量读取,再也不敢偷懒了。
五、知识体系总览
下面这张图把今天讲的四个模块串起来了。你可以看到请求从客户端进来,先经过路由分发,然后根据内容协商返回 JSON 或 HTML,静态文件直接走文件服务,Session 管理负责跟踪用户状态。
六、避坑指南
最后分享几个我踩过的坑,希望能帮你省点时间:
- 路由顺序问题:Ktor 匹配路由是按注册顺序来的。如果你把
get("/{param}")写在get("/static")前面,那/static永远匹配不到。我建议把精确路径写在前面,通配路径放后面。 - Session 过期:默认 Session 是临时的,浏览器一关就没了。如果需要持久化,记得设置
cookie<T>(...) { ... }里的过期时间。 - 静态文件缓存:开发时改了前端代码,浏览器还是旧版本。这是因为 Ktor 默认加了强缓存。可以在
static配置里关掉缓存,或者加版本号。 - 内容协商失败:如果客户端传了
Accept: application/xml,但你只注册了 JSON 序列化,Ktor 会返回 406 Not Acceptable。记得检查客户端的请求头。
好了,今天的内容就到这里。这四个模块是 Ktor 服务端开发的基础,掌握了它们,你就能搭建一个完整的 Web 服务了。下次遇到具体场景,我们再深入聊。
公众号:蓝海资料掘金营,微信deep3321