2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 06:47:20 +08:00
Files
panel/pkg/webserver/nginx/proxy.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()
}