mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 06:16:37 +08:00
feat(rate-limit): add default request rate limit configuration
- Support openresty_default_limit_req_per_ip in system_configs. - Add limit_req and limit_req_status 429 directive generation in openresty renderer. - Implement route-level limit_req_per_ip override and explicit disable. - Add frontend UI inputs and validation in rate limits tab config. - Update swagger API docs and changelog for v3.4.3-beta.3.
This commit is contained in:
@@ -201,7 +201,7 @@ func renderTemplateDirective(enabled bool, statement string) string {
|
||||
}
|
||||
|
||||
func renderOpenRestyCacheTemplateBlock(cfg ConfigSnapshot) string {
|
||||
lines := []string{renderOpenRestyLimitZoneBlock()}
|
||||
lines := []string{renderOpenRestyLimitZoneBlock(cfg)}
|
||||
if !cfg.CacheEnabled {
|
||||
lines = append(lines, renderOpenRestyObservabilityTemplateBlock())
|
||||
return strings.Join(lines, "")
|
||||
@@ -222,8 +222,14 @@ func renderOpenRestyCacheTemplateBlock(cfg ConfigSnapshot) string {
|
||||
return strings.Join(lines, "")
|
||||
}
|
||||
|
||||
func renderOpenRestyLimitZoneBlock() string {
|
||||
return " limit_conn_zone $server_name zone=openflare_conn_per_server:10m;\n limit_conn_zone $binary_remote_addr zone=openflare_conn_per_ip:10m;\n"
|
||||
func renderOpenRestyLimitZoneBlock(cfg ConfigSnapshot) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(" limit_conn_zone $server_name zone=openflare_conn_per_server:10m;\n")
|
||||
builder.WriteString(" limit_conn_zone $binary_remote_addr zone=openflare_conn_per_ip:10m;\n")
|
||||
if strings.TrimSpace(cfg.DefaultLimitReqPerIP) != "" {
|
||||
fmt.Fprintf(&builder, " limit_req_zone $binary_remote_addr zone=openflare_req_per_ip:10m rate=%s;\n", strings.TrimSpace(cfg.DefaultLimitReqPerIP))
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func renderOpenRestyObservabilityTemplateBlock() string {
|
||||
@@ -475,6 +481,11 @@ func renderRouteLimitBlock(limitConfig routeLimitConfig) string {
|
||||
if strings.TrimSpace(limitConfig.LimitRate) != "" {
|
||||
fmt.Fprintf(&builder, " limit_rate %s;\n", limitConfig.LimitRate)
|
||||
}
|
||||
if strings.TrimSpace(limitConfig.LimitReqPerIP) != "" {
|
||||
burst := calculateBurst(limitConfig.LimitReqPerIP)
|
||||
fmt.Fprintf(&builder, " limit_req zone=openflare_req_per_ip burst=%d nodelay;\n", burst)
|
||||
fmt.Fprintf(&builder, " limit_req_status 429;\n")
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
@@ -483,6 +494,7 @@ func mergeRouteLimitConfig(route Route, cfg ConfigSnapshot) routeLimitConfig {
|
||||
LimitConnPerServer: mergeLimitConn(route.LimitConnPerServer, cfg.DefaultLimitConnPerServer),
|
||||
LimitConnPerIP: mergeLimitConn(route.LimitConnPerIP, cfg.DefaultLimitConnPerIP),
|
||||
LimitRate: mergeLimitRate(route.LimitRate, cfg.DefaultLimitRate),
|
||||
LimitReqPerIP: mergeLimitRate(route.LimitReqPerIP, cfg.DefaultLimitReqPerIP),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,3 +876,29 @@ func buildPathExactMatchPattern(rules []string) string {
|
||||
}
|
||||
return fmt.Sprintf("^(?:%s)$", strings.Join(parts, "|"))
|
||||
}
|
||||
|
||||
func calculateBurst(rateStr string) int {
|
||||
rateStr = strings.ToLower(strings.TrimSpace(rateStr))
|
||||
if rateStr == "" {
|
||||
return 0
|
||||
}
|
||||
var val int
|
||||
var unit string
|
||||
_, err := fmt.Sscanf(rateStr, "%dr/%s", &val, &unit)
|
||||
if err != nil {
|
||||
return 5
|
||||
}
|
||||
if val <= 0 {
|
||||
return 5
|
||||
}
|
||||
if unit == "s" {
|
||||
return val * 2
|
||||
} else if unit == "m" {
|
||||
b := val / 5
|
||||
if b < 5 {
|
||||
b = 5
|
||||
}
|
||||
return b
|
||||
}
|
||||
return 5
|
||||
}
|
||||
|
||||
@@ -464,16 +464,18 @@ func TestMergeRouteLimitConfig(t *testing.T) {
|
||||
DefaultLimitConnPerServer: 100,
|
||||
DefaultLimitConnPerIP: 10,
|
||||
DefaultLimitRate: "512k",
|
||||
DefaultLimitReqPerIP: "10r/s",
|
||||
},
|
||||
want: routeLimitConfig{LimitConnPerServer: 100, LimitConnPerIP: 10, LimitRate: "512k"},
|
||||
want: routeLimitConfig{LimitConnPerServer: 100, LimitConnPerIP: 10, LimitRate: "512k", LimitReqPerIP: "10r/s"},
|
||||
},
|
||||
{
|
||||
name: "explicit off ignores default",
|
||||
route: Route{LimitConnPerServer: -1, LimitConnPerIP: -1, LimitRate: "-1"},
|
||||
route: Route{LimitConnPerServer: -1, LimitConnPerIP: -1, LimitRate: "-1", LimitReqPerIP: "-1"},
|
||||
cfg: ConfigSnapshot{
|
||||
DefaultLimitConnPerServer: 100,
|
||||
DefaultLimitConnPerIP: 10,
|
||||
DefaultLimitRate: "512k",
|
||||
DefaultLimitReqPerIP: "10r/s",
|
||||
},
|
||||
want: routeLimitConfig{},
|
||||
},
|
||||
@@ -484,8 +486,9 @@ func TestMergeRouteLimitConfig(t *testing.T) {
|
||||
DefaultLimitConnPerServer: 100,
|
||||
DefaultLimitConnPerIP: 10,
|
||||
DefaultLimitRate: "512k",
|
||||
DefaultLimitReqPerIP: "10r/s",
|
||||
},
|
||||
want: routeLimitConfig{LimitConnPerServer: 50, LimitConnPerIP: 5, LimitRate: "1m"},
|
||||
want: routeLimitConfig{LimitConnPerServer: 50, LimitConnPerIP: 5, LimitRate: "1m", LimitReqPerIP: "10r/s"},
|
||||
},
|
||||
{
|
||||
name: "partial inherit",
|
||||
@@ -494,8 +497,9 @@ func TestMergeRouteLimitConfig(t *testing.T) {
|
||||
DefaultLimitConnPerServer: 100,
|
||||
DefaultLimitConnPerIP: 10,
|
||||
DefaultLimitRate: "256k",
|
||||
DefaultLimitReqPerIP: "10r/s",
|
||||
},
|
||||
want: routeLimitConfig{LimitConnPerServer: 100, LimitConnPerIP: 0, LimitRate: "256k"},
|
||||
want: routeLimitConfig{LimitConnPerServer: 100, LimitConnPerIP: 0, LimitRate: "256k", LimitReqPerIP: "10r/s"},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
@@ -522,6 +526,7 @@ func TestRenderRouteConfigAppliesDefaultLimits(t *testing.T) {
|
||||
DefaultLimitConnPerServer: 120,
|
||||
DefaultLimitConnPerIP: 12,
|
||||
DefaultLimitRate: "512k",
|
||||
DefaultLimitReqPerIP: "10r/s",
|
||||
},
|
||||
}
|
||||
rendered, err := RenderRouteConfig(doc, nil)
|
||||
@@ -532,6 +537,8 @@ func TestRenderRouteConfigAppliesDefaultLimits(t *testing.T) {
|
||||
"limit_conn openflare_conn_per_server 120;",
|
||||
"limit_conn openflare_conn_per_ip 12;",
|
||||
"limit_rate 512k;",
|
||||
"limit_req zone=openflare_req_per_ip burst=20 nodelay;",
|
||||
"limit_req_status 429;",
|
||||
} {
|
||||
if !strings.Contains(rendered, want) {
|
||||
t.Fatalf("expected %q in route config, got:\n%s", want, rendered)
|
||||
@@ -550,18 +557,20 @@ func TestRenderRouteConfigExplicitOffSkipsDefaultLimits(t *testing.T) {
|
||||
LimitConnPerServer: -1,
|
||||
LimitConnPerIP: -1,
|
||||
LimitRate: "-1",
|
||||
LimitReqPerIP: "-1",
|
||||
}},
|
||||
OpenRestyConfig: ConfigSnapshot{
|
||||
DefaultLimitConnPerServer: 120,
|
||||
DefaultLimitConnPerIP: 12,
|
||||
DefaultLimitRate: "512k",
|
||||
DefaultLimitReqPerIP: "10r/s",
|
||||
},
|
||||
}
|
||||
rendered, err := RenderRouteConfig(doc, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("RenderRouteConfig() error = %v", err)
|
||||
}
|
||||
if strings.Contains(rendered, "limit_conn") || strings.Contains(rendered, "limit_rate") {
|
||||
if strings.Contains(rendered, "limit_conn") || strings.Contains(rendered, "limit_rate") || strings.Contains(rendered, "limit_req") {
|
||||
t.Fatalf("expected no limit directives, got:\n%s", rendered)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +165,7 @@ type Route struct {
|
||||
LimitConnPerServer int `json:"limit_conn_per_server,omitempty"`
|
||||
LimitConnPerIP int `json:"limit_conn_per_ip,omitempty"`
|
||||
LimitRate string `json:"limit_rate,omitempty"`
|
||||
LimitReqPerIP string `json:"limit_req_per_ip,omitempty"`
|
||||
CacheEnabled bool `json:"cache_enabled"`
|
||||
CachePolicy string `json:"cache_policy,omitempty"`
|
||||
CacheRules []string `json:"cache_rules,omitempty"`
|
||||
@@ -312,6 +313,7 @@ type ConfigSnapshot struct {
|
||||
DefaultLimitConnPerServer int `json:"default_limit_conn_per_server,omitempty"`
|
||||
DefaultLimitConnPerIP int `json:"default_limit_conn_per_ip,omitempty"`
|
||||
DefaultLimitRate string `json:"default_limit_rate,omitempty"`
|
||||
DefaultLimitReqPerIP string `json:"default_limit_req_per_ip,omitempty"`
|
||||
}
|
||||
|
||||
// Document is the top-level input structure for the OpenResty renderer,
|
||||
@@ -341,6 +343,7 @@ type routeLimitConfig struct {
|
||||
LimitConnPerServer int
|
||||
LimitConnPerIP int
|
||||
LimitRate string
|
||||
LimitReqPerIP string
|
||||
}
|
||||
|
||||
type routeUpstreamConfig struct {
|
||||
|
||||
Reference in New Issue
Block a user