mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 04:22:33 +08:00
680 lines
21 KiB
Go
680 lines
21 KiB
Go
package nginx
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/samber/lo"
|
|
|
|
"github.com/acepanel/panel/pkg/webserver/types"
|
|
)
|
|
|
|
// 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)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var proxies []types.Proxy
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
matches := proxyFilePattern.FindStringSubmatch(entry.Name())
|
|
if matches == nil {
|
|
continue
|
|
}
|
|
|
|
num, _ := strconv.Atoi(matches[1])
|
|
if num < ProxyStartNum || num > ProxyEndNum {
|
|
continue
|
|
}
|
|
|
|
filePath := filepath.Join(siteDir, entry.Name())
|
|
proxy, err := parseProxyFile(filePath)
|
|
if err != nil {
|
|
continue // 跳过解析失败的文件
|
|
}
|
|
if proxy != nil {
|
|
proxies = append(proxies, *proxy)
|
|
}
|
|
}
|
|
|
|
return proxies, nil
|
|
}
|
|
|
|
// parseProxyFile 解析单个代理配置文件
|
|
func parseProxyFile(filePath string) (*types.Proxy, error) {
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
contentStr := string(content)
|
|
|
|
// 解析 location 块
|
|
// location / {
|
|
// proxy_pass http://backend;
|
|
// ...
|
|
// }
|
|
locationPattern := regexp.MustCompile(`location\s+([^{]+)\{([^}]+(?:\{[^}]*}[^}]*)*)}`)
|
|
matches := locationPattern.FindStringSubmatch(contentStr)
|
|
if matches == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
proxy := &types.Proxy{
|
|
Location: strings.TrimSpace(matches[1]),
|
|
Resolver: []string{},
|
|
Headers: make(map[string]string),
|
|
Replaces: make(map[string]string),
|
|
}
|
|
|
|
blockContent := matches[2]
|
|
|
|
// 解析 proxy_pass
|
|
passPattern := regexp.MustCompile(`proxy_pass\s+([^;]+);`)
|
|
if pm := passPattern.FindStringSubmatch(blockContent); pm != nil {
|
|
proxy.Pass = strings.TrimSpace(pm[1])
|
|
}
|
|
|
|
// 解析 proxy_set_header Host
|
|
hostPattern := regexp.MustCompile(`proxy_set_header\s+Host\s+([^;]+);`)
|
|
if hm := hostPattern.FindStringSubmatch(blockContent); hm != nil {
|
|
host := strings.TrimSpace(hm[1])
|
|
// 移除引号
|
|
host = strings.Trim(host, `"'`)
|
|
proxy.Host = host
|
|
}
|
|
|
|
// 解析 proxy_ssl_name (SNI)
|
|
sniPattern := regexp.MustCompile(`proxy_ssl_name\s+([^;]+);`)
|
|
if sm := sniPattern.FindStringSubmatch(blockContent); sm != nil {
|
|
proxy.SNI = strings.TrimSpace(sm[1])
|
|
}
|
|
|
|
// 解析 proxy_buffering
|
|
bufferingPattern := regexp.MustCompile(`proxy_buffering\s+(on|off);`)
|
|
if bm := bufferingPattern.FindStringSubmatch(blockContent); bm != nil {
|
|
proxy.Buffering = bm[1] == "on"
|
|
}
|
|
|
|
// 解析 proxy_cache
|
|
cachePattern := regexp.MustCompile(`proxy_cache\s+(\S+);`)
|
|
if cm := cachePattern.FindStringSubmatch(blockContent); cm != nil && cm[1] != "off" {
|
|
proxy.Cache = &types.CacheConfig{
|
|
Valid: make(map[string]string),
|
|
NoCacheConditions: []string{},
|
|
UseStale: []string{},
|
|
Methods: []string{},
|
|
}
|
|
|
|
// 解析 proxy_cache_valid
|
|
cacheValidPattern := regexp.MustCompile(`proxy_cache_valid\s+([^;]+);`)
|
|
cacheValidMatches := cacheValidPattern.FindAllStringSubmatch(blockContent, -1)
|
|
for _, cvm := range cacheValidMatches {
|
|
parts := strings.Fields(cvm[1])
|
|
if len(parts) >= 2 {
|
|
// 最后一个是时长,前面的是状态码
|
|
duration := parts[len(parts)-1]
|
|
codes := strings.Join(parts[:len(parts)-1], " ")
|
|
proxy.Cache.Valid[codes] = duration
|
|
} else if len(parts) == 1 {
|
|
// 只有时长,表示 any
|
|
proxy.Cache.Valid["any"] = parts[0]
|
|
}
|
|
}
|
|
|
|
// 解析 proxy_cache_bypass / proxy_no_cache
|
|
bypassPattern := regexp.MustCompile(`proxy_cache_bypass\s+([^;]+);`)
|
|
if bm := bypassPattern.FindStringSubmatch(blockContent); bm != nil {
|
|
proxy.Cache.NoCacheConditions = strings.Fields(bm[1])
|
|
}
|
|
|
|
// 解析 proxy_cache_use_stale
|
|
useStalePattern := regexp.MustCompile(`proxy_cache_use_stale\s+([^;]+);`)
|
|
if usm := useStalePattern.FindStringSubmatch(blockContent); usm != nil {
|
|
proxy.Cache.UseStale = strings.Fields(usm[1])
|
|
}
|
|
|
|
// 解析 proxy_cache_background_update
|
|
bgUpdatePattern := regexp.MustCompile(`proxy_cache_background_update\s+(on|off);`)
|
|
if bgm := bgUpdatePattern.FindStringSubmatch(blockContent); bgm != nil {
|
|
proxy.Cache.BackgroundUpdate = bgm[1] == "on"
|
|
}
|
|
|
|
// 解析 proxy_cache_lock
|
|
lockPattern := regexp.MustCompile(`proxy_cache_lock\s+(on|off);`)
|
|
if lm := lockPattern.FindStringSubmatch(blockContent); lm != nil {
|
|
proxy.Cache.Lock = lm[1] == "on"
|
|
}
|
|
|
|
// 解析 proxy_cache_min_uses
|
|
minUsesPattern := regexp.MustCompile(`proxy_cache_min_uses\s+(\d+);`)
|
|
if mum := minUsesPattern.FindStringSubmatch(blockContent); mum != nil {
|
|
proxy.Cache.MinUses, _ = strconv.Atoi(mum[1])
|
|
}
|
|
|
|
// 解析 proxy_cache_methods
|
|
methodsPattern := regexp.MustCompile(`proxy_cache_methods\s+([^;]+);`)
|
|
if mm := methodsPattern.FindStringSubmatch(blockContent); mm != nil {
|
|
proxy.Cache.Methods = strings.Fields(mm[1])
|
|
}
|
|
|
|
// 解析 proxy_cache_key
|
|
keyPattern := regexp.MustCompile(`proxy_cache_key\s+"?([^";]+)"?;`)
|
|
if km := keyPattern.FindStringSubmatch(blockContent); km != nil {
|
|
proxy.Cache.Key = strings.TrimSpace(km[1])
|
|
}
|
|
}
|
|
|
|
// 解析 resolver
|
|
resolverPattern := regexp.MustCompile(`resolver\s+([^;]+);`)
|
|
if rm := resolverPattern.FindStringSubmatch(blockContent); rm != nil {
|
|
parts := strings.Fields(rm[1])
|
|
proxy.Resolver = parts
|
|
}
|
|
|
|
// 解析 resolver_timeout
|
|
resolverTimeoutPattern := regexp.MustCompile(`resolver_timeout\s+(\d+)([smh]?);`)
|
|
if rtm := resolverTimeoutPattern.FindStringSubmatch(blockContent); rtm != nil {
|
|
value, _ := strconv.Atoi(rtm[1])
|
|
unit := rtm[2]
|
|
switch unit {
|
|
case "m":
|
|
proxy.ResolverTimeout = time.Duration(value) * time.Minute
|
|
case "h":
|
|
proxy.ResolverTimeout = time.Duration(value) * time.Hour
|
|
default:
|
|
proxy.ResolverTimeout = time.Duration(value) * time.Second
|
|
}
|
|
}
|
|
|
|
// 解析 sub_filter (响应内容替换)
|
|
subFilterPattern := regexp.MustCompile(`sub_filter\s+"([^"]+)"\s+"([^"]*)";`)
|
|
subFilterMatches := subFilterPattern.FindAllStringSubmatch(blockContent, -1)
|
|
for _, sfm := range subFilterMatches {
|
|
proxy.Replaces[sfm[1]] = sfm[2]
|
|
}
|
|
|
|
// 解析自定义请求头
|
|
standardHeaders := map[string]bool{
|
|
"Host": true, "X-Real-IP": true, "X-Forwarded-For": true,
|
|
"X-Forwarded-Proto": true, "Upgrade": true, "Connection": true,
|
|
"Early-Data": true, "Accept-Encoding": true,
|
|
}
|
|
headerPattern := regexp.MustCompile(`proxy_set_header\s+(\S+)\s+"?([^";]+)"?;`)
|
|
headerMatches := headerPattern.FindAllStringSubmatch(blockContent, -1)
|
|
for _, hm := range headerMatches {
|
|
headerName := strings.TrimSpace(hm[1])
|
|
headerValue := strings.TrimSpace(hm[2])
|
|
// 排除标准头
|
|
if !standardHeaders[headerName] {
|
|
proxy.Headers[headerName] = headerValue
|
|
}
|
|
}
|
|
|
|
// 解析 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
|
|
}
|
|
|
|
// writeProxyFiles 将代理配置写入文件
|
|
func writeProxyFiles(siteDir string, proxies []types.Proxy) error {
|
|
// 删除现有的代理配置文件 (200-299)
|
|
if err := clearProxyFiles(siteDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 写入新的配置文件
|
|
for i, proxy := range proxies {
|
|
num := ProxyStartNum + i
|
|
if num > ProxyEndNum {
|
|
return fmt.Errorf("proxy rules exceed limit (%d)", ProxyEndNum-ProxyStartNum+1)
|
|
}
|
|
|
|
fileName := fmt.Sprintf("%03d-proxy.conf", num)
|
|
filePath := filepath.Join(siteDir, fileName)
|
|
|
|
content := generateProxyConfig(proxy)
|
|
if err := os.WriteFile(filePath, []byte(content), 0600); err != nil {
|
|
return fmt.Errorf("failed to write proxy config: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// clearProxyFiles 清除所有代理配置文件
|
|
func clearProxyFiles(siteDir string) error {
|
|
entries, err := os.ReadDir(siteDir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
matches := proxyFilePattern.FindStringSubmatch(entry.Name())
|
|
if matches == nil {
|
|
continue
|
|
}
|
|
|
|
num, _ := strconv.Atoi(matches[1])
|
|
if num >= ProxyStartNum && num <= ProxyEndNum {
|
|
filePath := filepath.Join(siteDir, entry.Name())
|
|
if err := os.Remove(filePath); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("failed to delete proxy config: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateProxyConfig 生成代理配置内容
|
|
func generateProxyConfig(proxy types.Proxy) string {
|
|
var sb strings.Builder
|
|
|
|
location := proxy.Location
|
|
|
|
sb.WriteString("# Auto-generated by AcePanel. DO NOT EDIT MANUALLY!\n")
|
|
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, " ")))
|
|
if proxy.ResolverTimeout > 0 {
|
|
sb.WriteString(fmt.Sprintf(" resolver_timeout %ds;\n", int(proxy.ResolverTimeout.Seconds())))
|
|
}
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf(" proxy_pass %s;\n", proxy.Pass))
|
|
|
|
// 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 {
|
|
return lo.If(strings.HasPrefix(proxy.Host, "$"), proxy.Host).Else("\"" + proxy.Host + "\"")
|
|
})
|
|
sb.WriteString(fmt.Sprintf(" proxy_set_header Host %s;\n", host))
|
|
|
|
// 标准代理头
|
|
sb.WriteString(" proxy_set_header X-Real-IP $remote_addr;\n")
|
|
sb.WriteString(" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n")
|
|
sb.WriteString(" proxy_set_header X-Forwarded-Proto $scheme;\n")
|
|
sb.WriteString(" proxy_set_header Upgrade $http_upgrade;\n")
|
|
sb.WriteString(" proxy_set_header Connection $connection_upgrade;\n")
|
|
sb.WriteString(" proxy_set_header Early-Data $ssl_early_data;\n")
|
|
|
|
// SNI 配置
|
|
if strings.HasPrefix(proxy.Pass, "https") {
|
|
sb.WriteString(" proxy_ssl_protocols TLSv1.2 TLSv1.3;\n")
|
|
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 配置
|
|
sb.WriteString(fmt.Sprintf(" proxy_buffering %s;\n", lo.If(proxy.Buffering, "on").Else("off")))
|
|
|
|
// Cache 配置
|
|
if proxy.Cache != nil {
|
|
sb.WriteString(" proxy_cache cache_one;\n")
|
|
|
|
// 缓存时长
|
|
if len(proxy.Cache.Valid) > 0 {
|
|
for codes, duration := range proxy.Cache.Valid {
|
|
if codes == "any" {
|
|
sb.WriteString(fmt.Sprintf(" proxy_cache_valid %s;\n", duration))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf(" proxy_cache_valid %s %s;\n", codes, duration))
|
|
}
|
|
}
|
|
} else {
|
|
// 默认缓存时长
|
|
sb.WriteString(" proxy_cache_valid 200 302 10m;\n")
|
|
sb.WriteString(" proxy_cache_valid 404 10s;\n")
|
|
}
|
|
|
|
// 不缓存条件
|
|
if len(proxy.Cache.NoCacheConditions) > 0 {
|
|
conditions := strings.Join(proxy.Cache.NoCacheConditions, " ")
|
|
sb.WriteString(fmt.Sprintf(" proxy_cache_bypass %s;\n", conditions))
|
|
sb.WriteString(fmt.Sprintf(" proxy_no_cache %s;\n", conditions))
|
|
}
|
|
|
|
// 过期缓存使用策略
|
|
if len(proxy.Cache.UseStale) > 0 {
|
|
sb.WriteString(fmt.Sprintf(" proxy_cache_use_stale %s;\n", strings.Join(proxy.Cache.UseStale, " ")))
|
|
}
|
|
|
|
// 后台更新
|
|
if proxy.Cache.BackgroundUpdate {
|
|
sb.WriteString(" proxy_cache_background_update on;\n")
|
|
}
|
|
|
|
// 缓存锁
|
|
if proxy.Cache.Lock {
|
|
sb.WriteString(" proxy_cache_lock on;\n")
|
|
}
|
|
|
|
// 最小请求次数
|
|
if proxy.Cache.MinUses > 0 {
|
|
sb.WriteString(fmt.Sprintf(" proxy_cache_min_uses %d;\n", proxy.Cache.MinUses))
|
|
}
|
|
|
|
// 缓存方法
|
|
if len(proxy.Cache.Methods) > 0 {
|
|
sb.WriteString(fmt.Sprintf(" proxy_cache_methods %s;\n", strings.Join(proxy.Cache.Methods, " ")))
|
|
}
|
|
|
|
// 自定义缓存键
|
|
if proxy.Cache.Key != "" {
|
|
sb.WriteString(fmt.Sprintf(" proxy_cache_key \"%s\";\n", proxy.Cache.Key))
|
|
}
|
|
}
|
|
|
|
// 自定义请求头
|
|
for name, value := range proxy.Headers {
|
|
sb.WriteString(fmt.Sprintf(" proxy_set_header %s %s;\n", name, lo.If(strings.HasPrefix(value, "$"), value).Else("\""+value+"\"")))
|
|
}
|
|
|
|
// 响应内容替换
|
|
if len(proxy.Replaces) > 0 {
|
|
sb.WriteString(" proxy_set_header Accept-Encoding \"\";\n")
|
|
sb.WriteString(" sub_filter_once off;\n")
|
|
for from, to := range proxy.Replaces {
|
|
sb.WriteString(fmt.Sprintf(" sub_filter \"%s\" \"%s\";\n", from, to))
|
|
}
|
|
}
|
|
|
|
// 响应头修改
|
|
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()
|
|
}
|