Ktor学习

Wed Sep 10 2025

最近想补一下网络编程的知识,最热门的肯定是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

image3

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

  1. ETag

服务器会在响应里加上 ETag 头,通常是一个哈希值或版本号。

客户端下次请求时,可以带上 If-None-Match 头,把上次的 ETag 发给服务器。 (-H 'If-None-Match: "abc123"')

服务器比对 ETag:

一样:说明资源没变 → 返回 304 Not Modified(不传 body,客户端用缓存)。

不一样:说明资源更新了 → 返回 200 OK(新内容 + 新的 ETag)。

  1. 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 之后。

这会带来一个问题:

当代理把请求转发给应用时:

原始的 客户端 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请求的流程:

  1. 客户端发送一个不带认证的请求
  2. 服务端返回401,和www-authenticate Header。 提示需要认证信息。
www-authenticate: Basic realm="Access to the '/' path", charset=UTF-8
  1. 客户端携带认证信息再次请求。
Authorization: Basic amV0YnJhaW5zOmZvb2Jhcg==
  1. 服务端校验认证信息, 并返回结果。

Digest

Digest请求的流程:

  1. 客户端发送一个不带认证的请求
  2. 服务端返回401,和一次性随机字符串nonce
www-authenticate: Digest realm="Access to the '/' path", nonce="342ca1fbbcb996f0", algorithm=MD5
  1. 客户端携带认证信息再次请求。认证信息的计算方法为: 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
  1. 服务端校验认证信息, 并返回结果。

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

  1. 客户端使用凭证(用户名和密码)向服务器的特定认证路由发送 POST 请求
  2. 如果凭证有效,服务器会生成一个 JSON Web Token (JWT), 并使用指定的算法对其进行签名。
  3. 服务器返回 JWT
  4. 客户端在访问受保护资源时,会在请求头中通过 Authorization: Bearer 方式携带该 Token。
GET http://localhost:8080/hello
Authorization: Bearer {{auth_token}}
  1. 服务端取得token并进行验证。
    • 根据签名算法验证Signature签名。
    • 服务端根据payload自定义验证。
  2. 验证成功后, 返回请求的资源。

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

类型安全的路由定义插件

← Back to home