-
Notifications
You must be signed in to change notification settings - Fork 28
/
rate_limiter.go
102 lines (96 loc) · 3.39 KB
/
rate_limiter.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
package api
import (
"fmt"
"github.com/Peripli/service-manager/api/filters"
"github.com/ulule/limiter"
"github.com/ulule/limiter/drivers/middleware/stdlib"
"github.com/ulule/limiter/drivers/store/memory"
"path"
"strings"
)
func validateRateLimiterConfiguration(config string) error {
_, err := parseRateLimiterConfiguration(config)
if err != nil {
return err
}
return nil
}
type RateLimiterConfiguration struct {
rate limiter.Rate
pathPrefix string
}
func createRateLimiterConfigurationSectionError(index int, section string, details string) error {
return fmt.Errorf("invalid rate limiter configuration in section #%d: '%s', %s", index+1, section, details)
}
/**
* Rate limit custom path format syntax:
* rate<:path><,rate<:path>,...>
* Examples:
* Single rate (no path specified - targets any path):
* `5-M` (identical to `5-M:/`) --- 5 req per minute on any path
* Single rate on specific path:
* `5-M:/v1/endpoint` --- 5 requests per minute on path starting with /v1/endpoint
* Multiple rates:
* `5-M:/v1/endpoint,10-M:/v2/endpoint` --- 5 requests per minute on /v1/endpoint, 10 rpm on /v2/endpoint
* Complex scenario:
* `10000-H,1000-M,5-M:/v1/endpoint` --- 10000 requests per hour on any path, 1000 per minute on any path, 5 requests per minute on /v1/endpoint
*/
func parseRateLimiterConfiguration(input string) ([]RateLimiterConfiguration, error) {
var configurations []RateLimiterConfiguration
input = strings.TrimSpace(input)
if len(input) == 0 {
return configurations, nil
}
for index, section := range strings.Split(input, ",") {
if len(section) == 0 {
return nil, createRateLimiterConfigurationSectionError(index, section, "no content, expected 'rate:path' format")
}
rateAndPath := strings.Split(section, ":")
if len(rateAndPath) > 2 {
return nil, createRateLimiterConfigurationSectionError(index, section, "too many elements, expected 'rate:path' format")
}
rateConfig := rateAndPath[0]
rate, err := limiter.NewRateFromFormatted(rateConfig)
if err != nil {
return nil, createRateLimiterConfigurationSectionError(index, section, "unable to parse rate: "+err.Error())
}
pathPrefix := "/"
if len(rateAndPath) == 2 {
pathPrefix = rateAndPath[1]
if pathPrefix == "" {
return nil, createRateLimiterConfigurationSectionError(index, section, "path should not be empty")
}
if !strings.HasPrefix(pathPrefix, "/") {
return nil, createRateLimiterConfigurationSectionError(index, section, "path should start with /")
}
if path.Clean(pathPrefix) != pathPrefix {
return nil, createRateLimiterConfigurationSectionError(index, section, "path is not clean, expected path '"+path.Clean(pathPrefix)+"'")
}
}
configurations = append(configurations, RateLimiterConfiguration{
rate: rate,
pathPrefix: pathPrefix,
})
}
return configurations, nil
}
func initRateLimiters(options *Options) ([]filters.RateLimiterMiddleware, error) {
var rateLimiters []filters.RateLimiterMiddleware
if !options.APISettings.RateLimitingEnabled {
return nil, nil
}
configurations, err := parseRateLimiterConfiguration(options.APISettings.RateLimit)
if err != nil {
return nil, err
}
for _, configuration := range configurations {
rateLimiters = append(
rateLimiters,
filters.NewRateLimiterMiddleware(
stdlib.NewMiddleware(limiter.New(memory.NewStore(), configuration.rate)),
configuration.pathPrefix,
),
)
}
return rateLimiters, nil
}