1
1
package io .iohk .ethereum .jsonrpc .server .http
2
2
3
+ import java .time .Duration
4
+
5
+ import akka .NotUsed
3
6
import akka .http .scaladsl .model .{RemoteAddress , StatusCodes }
4
7
import akka .http .scaladsl .server .{Directive0 , Route }
5
- import io .iohk .ethereum .db .cache .SimpleLRU
6
8
import io .iohk .ethereum .jsonrpc .server .http .JsonRpcHttpServer .RateLimitConfig
7
9
import akka .http .scaladsl .server .Directives ._
10
+ import com .google .common .base .Ticker
11
+ import com .google .common .cache .CacheBuilder
8
12
import io .iohk .ethereum .jsonrpc .JsonRpcError
9
13
import de .heikoseeberger .akkahttpjson4s .Json4sSupport
10
14
import io .iohk .ethereum .jsonrpc .serialization .JsonSerializers
@@ -15,30 +19,50 @@ class RateLimit(config: RateLimitConfig) extends Directive0 with Json4sSupport {
15
19
private implicit val serialization : Serialization = native.Serialization
16
20
private implicit val formats : Formats = DefaultFormats + JsonSerializers .RpcErrorJsonSerializer
17
21
18
- protected def getCurrentTime : Long = System .currentTimeMillis()
22
+ protected def getCurrentTimeNanos : Long = System .nanoTime()
23
+
24
+ private [this ] val ticker : Ticker = new Ticker {
25
+ override def read (): Long = getCurrentTimeNanos
26
+ }
19
27
20
- lazy val lru = new SimpleLRU [RemoteAddress ](
21
- config.latestTimestampCacheSize,
22
- config.minRequestInterval.toMillis
23
- ) {
24
- override protected def getCurrentTime : Long = RateLimit .this .getCurrentTime
28
+ private [this ] lazy val lru = {
29
+ val nanoDuration = config.minRequestInterval.toNanos
30
+ val javaDuration = Duration .ofNanos(nanoDuration)
31
+ CacheBuilder
32
+ .newBuilder()
33
+ .weakKeys()
34
+ .expireAfterAccess(javaDuration)
35
+ .ticker(ticker)
36
+ .build[RemoteAddress , NotUsed ]()
25
37
}
26
38
27
39
// Such algebras prevent if-elseif-else boilerplate in the JsonRPCServer code
28
40
// It is also guaranteed that:
29
41
// 1) config.enabled is checked only once - on route init
30
42
// 2) no IP address is extracted unless config.enabled is true
31
43
// 3) no LRU is created unless config.enabled is true
44
+ // 4) cache is accessed only once (using get)
32
45
val rateLimitAlgebra : (Unit => Route ) => Route = {
33
46
if (config.enabled) {
34
47
val minInterval = config.minRequestInterval.toSeconds
35
- f =>
48
+ f => {
36
49
extractClientIP { ip =>
37
- if (lru.checkAndRefreshEntry(ip)) {
50
+ var exists = true
51
+ lru.get(
52
+ ip,
53
+ () => {
54
+ exists = false
55
+ NotUsed
56
+ }
57
+ )
58
+ if (exists) {
38
59
val err = JsonRpcError .RateLimitError (minInterval)
39
60
complete((StatusCodes .TooManyRequests , err))
40
- } else f.apply(())
61
+ } else {
62
+ f.apply(())
63
+ }
41
64
}
65
+ }
42
66
} else _.apply(())
43
67
}
44
68
0 commit comments