mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:40:59 +08:00
feat: 反向代理支持更多设置
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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 上游服务器配置
|
||||
|
||||
Reference in New Issue
Block a user