forked from TykTechnologies/tyk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmiddleware_rate_limiting.go
128 lines (104 loc) · 4.08 KB
/
middleware_rate_limiting.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
package main
import "net/http"
import (
"errors"
"github.com/TykTechnologies/logrus"
"github.com/gorilla/context"
)
var sessionLimiter = SessionLimiter{}
var sessionMonitor = Monitor{}
// RateLimitAndQuotaCheck will check the incomming request and key whether it is within it's quota and
// within it's rate limit, it makes use of the SessionLimiter object to do this
type RateLimitAndQuotaCheck struct {
*TykMiddleware
}
func (mw *RateLimitAndQuotaCheck) GetName() string {
return "RateLimitAndQuotaCheck"
}
// New lets you do any initialisations for the object can be done here
func (k *RateLimitAndQuotaCheck) New() {}
// GetConfig retrieves the configuration from the API config - we user mapstructure for this for simplicity
func (k *RateLimitAndQuotaCheck) GetConfig() (interface{}, error) {
return nil, nil
}
func (k *RateLimitAndQuotaCheck) IsEnabledForSpec() bool {
if k.TykMiddleware.Spec.DisableRateLimit && k.TykMiddleware.Spec.DisableQuota {
return false
}
return true
}
func (k *RateLimitAndQuotaCheck) handleRateLimitFailure(w http.ResponseWriter, r *http.Request, authHeaderValue string) (error, int) {
log.WithFields(logrus.Fields{
"path": r.URL.Path,
"origin": GetIPFromRequest(r),
"key": authHeaderValue,
}).Info("Key rate limit exceeded.")
// Fire a rate limit exceeded event
go k.TykMiddleware.FireEvent(EVENT_RateLimitExceeded,
EVENT_RateLimitExceededMeta{
EventMetaDefault: EventMetaDefault{Message: "Key Rate Limit Exceeded", OriginatingRequest: EncodeRequestToEvent(r)},
Path: r.URL.Path,
Origin: GetIPFromRequest(r),
Key: authHeaderValue,
})
// Report in health check
ReportHealthCheckValue(k.Spec.Health, Throttle, "-1")
return errors.New("Rate limit exceeded"), 429
}
func (k *RateLimitAndQuotaCheck) handleQuotaFailure(w http.ResponseWriter, r *http.Request, authHeaderValue string) (error, int) {
log.WithFields(logrus.Fields{
"path": r.URL.Path,
"origin": GetIPFromRequest(r),
"key": authHeaderValue,
}).Info("Key quota limit exceeded.")
// Fire a quota exceeded event
go k.TykMiddleware.FireEvent(EVENT_QuotaExceeded,
EVENT_QuotaExceededMeta{
EventMetaDefault: EventMetaDefault{Message: "Key Quota Limit Exceeded", OriginatingRequest: EncodeRequestToEvent(r)},
Path: r.URL.Path,
Origin: GetIPFromRequest(r),
Key: authHeaderValue,
})
// Report in health check
ReportHealthCheckValue(k.Spec.Health, QuotaViolation, "-1")
return errors.New("Quota exceeded"), 403
}
// ProcessRequest will run any checks on the request on the way through the system, return an error to have the chain fail
func (k *RateLimitAndQuotaCheck) ProcessRequest(w http.ResponseWriter, r *http.Request, configuration interface{}) (error, int) {
sessionState := context.Get(r, SessionData).(SessionState)
authHeaderValue := context.Get(r, AuthHeaderValue).(string)
storeRef := k.Spec.SessionManager.GetStore()
forwardMessage, reason := sessionLimiter.ForwardMessage(&sessionState,
authHeaderValue,
storeRef,
!k.Spec.DisableRateLimit,
!k.Spec.DisableQuota)
// If either are disabled, save the write roundtrip
if !k.Spec.DisableRateLimit || !k.Spec.DisableQuota {
// Ensure quota and rate data for this session are recorded
if !config.UseAsyncSessionWrite {
k.Spec.SessionManager.UpdateSession(authHeaderValue, sessionState, GetLifetime(k.Spec, &sessionState))
context.Set(r, SessionData, sessionState)
} else {
go k.Spec.SessionManager.UpdateSession(authHeaderValue, sessionState, GetLifetime(k.Spec, &sessionState))
go context.Set(r, SessionData, sessionState)
}
}
log.Debug("SessionState: ", sessionState)
if !forwardMessage {
// TODO Use an Enum!
if reason == 1 {
return k.handleRateLimitFailure(w, r, authHeaderValue)
} else if reason == 2 {
return k.handleQuotaFailure(w, r, authHeaderValue)
}
// Other reason? Still not allowed
return errors.New("Access denied"), 403
}
// Run the trigger monitor
if config.Monitor.MonitorUserKeys {
sessionMonitor.Check(&sessionState, authHeaderValue)
}
// Request is valid, carry on
return nil, 200
}