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 一个进程全搞定,多省事。

性能提示:生产环境建议用 Nginx 或 CDN 托管静态文件,Ktor 只做 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 被公开了。嗯,后来我改成从环境变量读取,再也不敢偷懒了。

安全提醒:Session 加密密钥一定要用强随机字符串,长度至少 32 位。生产环境从环境变量或密钥管理服务获取,不要硬编码。

五、知识体系总览

下面这张图把今天讲的四个模块串起来了。你可以看到请求从客户端进来,先经过路由分发,然后根据内容协商返回 JSON 或 HTML,静态文件直接走文件服务,Session 管理负责跟踪用户状态。

Ktor 服务端核心模块 客户端请求 路由分发 Routing DSL 内容协商 ContentNegotiation 静态文件服务 static / resources Session 管理 Sessions JSON / XML / HTML 图片 / CSS / JS 用户状态跟踪

六、避坑指南

最后分享几个我踩过的坑,希望能帮你省点时间:

  • 路由顺序问题:Ktor 匹配路由是按注册顺序来的。如果你把 get("/{param}") 写在 get("/static") 前面,那 /static 永远匹配不到。我建议把精确路径写在前面,通配路径放后面。
  • Session 过期:默认 Session 是临时的,浏览器一关就没了。如果需要持久化,记得设置 cookie<T>(...) { ... } 里的过期时间。
  • 静态文件缓存:开发时改了前端代码,浏览器还是旧版本。这是因为 Ktor 默认加了强缓存。可以在 static 配置里关掉缓存,或者加版本号。
  • 内容协商失败:如果客户端传了 Accept: application/xml,但你只注册了 JSON 序列化,Ktor 会返回 406 Not Acceptable。记得检查客户端的请求头。

好了,今天的内容就到这里。这四个模块是 Ktor 服务端开发的基础,掌握了它们,你就能搭建一个完整的 Web 服务了。下次遇到具体场景,我们再深入聊。


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