限流
所需依赖项: io.ktor:ktor-server-rate-limit
代码示例: rate-limit
RateLimit 插件允许您限制客户端在特定时间段内可以发出的请求数量。 Ktor 提供了多种配置限流的方式,例如:
- 您可以为整个应用程序全局启用限流,或为不同的资源配置不同的限流规则。
- 您可以根据特定的请求参数配置限流:例如 IP 地址、API 密钥或访问令牌等。
添加依赖项
要使用 RateLimit
,您需要在构建脚本中包含 ktor-server-rate-limit
artifact:
安装 RateLimit
要安装 RateLimit
插件到应用程序, 请在指定的
install
函数。 下面的代码片段展示了如何安装 RateLimit
... - ... 在
embeddedServer
函数调用内部。 - ... 在显式定义的
module
内部,它是一个Application
类的扩展函数。
配置 RateLimit
概览
Ktor 使用令牌桶算法进行限流,其工作原理如下:
- 最初,我们有一个由其容量(即令牌数量)定义的桶。
- 每个传入的请求都尝试从桶中消耗一个令牌:
- 如果容量充足,服务器将处理请求并发送包含以下 HTTP 头的响应:
X-RateLimit-Limit
:指定的桶容量。X-RateLimit-Remaining
:桶中剩余的令牌数量。X-RateLimit-Reset
:一个 UTC 时间戳(以秒为单位),指定桶的重新填充时间。
- 如果容量不足,服务器将使用
429 Too Many Requests
响应拒绝请求,并添加Retry-After
HTTP 头,指示客户端应等待多长时间(以秒为单位)才能发起后续请求。
- 如果容量充足,服务器将处理请求并发送包含以下 HTTP 头的响应:
- 在指定的时间段后,桶容量会重新填充。
注册限流器
Ktor 允许您将限流应用于整个应用程序或特定路由:
要将限流应用于整个应用程序,请调用
global
方法并传递一个已配置的限流器。kotlininstall(RateLimit) { global { rateLimiter(limit = 5, refillPeriod = 60.seconds) } }
register
方法注册一个限流器,该限流器可应用于特定路由。kotlininstall(RateLimit) { register { rateLimiter(limit = 5, refillPeriod = 60.seconds) } }
上面的代码示例展示了 RateLimit
插件的最小配置,但对于使用 register
方法注册的限流器,您还需要将其应用于特定路由。
配置限流
在本节中,我们将了解如何配置限流:
(可选)
register
方法允许您指定一个限流器名称,该名称可用于将限流规则应用于特定路由:kotlininstall(RateLimit) { register(RateLimitName("protected")) { // ... } }
rateLimiter
方法创建一个限流器,带有两个形参:limit
定义桶容量,而refillPeriod
指定此桶的重新填充周期。下面的示例中的限流器允许每分钟处理 30 个请求:kotlinregister(RateLimitName("protected")) { rateLimiter(limit = 30, refillPeriod = 60.seconds) }
(可选)
requestKey
允许您指定一个为请求返回键的函数。具有不同键的请求拥有独立的限流。在下面的示例中,login
查询形参 是用于区分不同用户的键:kotlinregister(RateLimitName("protected")) { requestKey { applicationCall -> applicationCall.request.queryParameters["login"]!! } }
请注意,键应该具有良好的
equals
和hashCode
实现。(可选)
requestWeight
设置一个函数,该函数返回一个请求消耗的令牌数量。在下面的示例中,请求键用于配置请求权重:kotlinregister(RateLimitName("protected")) { requestKey { applicationCall -> applicationCall.request.queryParameters["login"]!! } requestWeight { applicationCall, key -> when(key) { "jetbrains" -> 1 else -> 2 } } }
(可选)
modifyResponse
允许您覆盖每个请求发送的默认X-RateLimit-*
HTTP 头:kotlinregister(RateLimitName("protected")) { modifyResponse { applicationCall, state -> applicationCall.response.header("X-RateLimit-Custom-Header", "Some value") } }
定义限流作用域
配置限流器后,您可以使用 rateLimit
方法将其规则应用于特定路由:
routing {
rateLimit {
get("/") {
val requestsLeft = call.response.headers["X-RateLimit-Remaining"]
call.respondText("Welcome to the home page! $requestsLeft requests left.")
}
}
}
此方法也可以接受一个限流器名称:
routing {
rateLimit(RateLimitName("protected")) {
get("/protected-api") {
val requestsLeft = call.response.headers["X-RateLimit-Remaining"]
val login = call.request.queryParameters["login"]
call.respondText("Welcome to protected API, $login! $requestsLeft requests left.")
}
}
}
示例
下面的代码示例演示了如何使用 RateLimit
插件将不同的限流器应用于不同的资源。StatusPages 插件用于处理被拒绝的请求,即那些发送了 429 Too Many Requests
响应的请求。
package com.example
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.ratelimit.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlin.time.Duration.Companion.seconds
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module() {
install(RateLimit) {
register {
rateLimiter(limit = 5, refillPeriod = 60.seconds)
}
register(RateLimitName("public")) {
rateLimiter(limit = 10, refillPeriod = 60.seconds)
}
register(RateLimitName("protected")) {
rateLimiter(limit = 30, refillPeriod = 60.seconds)
requestKey { applicationCall ->
applicationCall.request.queryParameters["login"]!!
}
requestWeight { applicationCall, key ->
when(key) {
"jetbrains" -> 1
else -> 2
}
}
}
}
install(StatusPages) {
status(HttpStatusCode.TooManyRequests) { call, status ->
val retryAfter = call.response.headers["Retry-After"]
call.respondText(text = "429: Too many requests. Wait for $retryAfter seconds.", status = status)
}
}
routing {
rateLimit {
get("/") {
val requestsLeft = call.response.headers["X-RateLimit-Remaining"]
call.respondText("Welcome to the home page! $requestsLeft requests left.")
}
}
rateLimit(RateLimitName("public")) {
get("/public-api") {
val requestsLeft = call.response.headers["X-RateLimit-Remaining"]
call.respondText("Welcome to public API! $requestsLeft requests left.")
}
}
rateLimit(RateLimitName("protected")) {
get("/protected-api") {
val requestsLeft = call.response.headers["X-RateLimit-Remaining"]
val login = call.request.queryParameters["login"]
call.respondText("Welcome to protected API, $login! $requestsLeft requests left.")
}
}
}
}
您可以在此处找到完整的示例:rate-limit。