forked from zalando/skipper
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ratelimit.go
192 lines (165 loc) · 5.06 KB
/
ratelimit.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package ratelimit
import (
"fmt"
"net/http"
"time"
circularbuffer "github.com/szuecs/rate-limit-buffer"
"github.com/zalando/skipper/net"
)
// Type defines the type of the used breaker: consecutive, rate or
// disabled.
type Type int
const (
// Header is
Header = "X-Rate-Limit"
// ServiceRatelimitName is the name of the Ratelimit filter, which will be shown in log
ServiceRatelimitName = "ratelimit"
// LocalRatelimitName is the name of the LocalRatelimit filter, which will be shown in log
LocalRatelimitName = "localRatelimit"
// DisableRatelimitName is the name of the DisableRatelimit, which will be shown in log
DisableRatelimitName = "disableRatelimit"
)
const (
// NoRatelimit is not used
NoRatelimit Type = iota
// ServiceRatelimit is used to have a simple rate limit for a
// backend service, which is calculated and measured within
// each instance
ServiceRatelimit
// LocalRatelimit is used to have a simple local rate limit
// per user for a backend, which is calculated and measured
// within each instance
LocalRatelimit
// DisableRatelimit is used to disable rate limit
DisableRatelimit
)
// Lookuper makes it possible to be more flexible for ratelimiting.
type Lookuper interface {
// Lookup is used to get the string which is used to define
// how the bucket of a ratelimiter looks like, which is used
// to decide to ratelimit or not. For example you can use the
// X-Forwarded-For Header if you want to rate limit based on
// source ip behind a proxy/loadbalancer or the Authorization
// Header for request per token or user.
Lookup(*http.Request) string
}
// SameBucketLookuper implements Lookuper interface and will always
// match to the same bucket.
type SameBucketLookuper struct{}
// NewSameBucketLookuper returns a SameBucketLookuper.
func NewSameBucketLookuper() SameBucketLookuper {
return SameBucketLookuper{}
}
// Lookup will always return "s" to select the same bucket.
func (SameBucketLookuper) Lookup(*http.Request) string {
return "s"
}
// XForwardedForLookuper implements Lookuper interface and will
// select a bucket by X-Forwarded-For header or clientIP.
type XForwardedForLookuper struct{}
// NewXForwardedForLookuper returns an empty XForwardedForLookuper
func NewXForwardedForLookuper() XForwardedForLookuper {
return XForwardedForLookuper{}
}
// Lookup returns the content of the X-Forwarded-For header or the
// clientIP if not set.
func (XForwardedForLookuper) Lookup(req *http.Request) string {
return net.RemoteHost(req).String()
}
// AuthLookuper implements Lookuper interface and will select a bucket
// by Authorization header.
type AuthLookuper struct{}
// NewAuthLookuper returns an empty AuthLookuper
func NewAuthLookuper() AuthLookuper {
return AuthLookuper{}
}
// Lookup returns the content of the Authorization header.
func (AuthLookuper) Lookup(req *http.Request) string {
return req.Header.Get("Authorization")
}
// Settings configures the chosen rate limiter
type Settings struct {
Type Type
Lookuper Lookuper
Host string
MaxHits int
TimeWindow time.Duration
CleanInterval time.Duration
}
func (s Settings) Empty() bool {
return s == Settings{}
}
func (to Settings) mergeSettings(from Settings) Settings {
if to.Type == NoRatelimit {
to.Type = from.Type
}
if to.MaxHits == 0 {
to.MaxHits = from.MaxHits
}
if to.TimeWindow == 0 {
to.TimeWindow = from.TimeWindow
}
if to.CleanInterval == 0 {
to.CleanInterval = from.CleanInterval
}
return to
}
func (s Settings) String() string {
switch s.Type {
case DisableRatelimit:
return "disable"
case ServiceRatelimit:
return fmt.Sprintf("ratelimit(type=service,max-hits=%d,time-window=%s)", s.MaxHits, s.TimeWindow)
case LocalRatelimit:
return fmt.Sprintf("ratelimit(type=local,max-hits=%d,time-window=%s)", s.MaxHits, s.TimeWindow)
default:
return "non"
}
}
type implementation interface {
// Allow is used to get a decision if you should allow the call to pass or to ratelimit
Allow(string) bool
// Close is used to clean up underlying implementations, if you want to stop a Ratelimiter
Close()
}
// Ratelimit is a proxy objects that delegates to implemetations and
// stores settings for the ratelimiter
type Ratelimit struct {
settings Settings
ts time.Time
impl implementation
}
// Allow returns true if the s is not ratelimited, false if it is
// ratelimited
func (l *Ratelimit) Allow(s string) bool {
if l == nil {
return true
}
return l.impl.Allow(s)
}
// Close will stop a cleanup goroutines in underlying implementation.
func (l *Ratelimit) Close() {
l.impl.Close()
}
type voidRatelimit struct{}
// Allow always returns true, not ratelimited
func (l voidRatelimit) Allow(string) bool {
return true
}
func (l voidRatelimit) Close() {
}
func newRatelimit(s Settings) *Ratelimit {
var impl implementation
switch s.Type {
case ServiceRatelimit:
impl = circularbuffer.NewRateLimiter(s.MaxHits, s.TimeWindow)
case LocalRatelimit:
impl = circularbuffer.NewClientRateLimiter(s.MaxHits, s.TimeWindow, s.CleanInterval)
default:
impl = voidRatelimit{}
}
return &Ratelimit{
settings: s,
impl: impl,
}
}