속도 제한
필수 종속성: 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는 속도 제한을 위해 토큰 버킷 알고리즘을 사용하며, 이는 다음과 같이 작동합니다:
- 처음에 우리는 용량(토큰 수)으로 정의된 버킷을 가집니다.
- 각 들어오는 요청은 버킷에서 하나의 토큰을 소비하려고 시도합니다:
- 용량이 충분하면, 서버는 요청을 처리하고 다음 헤더와 함께 응답을 보냅니다:
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
플러그인을 사용하여 다양한 리소스에 다른 속도 제한기를 적용하는 방법을 보여줍니다. 429 Too Many Requests
응답이 전송된 거부된 요청을 처리하기 위해 StatusPages 플러그인이 사용됩니다.
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.