Skip to content

Commit a00a871

Browse files
Move RateLimit class from Jicofo.
1 parent 65e2bd7 commit a00a871

File tree

2 files changed

+107
-0
lines changed

2 files changed

+107
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.jitsi.utils
2+
3+
import java.time.Clock
4+
import java.time.Duration
5+
import java.time.Instant
6+
import java.util.Deque
7+
import java.util.LinkedList
8+
9+
/**
10+
* Rate limiting which works as follows:
11+
* - must be at least [minInterval] gap between the requests
12+
* - no more than [maxRequests] requests within the [interval]
13+
*/
14+
class RateLimit(
15+
/** Never accept a request unless at least [minInterval] has passed since the last request */
16+
private val minInterval: Duration = 10.secs,
17+
/** Accept at most [maxRequests] per [interval]. */
18+
private val maxRequests: Int = 3,
19+
/** Accept at most [maxRequests] per [interval]. */
20+
private val interval: Duration = 60.secs,
21+
private val clock: Clock = Clock.systemUTC()
22+
) {
23+
/** Stores the timestamps of requests that have been received. */
24+
private val requests: Deque<Instant> = LinkedList()
25+
26+
/** Return true if the request should be accepted and false otherwise. */
27+
fun accept(): Boolean {
28+
val now = clock.instant()
29+
val previousRequest = requests.peekLast()
30+
if (previousRequest == null) {
31+
requests.add(now)
32+
return true
33+
}
34+
35+
if (Duration.between(previousRequest, now) < minInterval) {
36+
return false
37+
}
38+
39+
// Allow only [maxRequests] requests within the last [interval]
40+
requests.removeIf { Duration.between(it, now) > interval }
41+
if (requests.size >= maxRequests) {
42+
return false
43+
}
44+
requests.add(now)
45+
return true
46+
}
47+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.jitsi.utils
2+
3+
import io.kotest.core.spec.style.ShouldSpec
4+
import io.kotest.matchers.shouldBe
5+
import org.jitsi.utils.time.FakeClock
6+
7+
class RateLimitTest : ShouldSpec() {
8+
init {
9+
context("RateLimit test") {
10+
val clock = FakeClock()
11+
val rateLimit = RateLimit(clock = clock)
12+
13+
should("allow 1st request") {
14+
rateLimit.accept() shouldBe true
15+
}
16+
should("not allow next request immediately") {
17+
rateLimit.accept() shouldBe false
18+
}
19+
clock.elapse(5.secs)
20+
should("not allow next request after 5 seconds") {
21+
rateLimit.accept() shouldBe false
22+
}
23+
clock.elapse(6.secs)
24+
should("allow 2nd request after 11 seconds") {
25+
rateLimit.accept() shouldBe true
26+
}
27+
should("not allow 3rd request after 11 seconds") {
28+
rateLimit.accept() shouldBe false
29+
}
30+
clock.elapse(10.secs)
31+
should("allow 3rd request after 21 seconds") {
32+
rateLimit.accept() shouldBe true
33+
}
34+
clock.elapse(11.secs)
35+
should("not allow more than 3 request within the last minute (31 second)") {
36+
rateLimit.accept() shouldBe false
37+
}
38+
clock.elapse(10.secs)
39+
should("not allow more than 3 request within the last minute (41 second)") {
40+
rateLimit.accept() shouldBe false
41+
}
42+
clock.elapse(10.secs)
43+
should("not allow more than 3 request within the last minute (51 second)") {
44+
rateLimit.accept() shouldBe false
45+
}
46+
clock.elapse(10.secs)
47+
should("allow the 4th request after 60 seconds have passed since the 1st (61 second)") {
48+
rateLimit.accept() shouldBe true
49+
}
50+
clock.elapse(5.secs)
51+
should("not allow the 5th request in 66th second") {
52+
rateLimit.accept() shouldBe false
53+
}
54+
clock.elapse(5.secs)
55+
should("allow the 5th request in 71st second") {
56+
rateLimit.accept() shouldBe true
57+
}
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)