レート制限
必須依存関係: io.ktor:ktor-server-rate-limit
コード例: rate-limit
RateLimitプラグインを使用すると、クライアントが特定の期間内に行うことができるリクエストの数を制限できます。 Ktorは、レート制限を構成するためのさまざまな手段を提供しています。例えば:
- アプリケーション全体に対してグローバルにレート制限を有効にしたり、異なるリソースごとに異なるレート制限を構成したりできます。
- IPアドレス、APIキー、アクセストークンなどの特定の要求パラメータに基づいてレート制限を構成できます。
依存関係の追加
RateLimitを使用するには、ビルドスクリプトにktor-server-rate-limitアーティファクトを含める必要があります。
RateLimitのインストール
RateLimitプラグインをアプリケーションにインストールするには、指定された
install関数に渡します。 以下のコードスニペットは、RateLimitをインストールする方法を示しています... - ...
embeddedServer関数の呼び出し内。 - ...
Applicationクラスの拡張関数である、明示的に定義されたmodule内。
RateLimitの構成
概要
Ktorはレート制限に「トークンバケットアルゴリズム」を使用しており、以下のように動作します。
- 最初に、トークンの数によって定義されるキャパシティ(容量)を持つバケットがあります。
- 各受信リクエストは、バケットから1つのトークンを消費しようとします。
- 十分なキャパシティがある場合、サーバーはリクエストを処理し、以下のヘッダーを含むレスポンスを送信します。
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メソッドは、2つのパラメータを使用してレートリミッターを作成します。limitはバケットのキャパシティを定義し、refillPeriodはこのバケットの補充期間を指定します。 以下の例のレートリミッターは、1分間に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
