速率限制 (Rate limiting
[//]: # (title: 速率限制 (Rate limiting))
必要的相依性:io.ktor:ktor-server-rate-limit
程式碼範例: rate-limit
RateLimit 外掛程式允許您限制用戶端在特定時間段內可以發送的 請求 數量。 Ktor 提供了多種配置速率限制的方式,例如:
- 您可以為整個應用程式全域啟用速率限制,或為不同的 資源 配置不同的速率限制。
- 您可以根據特定的請求參數配置速率限制:如 IP 位址、API 金鑰或存取權杖等。
新增相依性
若要使用 RateLimit,您需要在建置指令碼中包含 ktor-server-rate-limit 構件:
安裝 RateLimit
若要將 RateLimit 外掛程式 安裝 到應用程式,請將其傳遞給指定
install 函式。 下面的程式碼片段顯示了如何安裝 RateLimit ... - ... 在
embeddedServer函式呼叫中。 - ... 在明確定義的
module(Application類別的擴充函式)中。
配置 RateLimit
總覽
Ktor 使用 權杖桶 (token bucket) 演算法進行速率限制,其工作原理如下:
- 開始時,我們有一個由其容量(即權杖數量)定義的桶。
- 每個傳入請求都會嘗試從桶中消耗一個權杖:
- 如果容量足夠,伺服器會處理請求並發送包含以下標頭的回應:
X-RateLimit-Limit:指定的桶容量。X-RateLimit-Remaining:桶中剩餘的權杖數量。X-RateLimit-Reset:一個 UTC 時間戳記(以秒為單位),指定補充桶的時間。
- 如果容量不足,伺服器會使用
429 Too Many Requests回應拒絕請求,並新增Retry-After標頭,指示用戶端在進行後續請求之前應等待多長時間(以秒為單位)。
- 如果容量足夠,伺服器會處理請求並發送包含以下標頭的回應:
- 經過一段指定的時間後,桶容量會被重新填滿。
註冊速率限制器
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-*標頭: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。
