Does anyone have an example of implementing Redis ...
# ktor
t
Does anyone have an example of implementing Redis with the Rate Limiter plugin?
Hey! After a bit of experimenting I managed to get this working
Copy code
private class RedisRateLimiter(
    private val key: String,
    private val limit: Int,
    private val refillPeriod: Duration,
    private val clock: () -> Long = ::getTimeMillis,
): RateLimiter, KoinComponent {
    private val jedis by inject<JedisPool>()

    override suspend fun tryConsume(tokens: Int): RateLimiter.State {
        val k = buildKey(key)

        jedis.resource.use { j ->
            val current = j.get(k)
                ?.toIntOrNull()
                ?: 0

            val next = current + tokens
            val now = clock()

            val refillTime = j.get("$k:refill")
                ?.toLongOrNull()
                ?: now

            if (now >= refillTime) {
                j.setex(k, refillPeriod.inWholeSeconds, tokens.toString())
                j.setex("$k:refill", refillPeriod.inWholeSeconds, nextRefillTime(now, refillTime).toString())

                return RateLimiter.State.Available(
                    remainingTokens = limit - tokens,
                    limit = limit,
                    refillAtTimeMillis = now + refillPeriod.inWholeMilliseconds
                )
            }

            if (next <= limit) {
                j.setex(k, refillPeriod.inWholeSeconds, next.toString())

                return RateLimiter.State.Available(
                    remainingTokens = limit - next,
                    limit = limit,
                    refillAtTimeMillis = refillTime
                )
            }

            return RateLimiter.State.Exhausted(
                toWait = (refillTime - now).toDuration(DurationUnit.MILLISECONDS)
            )
        }
    }

    private fun nextRefillTime(now: Long, refillTime: Long): Long {
        return if (now >= refillTime) {
            now + refillPeriod.inWholeMilliseconds
        } else {
            refillTime
        }
    }

    private fun buildKey(key: String) = "$RATE_LIMIT_KEY:$key"

    companion object {
        private const val RATE_LIMIT_KEY = "service:rate_limit"
    }
}
Obviously not the fanciest code but it seems to work. Then when I register my ratelimiter I see the request key to the identifier of the principal of the user if I know it'll be set in the route they're in.