@@ -2,6 +2,7 @@ package proxy
2
2
3
3
import (
4
4
"regexp"
5
+ "sync"
5
6
"time"
6
7
7
8
"golang.org/x/time/rate"
@@ -31,8 +32,23 @@ type RateLimit struct {
31
32
}
32
33
33
34
type compiledRateLimit struct {
34
- re * regexp.Regexp
35
+ // protects the l402Limiters map.
36
+ sync.Mutex
37
+
38
+ // re is the regular expression used to match the path of the URL.
39
+ re * regexp.Regexp
40
+
41
+ // global limiter is used when no per-L402 key can be derived.
35
42
limiter * rate.Limiter
43
+
44
+ // limiter per L402 key.
45
+ limit rate.Limit
46
+
47
+ // burst is the burst size allowed in addition to steady rate.
48
+ burst int
49
+
50
+ // l402Limiters is a map of per-L402 key limiters.
51
+ l402Limiters map [string ]* rate.Limiter
36
52
}
37
53
38
54
// compile prepares the regular expression and the limiter.
@@ -57,13 +73,62 @@ func (r *RateLimit) compile() error {
57
73
58
74
// rate.Every(per/requests) creates an average rate of requests
59
75
// per 'per'.
60
- lim := rate .NewLimiter (rate .Every (per / time .Duration (requests )), burst )
61
- r .compiled = & compiledRateLimit {re : re , limiter : lim }
76
+ limit := rate .Every (per / time .Duration (requests ))
77
+ lim := rate .NewLimiter (limit , burst )
78
+ r .compiled = & compiledRateLimit {
79
+ re : re ,
80
+ limiter : lim ,
81
+ limit : limit ,
82
+ burst : burst ,
83
+ l402Limiters : make (map [string ]* rate.Limiter ),
84
+ }
62
85
63
86
return nil
64
87
}
65
88
66
- // allow returns true if the rate limit permits an event now.
67
- func (c * compiledRateLimit ) allow () bool {
68
- return c .limiter .Allow ()
89
+ // allowFor returns true if the rate limit permits an event now for the given
90
+ // key. If the key is empty, the global limiter is used.
91
+ func (c * compiledRateLimit ) allowFor (key string ) bool {
92
+ if key == "" {
93
+ return c .limiter .Allow ()
94
+ }
95
+ l := c .getOrCreate (key )
96
+
97
+ return l .Allow ()
98
+ }
99
+
100
+ // reserveDelay reserves a token on the limiter for the given key and returns
101
+ // the suggested delay. Callers can use the delay to set Retry-After without
102
+ // consuming tokens.
103
+ func (c * compiledRateLimit ) reserveDelay (key string ) (time.Duration , bool ) {
104
+ var l * rate.Limiter
105
+ if key == "" {
106
+ l = c .limiter
107
+ } else {
108
+ l = c .getOrCreate (key )
109
+ }
110
+
111
+ res := l .Reserve ()
112
+ if ! res .OK () {
113
+ return 0 , false
114
+ }
115
+
116
+ delay := res .Delay ()
117
+ res .CancelAt (time .Now ())
118
+
119
+ return delay , true
120
+ }
121
+
122
+ func (c * compiledRateLimit ) getOrCreate (key string ) * rate.Limiter {
123
+ c .Lock ()
124
+ defer c .Unlock ()
125
+
126
+ if l , ok := c .l402Limiters [key ]; ok {
127
+ return l
128
+ }
129
+
130
+ l := rate .NewLimiter (c .limit , c .burst )
131
+ c .l402Limiters [key ] = l
132
+
133
+ return l
69
134
}
0 commit comments