Skip to content
Server Plugin

レート制限

必須の依存関係: io.ktor:ktor-server-rate-limit

コード例: rate-limit

ネイティブサーバー
モジュールを使用すると、ルートをグループ化してアプリケーションを構造化できます。
のサポート: ✅

RateLimitプラグインを使用すると、クライアントが特定の期間内に行えるリクエストの数を制限できます。 Ktorは、レート制限を設定するためのさまざまな方法を提供します。例えば、

  • アプリケーション全体に対してグローバルにレート制限を有効にしたり、異なるリソースに対して異なるレート制限を設定したりできます。
  • IPアドレス、APIキー、アクセストークンなどの特定のリクエストパラメータに基づいてレート制限を設定できます。

依存関係の追加

RateLimitを使用するには、ビルドスクリプトにktor-server-rate-limitアーティファクトを含める必要があります。

Kotlin
Groovy
XML

RateLimitのインストール

RateLimitプラグインをアプリケーションにインストールするには、指定された

モジュール
モジュールを使用すると、ルートをグループ化してアプリケーションを構造化できます。
内のinstall関数に渡します。 以下のコードスニペットは、RateLimitのインストール方法を示しています...

  • ... embeddedServer関数呼び出し内。
  • ... Applicationクラスの拡張関数である、明示的に定義されたmodule内。
kotlin
kotlin

RateLimitの設定

概要

Ktorはレート制限に_トークンバケット_アルゴリズムを使用しており、これは次のように機能します。

  1. まず、容量(トークンの数)によって定義されるバケットがあります。
  2. 各受信リクエストは、バケットから1つのトークンを消費しようとします。
    • 十分な容量がある場合、サーバーはリクエストを処理し、以下のヘッダーを含むレスポンスを送信します。
      • X-RateLimit-Limit: 指定されたバケット容量。
      • X-RateLimit-Remaining: バケットに残っているトークンの数。
      • X-RateLimit-Reset: バケットが補充される時刻を指定するUTCタイムスタンプ(秒単位)。
    • 容量が不足している場合、サーバーは429 Too Many Requestsレスポンスを使用してリクエストを拒否し、クライアントが次のリクエストを行うまでに待つべき時間(秒単位)を示すRetry-Afterヘッダーを追加します。
  3. 指定された期間の後、バケットの容量は補充されます。

レートリミッターの登録

Ktorでは、アプリケーション全体にグローバルにレート制限を適用したり、特定のルートに適用したりできます。

  • アプリケーション全体にレート制限を適用するには、globalメソッドを呼び出し、設定済みのレートリミッターを渡します。

    kotlin
    install(RateLimit) {
        global {
            rateLimiter(limit = 5, refillPeriod = 60.seconds)
        }
    }
  • registerメソッドは、特定のルートに適用できるレートリミッターを登録します。

    kotlin
    install(RateLimit) {
        register {
            rateLimiter(limit = 5, refillPeriod = 60.seconds)
        }
    }

上記のコードサンプルは、RateLimitプラグインの最小限の設定を示していますが、registerメソッドを使用して登録されたレートリミッターについては、特定のルートに適用する必要もあります。

レート制限の設定

このセクションでは、レート制限を設定する方法を説明します。

  1. (オプション)registerメソッドを使用すると、特定のルートにレート制限ルールを適用するために使用できるレートリミッター名を指定できます。

    kotlin
        install(RateLimit) {
            register(RateLimitName("protected")) {
                // ...
            }
        }
  2. rateLimiterメソッドは2つのパラメータを持つレートリミッターを作成します。limitはバケット容量を定義し、refillPeriodはこのバケットのリフィル期間を指定します。 以下の例のレートリミッターは、1分あたり30リクエストを処理できます。

    kotlin
    register(RateLimitName("protected")) {
        rateLimiter(limit = 30, refillPeriod = 60.seconds)
    }
  3. (オプション)requestKeyを使用すると、リクエストのキーを返す関数を指定できます。 異なるキーを持つリクエストは、独立したレート制限を持ちます。 以下の例では、loginクエリパラメータが異なるユーザーを区別するためのキーとして使用されています。

    kotlin
    register(RateLimitName("protected")) {
        requestKey { applicationCall ->
            applicationCall.request.queryParameters["login"]!!
        }
    }

    キーは適切なequalsおよびhashCodeの実装を持つ必要があることに注意してください。

  4. (オプション)requestWeightは、リクエストによって消費されるトークン数を返す関数を設定します。 以下の例では、リクエストキーを使用してリクエストの重みを設定しています。

    kotlin
    register(RateLimitName("protected")) {
        requestKey { applicationCall ->
            applicationCall.request.queryParameters["login"]!!
        }
        requestWeight { applicationCall, key ->
            when(key) {
                "jetbrains" -> 1
                else -> 2
            }
        }
    }
  5. (オプション)modifyResponseを使用すると、各リクエストとともに送信されるデフォルトのX-RateLimit-*ヘッダーを上書きできます。

    kotlin
    register(RateLimitName("protected")) {
        modifyResponse { applicationCall, state ->
            applicationCall.response.header("X-RateLimit-Custom-Header", "Some value")
        }
    }

レート制限スコープの定義

レートリミッターを設定した後、rateLimitメソッドを使用してそのルールを特定のルートに適用できます。

kotlin
routing {
    rateLimit {
        get("/") {
            val requestsLeft = call.response.headers["X-RateLimit-Remaining"]
            call.respondText("ホームページへようこそ! 残りリクエスト数: $requestsLeft")
        }
    }
}

このメソッドは、レートリミッター名も受け入れることができます。

kotlin
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レスポンスが送信された、拒否されたリクエストを処理するために使用されます。

kotlin
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