2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 06:40:59 +08:00

feat: 反向代理支持更多设置

This commit is contained in:
2026-01-29 20:06:44 +08:00
parent 6458957650
commit 6a73f6e333
3 changed files with 812 additions and 12 deletions

View File

@@ -17,6 +17,66 @@ import (
// proxyFilePattern 匹配代理配置文件名 (200-299)
var proxyFilePattern = regexp.MustCompile(`^(\d{3})-proxy\.conf$`)
// parseDurationFromNginx 从 Nginx 时间格式解析为 time.Duration
func parseDurationFromNginx(valueStr, unit string) time.Duration {
value, _ := strconv.Atoi(valueStr)
switch unit {
case "m":
return time.Duration(value) * time.Minute
case "h":
return time.Duration(value) * time.Hour
default:
return time.Duration(value) * time.Second
}
}
// parseSizeToBytes 解析大小字符串为字节数
func parseSizeToBytes(valueStr, unit string) int64 {
value, _ := strconv.ParseInt(valueStr, 10, 64)
switch strings.ToLower(unit) {
case "k":
return value * 1024
case "m":
return value * 1024 * 1024
case "g":
return value * 1024 * 1024 * 1024
default:
return value
}
}
// formatBytesToNginx 格式化字节数为 Nginx 大小格式
func formatBytesToNginx(bytes int64) string {
if bytes == 0 {
return "0"
}
if bytes%(1024*1024*1024) == 0 {
return fmt.Sprintf("%dg", bytes/(1024*1024*1024))
}
if bytes%(1024*1024) == 0 {
return fmt.Sprintf("%dm", bytes/(1024*1024))
}
if bytes%1024 == 0 {
return fmt.Sprintf("%dk", bytes/1024)
}
return fmt.Sprintf("%d", bytes)
}
// formatDurationToNginx 格式化 time.Duration 为 Nginx 时间格式
func formatDurationToNginx(d time.Duration) string {
if d == 0 {
return "0s"
}
seconds := int(d.Seconds())
if seconds%3600 == 0 {
return fmt.Sprintf("%dh", seconds/3600)
}
if seconds%60 == 0 {
return fmt.Sprintf("%dm", seconds/60)
}
return fmt.Sprintf("%ds", seconds)
}
// parseProxyFiles 从 site 目录解析所有代理配置
func parseProxyFiles(siteDir string) ([]types.Proxy, error) {
entries, err := os.ReadDir(siteDir)
@@ -227,6 +287,146 @@ func parseProxyFile(filePath string) (*types.Proxy, error) {
}
}
// 解析 proxy_http_version
httpVersionPattern := regexp.MustCompile(`proxy_http_version\s+([\d.]+);`)
if hvm := httpVersionPattern.FindStringSubmatch(blockContent); hvm != nil {
proxy.HTTPVersion = strings.TrimSpace(hvm[1])
}
// 解析超时配置
connectTimeoutPattern := regexp.MustCompile(`proxy_connect_timeout\s+(\d+)([smh]?);`)
readTimeoutPattern := regexp.MustCompile(`proxy_read_timeout\s+(\d+)([smh]?);`)
sendTimeoutPattern := regexp.MustCompile(`proxy_send_timeout\s+(\d+)([smh]?);`)
var timeout types.TimeoutConfig
hasTimeout := false
if ctm := connectTimeoutPattern.FindStringSubmatch(blockContent); ctm != nil {
timeout.Connect = parseDurationFromNginx(ctm[1], ctm[2])
hasTimeout = true
}
if rtm := readTimeoutPattern.FindStringSubmatch(blockContent); rtm != nil {
timeout.Read = parseDurationFromNginx(rtm[1], rtm[2])
hasTimeout = true
}
if stm := sendTimeoutPattern.FindStringSubmatch(blockContent); stm != nil {
timeout.Send = parseDurationFromNginx(stm[1], stm[2])
hasTimeout = true
}
if hasTimeout {
proxy.Timeout = &timeout
}
// 解析重试配置
nextUpstreamPattern := regexp.MustCompile(`proxy_next_upstream\s+([^;]+);`)
nextUpstreamTriesPattern := regexp.MustCompile(`proxy_next_upstream_tries\s+(\d+);`)
nextUpstreamTimeoutPattern := regexp.MustCompile(`proxy_next_upstream_timeout\s+(\d+)([smh]?);`)
var retry types.RetryConfig
hasRetry := false
if num := nextUpstreamPattern.FindStringSubmatch(blockContent); num != nil {
retry.Conditions = strings.Fields(num[1])
hasRetry = true
}
if nutm := nextUpstreamTriesPattern.FindStringSubmatch(blockContent); nutm != nil {
retry.Tries, _ = strconv.Atoi(nutm[1])
hasRetry = true
}
if nutom := nextUpstreamTimeoutPattern.FindStringSubmatch(blockContent); nutom != nil {
retry.Timeout = parseDurationFromNginx(nutom[1], nutom[2])
hasRetry = true
}
if hasRetry {
proxy.Retry = &retry
}
// 解析 client_max_body_size
clientMaxBodySizePattern := regexp.MustCompile(`client_max_body_size\s+(\d+)([kmgKMG]?);`)
if cmbsm := clientMaxBodySizePattern.FindStringSubmatch(blockContent); cmbsm != nil {
proxy.ClientMaxBodySize = parseSizeToBytes(cmbsm[1], cmbsm[2])
}
// 解析 SSL 后端验证配置
sslVerifyPattern := regexp.MustCompile(`proxy_ssl_verify\s+(on|off);`)
sslTrustedCertPattern := regexp.MustCompile(`proxy_ssl_trusted_certificate\s+([^;]+);`)
sslVerifyDepthPattern := regexp.MustCompile(`proxy_ssl_verify_depth\s+(\d+);`)
var sslBackend types.SSLBackendConfig
hasSSLBackend := false
if svm := sslVerifyPattern.FindStringSubmatch(blockContent); svm != nil {
sslBackend.Verify = svm[1] == "on"
hasSSLBackend = true
}
if stcm := sslTrustedCertPattern.FindStringSubmatch(blockContent); stcm != nil {
sslBackend.TrustedCertificate = strings.TrimSpace(stcm[1])
hasSSLBackend = true
}
if svdm := sslVerifyDepthPattern.FindStringSubmatch(blockContent); svdm != nil {
sslBackend.VerifyDepth, _ = strconv.Atoi(svdm[1])
hasSSLBackend = true
}
if hasSSLBackend {
proxy.SSLBackend = &sslBackend
}
// 解析响应头配置
hideHeaderPattern := regexp.MustCompile(`proxy_hide_header\s+([^;]+);`)
addHeaderPattern := regexp.MustCompile(`add_header\s+(\S+)\s+"?([^";]+)"?(?:\s+always)?;`)
var responseHeaders types.ResponseHeaderConfig
hasResponseHeaders := false
hideHeaderMatches := hideHeaderPattern.FindAllStringSubmatch(blockContent, -1)
if len(hideHeaderMatches) > 0 {
responseHeaders.Hide = []string{}
for _, hhm := range hideHeaderMatches {
responseHeaders.Hide = append(responseHeaders.Hide, strings.TrimSpace(hhm[1]))
}
hasResponseHeaders = true
}
addHeaderMatches := addHeaderPattern.FindAllStringSubmatch(blockContent, -1)
if len(addHeaderMatches) > 0 {
responseHeaders.Add = make(map[string]string)
for _, ahm := range addHeaderMatches {
responseHeaders.Add[strings.TrimSpace(ahm[1])] = strings.TrimSpace(ahm[2])
}
hasResponseHeaders = true
}
if hasResponseHeaders {
proxy.ResponseHeaders = &responseHeaders
}
// 解析 IP 访问控制
allowPattern := regexp.MustCompile(`allow\s+([^;]+);`)
denyPattern := regexp.MustCompile(`deny\s+([^;]+);`)
var accessControl types.AccessControlConfig
hasAccessControl := false
allowMatches := allowPattern.FindAllStringSubmatch(blockContent, -1)
if len(allowMatches) > 0 {
accessControl.Allow = []string{}
for _, am := range allowMatches {
accessControl.Allow = append(accessControl.Allow, strings.TrimSpace(am[1]))
}
hasAccessControl = true
}
denyMatches := denyPattern.FindAllStringSubmatch(blockContent, -1)
if len(denyMatches) > 0 {
accessControl.Deny = []string{}
for _, dm := range denyMatches {
accessControl.Deny = append(accessControl.Deny, strings.TrimSpace(dm[1]))
}
hasAccessControl = true
}
if hasAccessControl {
proxy.AccessControl = &accessControl
}
return proxy, nil
}
@@ -298,6 +498,21 @@ func generateProxyConfig(proxy types.Proxy) string {
sb.WriteString(fmt.Sprintf("# Reverse proxy: %s -> %s\n", location, proxy.Pass))
sb.WriteString(fmt.Sprintf("location %s {\n", location))
// IP 访问控制
if proxy.AccessControl != nil {
for _, ip := range proxy.AccessControl.Allow {
sb.WriteString(fmt.Sprintf(" allow %s;\n", ip))
}
for _, ip := range proxy.AccessControl.Deny {
sb.WriteString(fmt.Sprintf(" deny %s;\n", ip))
}
}
// 请求体大小限制
if proxy.ClientMaxBodySize > 0 {
sb.WriteString(fmt.Sprintf(" client_max_body_size %s;\n", formatBytesToNginx(proxy.ClientMaxBodySize)))
}
// resolver 配置
if len(proxy.Resolver) > 0 {
sb.WriteString(fmt.Sprintf(" resolver %s;\n", strings.Join(proxy.Resolver, " ")))
@@ -307,7 +522,10 @@ func generateProxyConfig(proxy types.Proxy) string {
}
sb.WriteString(fmt.Sprintf(" proxy_pass %s;\n", proxy.Pass))
sb.WriteString(" proxy_http_version 1.1;\n")
// HTTP 协议版本
httpVersion := lo.If(proxy.HTTPVersion != "", proxy.HTTPVersion).Else("1.1")
sb.WriteString(fmt.Sprintf(" proxy_http_version %s;\n", httpVersion))
// Host 头
host := lo.If(proxy.Host == "" || proxy.Host == "$proxy_host", "$proxy_host").ElseF(func() string {
@@ -329,6 +547,43 @@ func generateProxyConfig(proxy types.Proxy) string {
sb.WriteString(" proxy_ssl_session_reuse off;\n")
sb.WriteString(" proxy_ssl_server_name on;\n")
sb.WriteString(fmt.Sprintf(" proxy_ssl_name %s;\n", lo.If(proxy.SNI != "", proxy.SNI).Else("$proxy_host")))
// SSL 后端验证
if proxy.SSLBackend != nil && proxy.SSLBackend.Verify {
sb.WriteString(" proxy_ssl_verify on;\n")
if proxy.SSLBackend.VerifyDepth > 0 {
sb.WriteString(fmt.Sprintf(" proxy_ssl_verify_depth %d;\n", proxy.SSLBackend.VerifyDepth))
}
if proxy.SSLBackend.TrustedCertificate != "" {
sb.WriteString(fmt.Sprintf(" proxy_ssl_trusted_certificate %s;\n", proxy.SSLBackend.TrustedCertificate))
}
}
}
// 超时配置
if proxy.Timeout != nil {
if proxy.Timeout.Connect > 0 {
sb.WriteString(fmt.Sprintf(" proxy_connect_timeout %s;\n", formatDurationToNginx(proxy.Timeout.Connect)))
}
if proxy.Timeout.Read > 0 {
sb.WriteString(fmt.Sprintf(" proxy_read_timeout %s;\n", formatDurationToNginx(proxy.Timeout.Read)))
}
if proxy.Timeout.Send > 0 {
sb.WriteString(fmt.Sprintf(" proxy_send_timeout %s;\n", formatDurationToNginx(proxy.Timeout.Send)))
}
}
// 重试配置
if proxy.Retry != nil {
if len(proxy.Retry.Conditions) > 0 {
sb.WriteString(fmt.Sprintf(" proxy_next_upstream %s;\n", strings.Join(proxy.Retry.Conditions, " ")))
}
if proxy.Retry.Tries > 0 {
sb.WriteString(fmt.Sprintf(" proxy_next_upstream_tries %d;\n", proxy.Retry.Tries))
}
if proxy.Retry.Timeout > 0 {
sb.WriteString(fmt.Sprintf(" proxy_next_upstream_timeout %s;\n", formatDurationToNginx(proxy.Retry.Timeout)))
}
}
// Buffering 配置
@@ -405,6 +660,19 @@ func generateProxyConfig(proxy types.Proxy) string {
}
}
// 响应头修改
if proxy.ResponseHeaders != nil {
// 隐藏响应头
for _, header := range proxy.ResponseHeaders.Hide {
sb.WriteString(fmt.Sprintf(" proxy_hide_header %s;\n", header))
}
// 添加响应头
for name, value := range proxy.ResponseHeaders.Add {
formattedValue := lo.If(strings.HasPrefix(value, "$"), value).Else("\"" + value + "\"")
sb.WriteString(fmt.Sprintf(" add_header %s %s always;\n", name, formattedValue))
}
}
sb.WriteString("}\n")
return sb.String()

View File

@@ -31,18 +31,71 @@ type CacheConfig struct {
Key string `form:"key" json:"key"`
}
// TimeoutConfig 超时配置
type TimeoutConfig struct {
Connect time.Duration `form:"connect" json:"connect"` // proxy_connect_timeout默认 60s
Read time.Duration `form:"read" json:"read"` // proxy_read_timeout默认 60s
Send time.Duration `form:"send" json:"send"` // proxy_send_timeout默认 60s
}
// RetryConfig 重试配置
type RetryConfig struct {
// 触发重试的条件 (proxy_next_upstream)
// 可选值: "error", "timeout", "invalid_header", "http_500", "http_502", "http_503", "http_504", "http_429", "non_idempotent", "off"
Conditions []string `form:"conditions" json:"conditions"`
// 最大重试次数 (proxy_next_upstream_tries)0 表示不限制
Tries int `form:"tries" json:"tries"`
// 重试超时时间 (proxy_next_upstream_timeout)0 表示不限制
Timeout time.Duration `form:"timeout" json:"timeout"`
}
// SSLBackendConfig SSL 后端验证配置
type SSLBackendConfig struct {
Verify bool `form:"verify" json:"verify"` // proxy_ssl_verify on/off
TrustedCertificate string `form:"trusted_certificate" json:"trusted_certificate"` // proxy_ssl_trusted_certificate 路径
VerifyDepth int `form:"verify_depth" json:"verify_depth"` // proxy_ssl_verify_depth默认 1
}
// ResponseHeaderConfig 响应头修改配置
type ResponseHeaderConfig struct {
// 隐藏的响应头 (proxy_hide_header)
Hide []string `form:"hide" json:"hide"`
// 添加的响应头 (add_header)key -> value
// 值可以包含变量,如 $upstream_cache_status
Add map[string]string `form:"add" json:"add"`
}
// AccessControlConfig IP 访问控制配置
type AccessControlConfig struct {
// 允许的 IP/CIDR 列表 (allow)
Allow []string `form:"allow" json:"allow"`
// 拒绝的 IP/CIDR 列表 (deny)
Deny []string `form:"deny" json:"deny"`
}
// Proxy 反向代理配置
type Proxy struct {
Location string `form:"location" json:"location" validate:"required"` // 匹配路径,如: "/", "/api", "~ ^/api/v[0-9]+/"
Pass string `form:"pass" json:"pass" validate:"required"` // 代理地址,如: "http://example.com", "http://backend"
Host string `form:"host" json:"host"` // 代理 Host如: "example.com"
SNI string `form:"sni" json:"sni"` // 代理 SNI如: "example.com"
Cache *CacheConfig `form:"cache" json:"cache"` // 缓存配置nil 表示禁用缓存
Buffering bool `form:"buffering" json:"buffering"` // 是否启用缓冲
Resolver []string `form:"resolver" json:"resolver"` // 自定义 DNS 解析器配置,如: ["8.8.8.8", "ipv6=off"]
ResolverTimeout time.Duration `form:"resolver_timeout" json:"resolver_timeout"` // DNS 解析超时时间,如: 5 * time.Second
Headers map[string]string `form:"headers" json:"headers"` // 自定义请求头,如: map["X-Custom-Header"] = "value"
Replaces map[string]string `form:"replaces" json:"replaces"` // 响应内容替换,如: map["/old"] = "/new"
Location string `form:"location" json:"location" validate:"required"` // 匹配路径,如: "/", "/api", "~ ^/api/v[0-9]+/"
Pass string `form:"pass" json:"pass" validate:"required"` // 代理地址,如: "http://example.com", "http://backend"
Host string `form:"host" json:"host"` // 代理 Host如: "example.com"
SNI string `form:"sni" json:"sni"` // 代理 SNI如: "example.com"
Cache *CacheConfig `form:"cache" json:"cache"` // 缓存配置nil 表示禁用缓存
Buffering bool `form:"buffering" json:"buffering"` // 是否启用缓冲
Resolver []string `form:"resolver" json:"resolver"` // 自定义 DNS 解析器配置,如: ["8.8.8.8", "ipv6=off"]
ResolverTimeout time.Duration `form:"resolver_timeout" json:"resolver_timeout"` // DNS 解析超时时间,如: 5 * time.Second
Headers map[string]string `form:"headers" json:"headers"` // 自定义请求头,如: map["X-Custom-Header"] = "value"
Replaces map[string]string `form:"replaces" json:"replaces"` // 响应内容替换,如: map["/old"] = "/new"
HTTPVersion string `form:"http_version" json:"http_version"` // HTTP 协议版本 (proxy_http_version),可选: "1.0", "1.1", "2"
Timeout *TimeoutConfig `form:"timeout" json:"timeout"` // 超时配置
Retry *RetryConfig `form:"retry" json:"retry"` // 重试配置
ClientMaxBodySize int64 `form:"client_max_body_size" json:"client_max_body_size"` // 请求体大小限制 (client_max_body_size)单位字节0 表示使用全局配置
SSLBackend *SSLBackendConfig `form:"ssl_backend" json:"ssl_backend"` // SSL 后端验证配置
ResponseHeaders *ResponseHeaderConfig `form:"response_headers" json:"response_headers"` // 响应头修改配置
AccessControl *AccessControlConfig `form:"access_control" json:"access_control"` // IP 访问控制配置
}
// Upstream 上游服务器配置