Skip to content
Server Plugin

レート制限

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

コード例: rate-limit

Nativeサーバー
Ktor supports Kotlin/Native and allows you to run a server without an additional runtime or virtual machine.
のサポート: ✅

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

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

依存関係の追加

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

Kotlin
Groovy
XML

RateLimitのインストール

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

モジュール
Modules allow you to structure your application by grouping routes.
内の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("Welcome to the home page! $requestsLeft requests left.")
        }
    }
}

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

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

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: 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