Skip to content

Commit 9a35a58

Browse files
Move RateLimit class from Jicofo. (#151)
* Move RateLimit class from Jicofo. * Allow "now" parameter to be passed to RateLimit.accept().
1 parent 65e2bd7 commit 9a35a58

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Jicofo, the Jitsi Conference Focus.
3+
*
4+
* Copyright @ 2015-Present 8x8, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.jitsi.utils
19+
20+
import java.time.Clock
21+
import java.time.Duration
22+
import java.time.Instant
23+
import java.util.Deque
24+
import java.util.LinkedList
25+
26+
/**
27+
* Rate limiting which works as follows:
28+
* - must be at least [minInterval] gap between the requests
29+
* - no more than [maxRequests] requests within the [interval]
30+
*/
31+
class RateLimit(
32+
/** Never accept a request unless at least [minInterval] has passed since the last request */
33+
private val minInterval: Duration = 10.secs,
34+
/** Accept at most [maxRequests] per [interval]. */
35+
private val maxRequests: Int = 3,
36+
/** Accept at most [maxRequests] per [interval]. */
37+
private val interval: Duration = 60.secs,
38+
private val clock: Clock = Clock.systemUTC()
39+
) {
40+
/** Stores the timestamps of requests that have been received. */
41+
private val requests: Deque<Instant> = LinkedList()
42+
43+
/** Return true if the request should be accepted and false otherwise. */
44+
@JvmOverloads
45+
fun accept(now: Instant = clock.instant()): Boolean {
46+
val previousRequest = requests.peekLast()
47+
if (previousRequest == null) {
48+
requests.add(now)
49+
return true
50+
}
51+
52+
if (Duration.between(previousRequest, now) < minInterval) {
53+
return false
54+
}
55+
56+
// Allow only [maxRequests] requests within the last [interval]
57+
requests.removeIf { Duration.between(it, now) > interval }
58+
if (requests.size >= maxRequests) {
59+
return false
60+
}
61+
requests.add(now)
62+
return true
63+
}
64+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Jicofo, the Jitsi Conference Focus.
3+
*
4+
* Copyright @ 2018 - present 8x8, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.jitsi.utils
19+
20+
import io.kotest.core.spec.style.ShouldSpec
21+
import io.kotest.matchers.shouldBe
22+
import org.jitsi.utils.time.FakeClock
23+
24+
class RateLimitTest : ShouldSpec() {
25+
init {
26+
context("RateLimit test") {
27+
val clock = FakeClock()
28+
val rateLimit = RateLimit(clock = clock)
29+
30+
should("allow 1st request") {
31+
rateLimit.accept() shouldBe true
32+
}
33+
should("not allow next request immediately") {
34+
rateLimit.accept() shouldBe false
35+
}
36+
clock.elapse(5.secs)
37+
should("not allow next request after 5 seconds") {
38+
rateLimit.accept() shouldBe false
39+
}
40+
clock.elapse(6.secs)
41+
should("allow 2nd request after 11 seconds") {
42+
rateLimit.accept() shouldBe true
43+
}
44+
should("not allow 3rd request after 11 seconds") {
45+
rateLimit.accept() shouldBe false
46+
}
47+
clock.elapse(10.secs)
48+
should("allow 3rd request after 21 seconds") {
49+
rateLimit.accept() shouldBe true
50+
}
51+
clock.elapse(11.secs)
52+
should("not allow more than 3 request within the last minute (31 second)") {
53+
rateLimit.accept() shouldBe false
54+
}
55+
clock.elapse(10.secs)
56+
should("not allow more than 3 request within the last minute (41 second)") {
57+
rateLimit.accept() shouldBe false
58+
}
59+
clock.elapse(10.secs)
60+
should("not allow more than 3 request within the last minute (51 second)") {
61+
rateLimit.accept() shouldBe false
62+
}
63+
clock.elapse(10.secs)
64+
should("allow the 4th request after 60 seconds have passed since the 1st (61 second)") {
65+
rateLimit.accept() shouldBe true
66+
}
67+
clock.elapse(5.secs)
68+
should("not allow the 5th request in 66th second") {
69+
rateLimit.accept() shouldBe false
70+
}
71+
clock.elapse(5.secs)
72+
should("allow the 5th request in 71st second") {
73+
rateLimit.accept() shouldBe true
74+
}
75+
val then = clock.instant()
76+
clock.elapse(10.secs)
77+
should("determine result by passed-in time if present") {
78+
rateLimit.accept(then) shouldBe false
79+
rateLimit.accept() shouldBe true
80+
}
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)