mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 09:13:49 +08:00
Add redirect and advanced settings tabs to website editor (#1267)
* Initial plan * Add redirect tab to website editor with drag-and-drop support Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com> * Add advanced settings tab with rate limiting and basic auth Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com> * Add htpasswd file support for basic auth (nginx and apache compatible) Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com> * Fix code review issues: prevent duplicate usernames in basic auth Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com> * fix: bug * fix: bug * feat: 支持real ip设置 * feat: 支持real ip设置 * fix: lint * fix: lint --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com> Co-authored-by: 耗子 <haozi@loli.email>
This commit is contained in:
@@ -47,6 +47,14 @@ type WebsiteSetting struct {
|
||||
Upstreams []types.Upstream `json:"upstreams"`
|
||||
Proxies []types.Proxy `json:"proxies"`
|
||||
|
||||
// 重定向
|
||||
Redirects []types.Redirect `json:"redirects"`
|
||||
|
||||
// 高级设置
|
||||
RateLimit *types.RateLimit `json:"rate_limit"` // 限流限速配置
|
||||
RealIP *types.RealIP `json:"real_ip"` // 真实 IP 配置
|
||||
BasicAuth map[string]string `json:"basic_auth"` // 基本认证配置
|
||||
|
||||
// 自定义配置
|
||||
CustomConfigs []WebsiteCustomConfig `json:"custom_configs"`
|
||||
}
|
||||
|
||||
@@ -78,11 +78,21 @@ func parseRedirectFile(filePath string) (*types.Redirect, error) {
|
||||
redirectMatchPattern := regexp.MustCompile(`RedirectMatch\s+(\d+)\s+(\S+)\s+(\S+)`)
|
||||
if matches := redirectMatchPattern.FindStringSubmatch(contentStr); matches != nil {
|
||||
statusCode, _ := strconv.Atoi(matches[1])
|
||||
to := matches[3]
|
||||
keepURI := strings.Contains(to, "$1")
|
||||
if keepURI {
|
||||
to = strings.TrimSuffix(to, "$1")
|
||||
}
|
||||
// 还原 from 为简单路径格式
|
||||
from := matches[2]
|
||||
from = strings.TrimPrefix(from, "^")
|
||||
from = strings.TrimSuffix(from, "(.*)$")
|
||||
from = strings.TrimSuffix(from, "$")
|
||||
return &types.Redirect{
|
||||
Type: types.RedirectTypeURL,
|
||||
From: matches[2],
|
||||
To: matches[3],
|
||||
KeepURI: strings.Contains(matches[3], "$1"),
|
||||
From: from,
|
||||
To: to,
|
||||
KeepURI: keepURI,
|
||||
StatusCode: statusCode,
|
||||
}, nil
|
||||
}
|
||||
@@ -94,11 +104,16 @@ func parseRedirectFile(filePath string) (*types.Redirect, error) {
|
||||
if matches := hostRewritePattern.FindStringSubmatch(contentStr); matches != nil {
|
||||
statusCode, _ := strconv.Atoi(matches[3])
|
||||
host := strings.ReplaceAll(matches[1], `\.`, ".")
|
||||
to := matches[2]
|
||||
keepURI := strings.Contains(to, "$1")
|
||||
if keepURI {
|
||||
to = strings.TrimSuffix(to, "$1")
|
||||
}
|
||||
return &types.Redirect{
|
||||
Type: types.RedirectTypeHost,
|
||||
From: host,
|
||||
To: matches[2],
|
||||
KeepURI: strings.Contains(matches[2], "$1"),
|
||||
To: to,
|
||||
KeepURI: keepURI,
|
||||
StatusCode: statusCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -541,24 +541,25 @@ func (v *baseVhost) RateLimit() *types.RateLimit {
|
||||
return nil
|
||||
}
|
||||
|
||||
rateLimit := &types.RateLimit{
|
||||
Zone: make(map[string]string),
|
||||
}
|
||||
rateLimit := &types.RateLimit{}
|
||||
|
||||
// 获取速率限制值
|
||||
rateValue := v.vhost.GetDirectiveValue("SetEnv")
|
||||
if rateValue != "" {
|
||||
rateLimit.Rate = rateValue
|
||||
// 获取速率限制值 (SetEnv rate-limit 512)
|
||||
args := v.vhost.GetDirectiveValues("SetEnv")
|
||||
if len(args) >= 2 && args[0] == "rate-limit" {
|
||||
_, _ = fmt.Sscanf(args[1], "%d", &rateLimit.Rate)
|
||||
}
|
||||
|
||||
return rateLimit
|
||||
}
|
||||
|
||||
func (v *baseVhost) SetRateLimit(limit *types.RateLimit) error {
|
||||
// 设置 mod_ratelimit
|
||||
v.vhost.SetDirective("SetOutputFilter", "RATE_LIMIT")
|
||||
if limit.Rate != "" {
|
||||
v.vhost.SetDirective("SetEnv", "rate-limit", limit.Rate)
|
||||
// Apache mod_ratelimit 只支持流量限制,不支持并发连接限制
|
||||
if limit.Rate > 0 {
|
||||
v.vhost.SetDirective("SetOutputFilter", "RATE_LIMIT")
|
||||
v.vhost.SetDirective("SetEnv", "rate-limit", fmt.Sprintf("%d", limit.Rate))
|
||||
} else {
|
||||
v.vhost.RemoveDirective("SetOutputFilter")
|
||||
v.vhost.RemoveDirectives("SetEnv")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -606,6 +607,58 @@ func (v *baseVhost) ClearBasicAuth() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *baseVhost) RealIP() *types.RealIP {
|
||||
// Apache 使用 mod_remoteip
|
||||
// RemoteIPHeader X-Forwarded-For
|
||||
// RemoteIPTrustedProxy 127.0.0.1
|
||||
header := v.vhost.GetDirectiveValue("RemoteIPHeader")
|
||||
if header == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var from []string
|
||||
for _, dir := range v.vhost.GetDirectives("RemoteIPTrustedProxy") {
|
||||
if len(dir.Args) > 0 {
|
||||
from = append(from, dir.Args[0])
|
||||
}
|
||||
}
|
||||
|
||||
return &types.RealIP{
|
||||
From: from,
|
||||
Header: header,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *baseVhost) SetRealIP(realIP *types.RealIP) error {
|
||||
// 清除现有配置
|
||||
v.vhost.RemoveDirective("RemoteIPHeader")
|
||||
v.vhost.RemoveDirectives("RemoteIPTrustedProxy")
|
||||
|
||||
if realIP == nil || (len(realIP.From) == 0 && realIP.Header == "") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 设置 RemoteIPHeader
|
||||
if realIP.Header != "" {
|
||||
v.vhost.SetDirective("RemoteIPHeader", realIP.Header)
|
||||
}
|
||||
|
||||
// 设置 RemoteIPTrustedProxy
|
||||
for _, ip := range realIP.From {
|
||||
if ip != "" {
|
||||
v.vhost.AddDirective("RemoteIPTrustedProxy", ip)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *baseVhost) ClearRealIP() error {
|
||||
v.vhost.RemoveDirective("RemoteIPHeader")
|
||||
v.vhost.RemoveDirectives("RemoteIPTrustedProxy")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *baseVhost) Redirects() []types.Redirect {
|
||||
siteDir := filepath.Join(v.configDir, "site")
|
||||
redirects, _ := parseRedirectFiles(siteDir)
|
||||
|
||||
@@ -237,12 +237,13 @@ func (s *VhostTestSuite) TestRateLimit() {
|
||||
s.Nil(s.vhost.RateLimit())
|
||||
|
||||
limit := &types.RateLimit{
|
||||
Rate: "512",
|
||||
Rate: 512,
|
||||
}
|
||||
s.NoError(s.vhost.SetRateLimit(limit))
|
||||
|
||||
got := s.vhost.RateLimit()
|
||||
s.NotNil(got)
|
||||
s.Equal(512, got.Rate)
|
||||
|
||||
s.NoError(s.vhost.ClearRateLimit())
|
||||
s.Nil(s.vhost.RateLimit())
|
||||
|
||||
@@ -40,15 +40,19 @@ var order = map[string]int{
|
||||
|
||||
"client_max_body_size": 100,
|
||||
"client_body_buffer_size": 101,
|
||||
"limit_except": 102,
|
||||
"limit_req_zone": 103,
|
||||
"limit_req": 104,
|
||||
"limit_conn_zone": 105,
|
||||
"limit_conn": 106,
|
||||
"allow": 107,
|
||||
"deny": 108,
|
||||
"auth_basic": 109,
|
||||
"auth_basic_user_file": 110,
|
||||
"limit_rate": 102,
|
||||
"limit_except": 103,
|
||||
"limit_req_zone": 104,
|
||||
"limit_req": 105,
|
||||
"limit_conn_zone": 106,
|
||||
"limit_conn": 107,
|
||||
"allow": 108,
|
||||
"deny": 109,
|
||||
"auth_basic": 110,
|
||||
"auth_basic_user_file": 111,
|
||||
"set_real_ip_from": 112,
|
||||
"real_ip_header": 113,
|
||||
"real_ip_recursive": 114,
|
||||
|
||||
"ssl": 200,
|
||||
"ssl_certificate": 201,
|
||||
|
||||
@@ -66,11 +66,16 @@ func parseRedirectFile(filePath string) (*types.Redirect, error) {
|
||||
urlPattern := regexp.MustCompile(`location\s*=\s*(\S+)\s*\{[^}]*return\s+(\d+)\s+([^;]+);`)
|
||||
if matches := urlPattern.FindStringSubmatch(contentStr); matches != nil {
|
||||
statusCode, _ := strconv.Atoi(matches[2])
|
||||
to := strings.TrimSpace(matches[3])
|
||||
keepURI := strings.Contains(to, "$request_uri")
|
||||
if keepURI {
|
||||
to = strings.TrimSuffix(to, "$request_uri")
|
||||
}
|
||||
return &types.Redirect{
|
||||
Type: types.RedirectTypeURL,
|
||||
From: matches[1],
|
||||
To: strings.TrimSpace(matches[3]),
|
||||
KeepURI: strings.Contains(matches[3], "$request_uri"),
|
||||
To: to,
|
||||
KeepURI: keepURI,
|
||||
StatusCode: statusCode,
|
||||
}, nil
|
||||
}
|
||||
@@ -79,11 +84,16 @@ func parseRedirectFile(filePath string) (*types.Redirect, error) {
|
||||
hostPattern := regexp.MustCompile(`if\s*\(\s*\$host\s*=\s*"?([^")\s]+)"?\s*\)\s*\{[^}]*return\s+(\d+)\s+([^;]+);`)
|
||||
if matches := hostPattern.FindStringSubmatch(contentStr); matches != nil {
|
||||
statusCode, _ := strconv.Atoi(matches[2])
|
||||
to := strings.TrimSpace(matches[3])
|
||||
keepURI := strings.Contains(to, "$request_uri")
|
||||
if keepURI {
|
||||
to = strings.TrimSuffix(to, "$request_uri")
|
||||
}
|
||||
return &types.Redirect{
|
||||
Type: types.RedirectTypeHost,
|
||||
From: matches[1],
|
||||
To: strings.TrimSpace(matches[3]),
|
||||
KeepURI: strings.Contains(matches[3], "$request_uri"),
|
||||
To: to,
|
||||
KeepURI: keepURI,
|
||||
StatusCode: statusCode,
|
||||
}, nil
|
||||
}
|
||||
@@ -92,11 +102,16 @@ func parseRedirectFile(filePath string) (*types.Redirect, error) {
|
||||
errorPattern := regexp.MustCompile(`error_page\s+404\s*=\s*@redirect_404;[^@]*location\s+@redirect_404\s*\{[^}]*return\s+(\d+)\s+([^;]+);`)
|
||||
if matches := errorPattern.FindStringSubmatch(contentStr); matches != nil {
|
||||
statusCode, _ := strconv.Atoi(matches[1])
|
||||
to := strings.TrimSpace(matches[2])
|
||||
keepURI := strings.Contains(to, "$request_uri")
|
||||
if keepURI {
|
||||
to = strings.TrimSuffix(to, "$request_uri")
|
||||
}
|
||||
return &types.Redirect{
|
||||
Type: types.RedirectType404,
|
||||
From: "",
|
||||
To: strings.TrimSpace(matches[2]),
|
||||
KeepURI: strings.Contains(matches[2], "$request_uri"),
|
||||
To: to,
|
||||
KeepURI: keepURI,
|
||||
StatusCode: statusCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -576,70 +576,84 @@ func (v *baseVhost) ClearSSL() error {
|
||||
}
|
||||
|
||||
func (v *baseVhost) RateLimit() *types.RateLimit {
|
||||
rate := ""
|
||||
var perServer, perIP, rate int
|
||||
|
||||
// 解析 limit_rate 配置
|
||||
directive, err := v.parser.FindOne("server.limit_rate")
|
||||
if err == nil {
|
||||
if len(v.parser.parameters2Slices(directive.GetParameters())) != 0 {
|
||||
rate = directive.GetParameters()[0].GetValue()
|
||||
params := v.parser.parameters2Slices(directive.GetParameters())
|
||||
if len(params) > 0 {
|
||||
// 解析 limit_rate 值,如 "512k" -> 512
|
||||
rateStr := params[0]
|
||||
rateStr = strings.TrimSuffix(rateStr, "k")
|
||||
rateStr = strings.TrimSuffix(rateStr, "K")
|
||||
_, _ = fmt.Sscanf(rateStr, "%d", &rate)
|
||||
}
|
||||
}
|
||||
directives, _ := v.parser.Find("server.limit_conn")
|
||||
var limitConn [][]string
|
||||
for _, dir := range directives {
|
||||
limitConn = append(limitConn, v.parser.parameters2Slices(dir.GetParameters()))
|
||||
}
|
||||
|
||||
if rate == "" && len(limitConn) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rateLimit := &types.RateLimit{
|
||||
Rate: rate,
|
||||
Zone: make(map[string]string),
|
||||
}
|
||||
|
||||
// 解析 limit_conn 配置
|
||||
for _, limit := range limitConn {
|
||||
if len(limit) >= 2 {
|
||||
// limit_conn zone connections
|
||||
// 例如: limit_conn perip 10
|
||||
rateLimit.Zone[limit[0]] = limit[1]
|
||||
directives, _ := v.parser.Find("server.limit_conn")
|
||||
for _, dir := range directives {
|
||||
params := v.parser.parameters2Slices(dir.GetParameters())
|
||||
if len(params) >= 2 {
|
||||
var val int
|
||||
_, _ = fmt.Sscanf(params[1], "%d", &val)
|
||||
switch params[0] {
|
||||
case "perserver":
|
||||
perServer = val
|
||||
case "perip":
|
||||
perIP = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rateLimit
|
||||
if perServer == 0 && perIP == 0 && rate == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.RateLimit{
|
||||
PerServer: perServer,
|
||||
PerIP: perIP,
|
||||
Rate: rate,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *baseVhost) SetRateLimit(limit *types.RateLimit) error {
|
||||
var limitConns [][]string
|
||||
for zone, connections := range limit.Zone {
|
||||
limitConns = append(limitConns, []string{zone, connections})
|
||||
}
|
||||
|
||||
// 设置限速
|
||||
// 设置限速 limit_rate
|
||||
_ = v.parser.Clear("server.limit_rate")
|
||||
if err := v.parser.Set("server", []*config.Directive{
|
||||
{
|
||||
Name: "limit_rate",
|
||||
Parameters: []config.Parameter{{Value: limit.Rate}},
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 设置并发连接数限制
|
||||
_ = v.parser.Clear("server.limit_conn")
|
||||
var directives []*config.Directive
|
||||
for _, lim := range limitConns {
|
||||
if len(lim) >= 2 {
|
||||
directives = append(directives, &config.Directive{
|
||||
Name: "limit_conn",
|
||||
Parameters: v.parser.slices2Parameters(lim),
|
||||
})
|
||||
if limit.Rate > 0 {
|
||||
if err := v.parser.Set("server", []*config.Directive{
|
||||
{
|
||||
Name: "limit_rate",
|
||||
Parameters: []config.Parameter{{Value: fmt.Sprintf("%dk", limit.Rate)}},
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return v.parser.Set("server", directives)
|
||||
// 设置并发连接数限制 limit_conn
|
||||
_ = v.parser.Clear("server.limit_conn")
|
||||
var directives []*config.Directive
|
||||
if limit.PerServer > 0 {
|
||||
directives = append(directives, &config.Directive{
|
||||
Name: "limit_conn",
|
||||
Parameters: []config.Parameter{{Value: "perserver"}, {Value: fmt.Sprintf("%d", limit.PerServer)}},
|
||||
})
|
||||
}
|
||||
if limit.PerIP > 0 {
|
||||
directives = append(directives, &config.Directive{
|
||||
Name: "limit_conn",
|
||||
Parameters: []config.Parameter{{Value: "perip"}, {Value: fmt.Sprintf("%d", limit.PerIP)}},
|
||||
})
|
||||
}
|
||||
if len(directives) > 0 {
|
||||
if err := v.parser.Set("server", directives); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *baseVhost) ClearRateLimit() error {
|
||||
@@ -706,6 +720,100 @@ func (v *baseVhost) ClearBasicAuth() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *baseVhost) RealIP() *types.RealIP {
|
||||
// 解析 set_real_ip_from 配置
|
||||
var from []string
|
||||
directives, _ := v.parser.Find("server.set_real_ip_from")
|
||||
for _, dir := range directives {
|
||||
params := v.parser.parameters2Slices(dir.GetParameters())
|
||||
if len(params) > 0 {
|
||||
from = append(from, params[0])
|
||||
}
|
||||
}
|
||||
|
||||
// 解析 real_ip_header 配置
|
||||
header := ""
|
||||
directive, err := v.parser.FindOne("server.real_ip_header")
|
||||
if err == nil {
|
||||
params := v.parser.parameters2Slices(directive.GetParameters())
|
||||
if len(params) > 0 {
|
||||
header = params[0]
|
||||
}
|
||||
}
|
||||
|
||||
// 解析 real_ip_recursive 配置
|
||||
recursive := false
|
||||
recursiveDir, err := v.parser.FindOne("server.real_ip_recursive")
|
||||
if err == nil {
|
||||
params := v.parser.parameters2Slices(recursiveDir.GetParameters())
|
||||
if len(params) > 0 && params[0] == "on" {
|
||||
recursive = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(from) == 0 && header == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.RealIP{
|
||||
From: from,
|
||||
Header: header,
|
||||
Recursive: recursive,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *baseVhost) SetRealIP(realIP *types.RealIP) error {
|
||||
// 清除现有配置
|
||||
_ = v.parser.Clear("server.set_real_ip_from")
|
||||
_ = v.parser.Clear("server.real_ip_header")
|
||||
_ = v.parser.Clear("server.real_ip_recursive")
|
||||
|
||||
if realIP == nil || (len(realIP.From) == 0 && realIP.Header == "") {
|
||||
return nil
|
||||
}
|
||||
|
||||
var directives []*config.Directive
|
||||
|
||||
// 添加 set_real_ip_from 配置
|
||||
for _, ip := range realIP.From {
|
||||
if ip != "" {
|
||||
directives = append(directives, &config.Directive{
|
||||
Name: "set_real_ip_from",
|
||||
Parameters: []config.Parameter{{Value: ip}},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 real_ip_header 配置
|
||||
if realIP.Header != "" {
|
||||
directives = append(directives, &config.Directive{
|
||||
Name: "real_ip_header",
|
||||
Parameters: []config.Parameter{{Value: realIP.Header}},
|
||||
})
|
||||
}
|
||||
|
||||
// 添加 real_ip_recursive 配置
|
||||
if realIP.Recursive {
|
||||
directives = append(directives, &config.Directive{
|
||||
Name: "real_ip_recursive",
|
||||
Parameters: []config.Parameter{{Value: "on"}},
|
||||
})
|
||||
}
|
||||
|
||||
if len(directives) > 0 {
|
||||
return v.parser.Set("server", directives)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *baseVhost) ClearRealIP() error {
|
||||
_ = v.parser.Clear("server.set_real_ip_from")
|
||||
_ = v.parser.Clear("server.real_ip_header")
|
||||
_ = v.parser.Clear("server.real_ip_recursive")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *baseVhost) Redirects() []types.Redirect {
|
||||
siteDir := filepath.Join(v.configDir, "site")
|
||||
redirects, _ := parseRedirectFiles(siteDir)
|
||||
|
||||
@@ -249,16 +249,17 @@ func (s *VhostTestSuite) TestRateLimit() {
|
||||
s.Nil(s.vhost.RateLimit())
|
||||
|
||||
limit := &types.RateLimit{
|
||||
Rate: "512k",
|
||||
Zone: map[string]string{
|
||||
"perip": "10",
|
||||
},
|
||||
PerServer: 300,
|
||||
PerIP: 25,
|
||||
Rate: 512,
|
||||
}
|
||||
s.NoError(s.vhost.SetRateLimit(limit))
|
||||
|
||||
got := s.vhost.RateLimit()
|
||||
s.NotNil(got)
|
||||
s.Equal("512k", got.Rate)
|
||||
s.Equal(300, got.PerServer)
|
||||
s.Equal(25, got.PerIP)
|
||||
s.Equal(512, got.Rate)
|
||||
|
||||
s.NoError(s.vhost.ClearRateLimit())
|
||||
s.Nil(s.vhost.RateLimit())
|
||||
|
||||
@@ -76,6 +76,13 @@ type Vhost interface {
|
||||
// ClearBasicAuth 清除基本认证
|
||||
ClearBasicAuth() error
|
||||
|
||||
// RealIP 取真实 IP 配置
|
||||
RealIP() *RealIP
|
||||
// SetRealIP 设置真实 IP 配置
|
||||
SetRealIP(realIP *RealIP) error
|
||||
// ClearRealIP 清除真实 IP 配置
|
||||
ClearRealIP() error
|
||||
|
||||
// Config 取指定名称的配置内容
|
||||
// type 可选值: "site", "shared"
|
||||
Config(name string, typ string) string
|
||||
@@ -162,9 +169,16 @@ type SSLConfig struct {
|
||||
|
||||
// RateLimit 限流限速配置
|
||||
type RateLimit struct {
|
||||
Rate string `json:"rate"` // 速率限制,如: "512k", "10r/s"
|
||||
Concurrent int `json:"concurrent"` // 并发连接数限制
|
||||
Zone map[string]string `json:"zone"` // 条件配置,如: map["perip"] = "10"
|
||||
PerServer int `json:"per_server"` // 站点最大并发数 (limit_conn perserver X)
|
||||
PerIP int `json:"per_ip"` // 单 IP 最大并发数 (limit_conn perip X)
|
||||
Rate int `json:"rate"` // 流量限制,单位 KB (limit_rate Xk)
|
||||
}
|
||||
|
||||
// RealIP 真实 IP 配置
|
||||
type RealIP struct {
|
||||
From []string `json:"from"` // 可信 IP 来源列表 (set_real_ip_from)
|
||||
Header string `json:"header"` // 真实 IP 头 (real_ip_header),如: X-Real-IP, X-Forwarded-For
|
||||
Recursive bool `json:"recursive"` // 递归搜索 (real_ip_recursive)
|
||||
}
|
||||
|
||||
// IncludeFile 包含文件配置
|
||||
|
||||
Reference in New Issue
Block a user