レート制限
必須の依存関係: 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("ホームページへようこそ! 残りリクエスト数: $requestsLeft")
}
}
}
このメソッドは、レートリミッター名も受け入れることができます。
routing {
rateLimit(RateLimitName("protected")) {
get("/protected-api") {
val requestsLeft = call.response.headers["X-RateLimit-Remaining"]
val login = call.request.queryParameters["login"]
call.respondText("保護されたAPIへようこそ、 $login! 残りリクエスト数: $requestsLeft")
}
}
}
例
以下のコードサンプルは、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: リクエストが多すぎます。$retryAfter秒間お待ちください。", status = status)
}
}
routing {
rateLimit {
get("/") {
val requestsLeft = call.response.headers["X-RateLimit-Remaining"]
call.respondText("ホームページへようこそ! 残りリクエスト数: $requestsLeft")
}
}
rateLimit(RateLimitName("public")) {
get("/public-api") {
val requestsLeft = call.response.headers["X-RateLimit-Remaining"]
call.respondText("公開APIへようこそ! 残りリクエスト数: $requestsLeft")
}
}
rateLimit(RateLimitName("protected")) {
get("/protected-api") {
val requestsLeft = call.response.headers["X-RateLimit-Remaining"]
val login = call.request.queryParameters["login"]
call.respondText("保護されたAPIへようこそ、 $login! 残りリクエスト数: $requestsLeft")
}
}
}
}
完全な例はこちらにあります: rate-limit。