最近想补一下网络编程的知识,最热门的肯定是Spring Boot,但是我对于Spring的框架设计很反感,我也不是很熟悉Java反射的api,所以阅读源码很困难。
Ktor是JetBrains推出的「协程优先的异步、非阻塞」HTTP框架。 总结一下这几天的理解。
在Java生态上构建
Ktor本身是一个多平台的FrameWork,Jvm上通过调用Netty,Jetty实现。 在Native平台, 通过Jetbrains基于协程实现的CIO实现Http/1协议,可惜的是至今没有支持Http/2。 Kotlin最开始是建立在Jvm生态上的语言,他可以用Jvm丰富的生态来让Ktor快速推广到商用稳定阶段,其他的平台上Ktor没有这个优势,以至于在Native平台上, Ktor基本不能用于商用。
Netty是基于Java NIO设计的非阻塞异步事件驱动网络库,他是一个相对底层的api,用Netty直接开发的难度较高,而且容易出错。
Spring WebFlux就是基于Netty设计的,他用Reactor来处理事件流, 相对来说更适合写业务。 但是Spring WebFlux仍然难用,处理多个流的时候,不得不组合Mono/Flux,而这样的写法既繁琐,可读性又差。 Spring一直在与Kotlin进行深度合作, 现在可以用协程api来替代Mono/Flux,利用Kotlin强大的协程API,可以简化写法。
而Ktor相比于SpringWebFlux,他从核心的设计上就是基于协程的,对于Kotlin user来说用着更自然,而且底层是Netty又保证了并发性能和稳定性。
Ktor的PipeLine
ktor有Server端和Client端实现, 这两段都是基于Pipeline工作流设计的。 ktor预先定义了一些“Phase”,Ktor插件里用拦截器可以挂在这些Phase上,实现不同的处理逻辑。
我整理了server端的Pipeline:
- EnginePipeline
当Netty收到请求时,执行EnginePipeline把请求交给Ktor处理。
- ApplicationCallPipeline
寻找匹配的Router处理业务逻辑。
- ApplicationReceivePipeline
call.receive() 触发。获得请求里的body, 并进行反序列化操作
- ApplicationSendPipeline
call.respond(...) 触发。ApplicationSendPipelin,在最后的“Engineg Phase“上把相应结果交给Netty

Ktor Server端插件整理
ContentNegotiation
https://ktor.io/docs/server-serialization.html
-
convertResponseBody 转换ResponseBody,拦截器挂在ApplicationSendPipeline.Transform
-
convertRequestBody 转换RequestBody,拦截器挂在ApplicationReceivePipeline.Transform
Caching Headers
https://ktor.io/docs/server-caching-headers.html#configure
RequestBodyLimit
ktor/ktor-server/ktor-server-plugins/ktor-server-body-limit/common/src/io/ktor/server/plugins/bodylimit/RequestBodyLimit.kt
限制request body 字节数, 如果超过限制, 则返回413 Payload Too Large。
AutoHeadResponse
某个路由定义了 GET 处理器时,它会自动生成对应的 HEAD 处理器,不需要你手动写。
curl -I -X HEAD http://localhost:8082/head
不返回body
ConditionalHeaders
- ETag
服务器会在响应里加上 ETag 头,通常是一个哈希值或版本号。
客户端下次请求时,可以带上 If-None-Match 头,把上次的 ETag 发给服务器。 (-H 'If-None-Match: "abc123"')
服务器比对 ETag:
一样:说明资源没变 → 返回 304 Not Modified(不传 body,客户端用缓存)。
不一样:说明资源更新了 → 返回 200 OK(新内容 + 新的 ETag)。
- If-Modified-Since 是 HTTP 缓存验证头。 "If-Modified-Since: Fri, 04 Mar 2022 09:52:07 GMT"
客户端告诉服务器:“我这边有个缓存,它最后修改时间是 xxx,如果你的资源在这之后没更新,就别传 body 给我了。”
服务器检查资源的 Last-Modified 时间:
如果 没有更新 → 返回 304 Not Modified(只发 headers,不发 body)。
如果 有更新 → 返回 200 OK + 新内容。
Compression
- 客户端请求头
客户端用这些头告诉服务器自己支持哪些压缩方式。
Accept-Encoding: gzip, deflate, br
- 服务器响应头
服务器用这些头告诉客户端实际用了什么压缩方式。
Content-Encoding: gzip
DefaultHeaders
默认两个Header, 也可以自定义
Date: Sun, 28 Sep 2025 04:31:05 GMT
Server: Ktor/3.2.3
DoubleReceive
TODO
ForwardedHeaders
在生产环境中,Web 应用通常不是直接暴露在公网,而是部署在 Nginx / Apache / HAProxy / LoadBalancer / CDN 之后。
这会带来一个问题:
-
你的应用实际监听的是 内网地址(比如 http://127.0.0.1:8080),
-
但用户访问的是 公网地址(比如 https://example.com)。
当代理把请求转发给应用时:
原始的 客户端 IP、协议 (http/https)、Host 信息就会丢失。
这时代理会加上类似这样的头:
X-Forwarded-For: 203.0.113.195 # 原始客户端 IP
X-Forwarded-Proto: https # 原始协议
X-Forwarded-Host: example.com # 原始 Host
FreeMarker/Mustache/JTE/Pebble/Thymeleaf/Velocity
常用于:生成 HTML 页面、邮件内容、文档等。
https://ktor.io/docs/server-templating.html
HSTS
HTTP Strict Transport Security(严格传输安全)
强制浏览器必须用HTTPS请求。
HttpsRedirect
A plugin that redirects all HTTP requests to the HTTPS
XHttpMethodOverride
ktor-server-method-override 插件会拦截请求, 检查是否有 方法覆盖标识,然后修改 call.request.httpMethod。
支持的覆盖方式:
-
HTTP Header X-HTTP-Method-Override: DELETE
-
表单字段
POST /resource,body 里有 _method=DELETE
MicrometerMetrics
自动收集 HTTP 请求指标,并注册到 Micrometer 的 MeterRegistry 里。
PartialContent
安装 PartialContent 插件 后,Ktor 会自动识别请求里的 Range 头,帮你切片返回,支持 206 Partial Content。
Request Header:
Range: bytes=0-1023
Response: 206 Partial Content
p.s. 返回body必须是ReadChannelContent
RateLimit
Rate limiting(限流) = 限制用户在一定时间内的请求次数.
请求次数过多会返回
HTTP/1.1 429 Too Many Requests
RequestValidation
Ktor 里的 请求校验插件(RequestValidation)。装上后,你可以为反序列化后的类型(如 data class)注册校验规则;
当你在路由里 call.receive() 时,会自动触发对应的校验。
StatusPage
StatusPages 是 Ktor 的“统一错误与状态响应”插件:拦截未处理异常或特定状态码,返回自定义内容(HTML/JSON/文本/文件)
Sessions
TODO
SSE
https://ktor.io/docs/server-server-sent-events.html
WebSockets
TODO
Authentication
Basic
用Base64加密用户名和密码, 放在HttpHeader里, 安全性很低。
Ktor中对应的实现:
internal fun constructBasicAuthValue(credentials: BasicAuthCredentials): String {
val authString = "${credentials.username}:${credentials.password}"
val authBuf = authString.toByteArray(Charsets.UTF_8).encodeBase64()
return "Basic $authBuf"
}Basic请求的流程:
- 客户端发送一个不带认证的请求
- 服务端返回401,和www-authenticate Header。 提示需要认证信息。
www-authenticate: Basic realm="Access to the '/' path", charset=UTF-8
- 客户端携带认证信息再次请求。
Authorization: Basic amV0YnJhaW5zOmZvb2Jhcg==
- 服务端校验认证信息, 并返回结果。
Digest
Digest请求的流程:
- 客户端发送一个不带认证的请求
- 服务端返回401,和一次性随机字符串nonce
www-authenticate: Digest realm="Access to the '/' path", nonce="342ca1fbbcb996f0", algorithm=MD5
- 客户端携带认证信息再次请求。认证信息的计算方法为: ktor/ktor-client/ktor-client-plugins/ktor-client-auth/common/src/io/ktor/client/plugins/auth/providers/DigestAuthProvider.kt
override suspend fun addRequestHeaders(request: HttpRequestBuilder, authHeader: HttpAuthHeader?)
Request Header
Authorization: Digest realm="Access to the '/' path", username="jetbrains", nonce="342ca1fbbcb996f0", cnonce="d2434ba276a811f7", response="1353d8e97dc21bea58a27e942347712d", uri="/digest_auth", nc=00000001, algorithm=MD5
- 服务端校验认证信息, 并返回结果。
Bearer
JWT (JSON Web Tokens)
https://www.jwt.io/introduction#when-to-use-json-web-tokens
- JWT 简介
JWT由三部分组成。
Header.Payload.Signature
- Header
{
"alg": "HS256", // 签名算法(例如 HS256、RS256)
"typ": "JWT" // 类型(通常是 JWT)
}这段Json会被编码成Base64Url
- PayLoad 用于存放实际业务数据(用户身份、权限、签发时间等)
JWT 的内容由若干个字段(称为 Claims)组成。
根据规范 RFC 7519 有三类:
- Registered Claims:
https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
- Public
自定义字段。
- Private Claims:
自定义字段。
Payload 的示例:
{
"iss": "my-auth-server",
"sub": "user_12345",
"aud": "my-frontend-app",
"iat": 1736892376,
"nbf": 1736892376,
"exp": 1736895976,
"jti": "9f7e8e6c7a5b42c8a2c3",
"role": "admin",
"scopes": ["read:users", "write:users"]
}Json会被编码成Base64Url.
- Signature 前两段拼在一起,然后用算法(alg) + 密钥进行签名。
计算规则:
Signature = Sign(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
key, ← 使用的密钥类型由 alg 决定
alg ← 使用的算法由 Header 指定
)
常见签名算法(alg)
HS256
RS256
ES256
PS256
- JWT认证流程:
https://ktor.io/docs/server-jwt.html
RS256/HS256签名算法的实装示例:
https://github.com/ktorio/ktor-documentation/tree/3.3.1/codeSnippets/snippets/auth-jwt-hs256 https://github.com/ktorio/ktor-documentation/tree/3.3.1/codeSnippets/snippets/auth-jwt-rs256
- 客户端使用凭证(用户名和密码)向服务器的特定认证路由发送 POST 请求
- 如果凭证有效,服务器会生成一个 JSON Web Token (JWT), 并使用指定的算法对其进行签名。
- 服务器返回 JWT
- 客户端在访问受保护资源时,会在请求头中通过 Authorization: Bearer 方式携带该 Token。
GET http://localhost:8080/hello
Authorization: Bearer {{auth_token}}
- 服务端取得token并进行验证。
- 根据签名算法验证Signature签名。
- 服务端根据payload自定义验证。
- 验证成功后, 返回请求的资源。
openAPI / swaggerUI
TODO
Ktor Client端插件整理
HttpCache
https://ktor.io/docs/client-caching.html
遵循ETag、Last-Modified、Cache-Control 等响应头,自动缓存并复用响应
val client = HttpClient(CIO) {
install(HttpCache)
}HttpCookies
https://ktor.io/docs/client-cookies.htm
默认情况下,插件使用一个内存保持的storage来存cookie, 如果想持久化,需要自定义一个storage。
val client = HttpClient(CIO) {
install(HttpCookies)
}
Logging
https://ktor.io/docs/client-logging.html
ContentNegotiation
https://ktor.io/docs/client-serialization.html
可以把response body反序列化, 也可以把request body序列化。
ContentEncoding
安装插件会添加请求头
Accept-Encoding: gzip
然后根据respond的压缩方式,解压缩。
WebSockets
SSE
SSE client, 接收服务器发送到消息。
client.sse("http://0.0.0.0:8082/sse") {
incoming.collect {
println("Event: ${it.data}")
}
}sse支持取消, 如果所在的协程被cancel了, 链接会断开。
Client的SSE插件可以配置Response buffering, 额外保存一份内存缓存,通过SSESession::bodyBuffer(), 主要作用是出异常时保存信息在Exception里, 用于Debug。
throw mapToSSEException(session.call, session.bodyBuffer(), cause)
HttpPlainText
默认安装的插件
-
请求 (Request) 处理, 当你在 request 里传入 String 作为 body 时,HttpPlainText 会自动把它转成 text/plain 类型的请求体。
-
响应 (Response) 处理, 当服务端返回 Content-Type: text/plain 时,你可以直接用 receive()
BodyProgress
默认安装的插件 用来监听 请求/响应体 (body) 的上传和下载进度。
SaveBody
默认安装的插件, 挂在HttpReceivePipeline::Before。 也就是说收到response是, 立即读取body所有数据并做内存缓存。 提供给后续phase使用, 例如ContentNegotiation。
ResponseObserver
监听和观察每一次 HTTP 响应,方便做日志、统计或调试。
HttpCallValidator
将错误响应,转换成App定义的exception。
BOMRemover
在 收到请求体 时检查是否有 BOM, 如果有就自动去掉。
Auth
Ktor 客户端支持以下三种Http认证,Basic, Digest,Bearer。
通过设置sendWithoutRequest过滤请求,可以不带认证访问服务端。
CallId
在发出的请求上自动附加一个 ID
Resources
类型安全的路由定义插件