-
Notifications
You must be signed in to change notification settings - Fork 23
/
limit.go
135 lines (106 loc) · 2.57 KB
/
limit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package rate
import (
"sync"
"time"
"github.com/Conflux-Chain/confura/util/acl"
"golang.org/x/time/rate"
)
type VisitContext struct {
Ip string // visiter IP
Key string // visiter key
Status *acl.VipStatus // visiter VIP status
Resource string // visited resource (also used as limit rule)
}
type Limiter interface {
Allow(vc *VisitContext, n int) bool
GC(timeout time.Duration)
Update(option Option) bool
}
// IpLimiter limiting by IP address
type IpLimiter struct {
*visitLimiter
}
func NewIpLimiter(option Option) *IpLimiter {
return &IpLimiter{
visitLimiter: newVisitLimiter(option.Rate, option.Burst),
}
}
func (l *IpLimiter) Allow(vc *VisitContext, n int) bool {
return l.visitLimiter.Allow(vc.Ip, n)
}
// KeyLimiter limiting by limit key
type KeyLimiter struct {
*visitLimiter
}
func NewKeyLimiter(option Option) *KeyLimiter {
return &KeyLimiter{
visitLimiter: newVisitLimiter(option.Rate, option.Burst),
}
}
func (l *KeyLimiter) Allow(vc *VisitContext, n int) bool {
return l.visitLimiter.Allow(vc.Key, n)
}
type Option struct {
Rate rate.Limit
Burst int
}
func NewOption(r int, b int) Option {
return Option{
Rate: rate.Limit(r),
Burst: b,
}
}
type visitor struct {
limiter *rate.Limiter // token bucket
lastSeen time.Time // used for GC when visitor inactive for a while
}
// visitLimiter is used to limit visit requests in terms of specific entity using
// token bucket algorithm.
type visitLimiter struct {
Option
// limit entity (eg., IP or limit key etc.) => visitor
visitors map[string]*visitor
mu sync.Mutex
}
func newVisitLimiter(rate rate.Limit, burst int) *visitLimiter {
return &visitLimiter{
Option: Option{rate, burst},
visitors: make(map[string]*visitor),
}
}
func (l *visitLimiter) Allow(entity string, n int) bool {
l.mu.Lock()
defer l.mu.Unlock()
v, ok := l.visitors[entity]
if !ok {
v = &visitor{
limiter: rate.NewLimiter(l.Rate, l.Burst),
}
l.visitors[entity] = v
}
v.lastSeen = time.Now()
return v.limiter.AllowN(v.lastSeen, n)
}
func (l *visitLimiter) GC(timeout time.Duration) {
now := time.Now()
l.mu.Lock()
defer l.mu.Unlock()
for entity, v := range l.visitors {
if v.lastSeen.Add(timeout).Before(now) {
delete(l.visitors, entity)
}
}
}
func (l *visitLimiter) Update(option Option) bool {
l.mu.Lock()
defer l.mu.Unlock()
if l.Rate == option.Rate && l.Burst == option.Burst {
return false
}
l.Option = option
for _, visitor := range l.visitors {
visitor.limiter.SetLimit(option.Rate)
visitor.limiter.SetBurst(option.Burst)
}
return true
}