[优化] 添加命名上游支持,优化代理配置生成逻辑

This commit is contained in:
ryan
2026-03-18 22:13:53 +08:00
parent 0cb4e06b11
commit 67197220ae
2 changed files with 98 additions and 21 deletions
+74 -12
View File
@@ -79,6 +79,13 @@ type routeCacheConfig struct {
Rules []string
}
type routeUpstreamConfig struct {
Name string
Scheme string
Address string
UsesNamedUpstream bool
}
type openRestyConfigSnapshot struct {
WorkerProcesses string `json:"worker_processes"`
WorkerConnections int `json:"worker_connections"`
@@ -650,8 +657,12 @@ func renderRouteConfig(routes []*model.ProxyRoute, cfg openRestyConfigSnapshot)
Policy: route.CachePolicy,
Rules: cacheRules,
}
upstreamConfig := buildRouteUpstreamConfig(route, cfg)
if upstreamConfig.UsesNamedUpstream {
builder.WriteString(renderNamedUpstreamBlock(upstreamConfig))
}
if !route.EnableHTTPS {
builder.WriteString(renderHTTPProxyServer(route.Domain, route.OriginURL, route.OriginHost, customHeaders, cacheConfig, cfg))
builder.WriteString(renderHTTPProxyServer(route.Domain, route.OriginURL, route.OriginHost, customHeaders, cacheConfig, upstreamConfig, cfg))
continue
}
if route.CertID == nil || *route.CertID == 0 {
@@ -668,9 +679,9 @@ func renderRouteConfig(routes []*model.ProxyRoute, cfg openRestyConfigSnapshot)
if route.RedirectHTTP {
builder.WriteString(renderHTTPRedirectServer(route.Domain))
} else {
builder.WriteString(renderHTTPProxyServer(route.Domain, route.OriginURL, route.OriginHost, customHeaders, cacheConfig, cfg))
builder.WriteString(renderHTTPProxyServer(route.Domain, route.OriginURL, route.OriginHost, customHeaders, cacheConfig, upstreamConfig, cfg))
}
builder.WriteString(renderHTTPSServer(route.Domain, route.OriginURL, route.OriginHost, certificate.ID, customHeaders, cacheConfig, cfg))
builder.WriteString(renderHTTPSServer(route.Domain, route.OriginURL, route.OriginHost, certificate.ID, customHeaders, cacheConfig, upstreamConfig, cfg))
}
return builder.String(), dedupeSupportFiles(supportFiles), nil
}
@@ -807,29 +818,29 @@ func nextVersionNumber(now time.Time) (string, error) {
return fmt.Sprintf("%s-%03d", prefix, count+1), nil
}
func renderHTTPProxyServer(domain string, originURL string, originHost string, customHeaders []ProxyRouteCustomHeaderInput, cacheConfig routeCacheConfig, cfg openRestyConfigSnapshot) string {
return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n\n location / {\n%s%s%s }\n}\n\n", domain, renderProxyHeaderBlock(originURL, originHost, customHeaders), renderRouteCacheBlock(cacheConfig, cfg), renderProxyPassBlock(originURL, cfg))
func renderHTTPProxyServer(domain string, originURL string, originHost string, customHeaders []ProxyRouteCustomHeaderInput, cacheConfig routeCacheConfig, upstreamConfig routeUpstreamConfig, cfg openRestyConfigSnapshot) string {
return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n\n location / {\n%s%s%s }\n}\n\n", domain, renderProxyHeaderBlock(originURL, originHost, customHeaders, upstreamConfig), renderRouteCacheBlock(cacheConfig, cfg), renderProxyPassBlock(originURL, upstreamConfig, cfg))
}
func renderHTTPRedirectServer(domain string) string {
return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n\n return 301 https://$host$request_uri;\n}\n\n", domain)
}
func renderHTTPSServer(domain string, originURL string, originHost string, certificateID uint, customHeaders []ProxyRouteCustomHeaderInput, cacheConfig routeCacheConfig, cfg openRestyConfigSnapshot) string {
func renderHTTPSServer(domain string, originURL string, originHost string, certificateID uint, customHeaders []ProxyRouteCustomHeaderInput, cacheConfig routeCacheConfig, upstreamConfig routeUpstreamConfig, cfg openRestyConfigSnapshot) string {
certPath := fmt.Sprintf("%s/%s", nginxCertDirPlaceholder, certificateCertFileName(certificateID))
keyPath := fmt.Sprintf("%s/%s", nginxCertDirPlaceholder, certificateKeyFileName(certificateID))
return fmt.Sprintf("server {\n listen 443 ssl http2;\n server_name %s;\n ssl_certificate %s;\n ssl_certificate_key %s;\n\n location / {\n%s%s%s }\n}\n\n", domain, certPath, keyPath, renderProxyHeaderBlock(originURL, originHost, customHeaders), renderRouteCacheBlock(cacheConfig, cfg), renderProxyPassBlock(originURL, cfg))
return fmt.Sprintf("server {\n listen 443 ssl http2;\n server_name %s;\n ssl_certificate %s;\n ssl_certificate_key %s;\n\n location / {\n%s%s%s }\n}\n\n", domain, certPath, keyPath, renderProxyHeaderBlock(originURL, originHost, customHeaders, upstreamConfig), renderRouteCacheBlock(cacheConfig, cfg), renderProxyPassBlock(originURL, upstreamConfig, cfg))
}
func renderConnectionUpgradeMap() string {
return " map $http_upgrade $connection_upgrade {\n default upgrade;\n '' close;\n }\n\n"
return " map $http_upgrade $connection_upgrade {\n default upgrade;\n '' \"\";\n }\n\n"
}
func renderDefaultServerBlock() string {
return " server {\n listen 80 default_server;\n server_name _;\n\n return 404;\n }\n\n"
}
func renderProxyHeaderBlock(originURL string, originHost string, customHeaders []ProxyRouteCustomHeaderInput) string {
func renderProxyHeaderBlock(originURL string, originHost string, customHeaders []ProxyRouteCustomHeaderInput, upstreamConfig routeUpstreamConfig) string {
var builder strings.Builder
if strings.TrimSpace(originHost) != "" {
builder.WriteString(fmt.Sprintf(" proxy_set_header Host %s;\n", quoteNginxHeaderValue(originHost)))
@@ -843,11 +854,13 @@ func renderProxyHeaderBlock(originURL string, originHost string, customHeaders [
builder.WriteString(" proxy_set_header X-Real-IP $remote_addr;\n")
builder.WriteString(" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n")
builder.WriteString(" proxy_set_header X-Forwarded-Proto $scheme;\n")
if common.OpenRestyWebsocketEnabled {
if common.OpenRestyWebsocketEnabled || upstreamConfig.UsesNamedUpstream {
builder.WriteString(" proxy_http_version 1.1;\n")
builder.WriteString(" proxy_set_header Upgrade $http_upgrade;\n")
builder.WriteString(" proxy_set_header Connection $connection_upgrade;\n")
}
if common.OpenRestyWebsocketEnabled {
builder.WriteString(" proxy_set_header Upgrade $http_upgrade;\n")
}
for _, header := range customHeaders {
builder.WriteString(fmt.Sprintf(" proxy_set_header %s %s;\n", header.Key, quoteNginxHeaderValue(header.Value)))
}
@@ -919,11 +932,14 @@ func buildPathExactMatchPattern(rules []string) string {
return fmt.Sprintf("^(?:%s)$", strings.Join(parts, "|"))
}
func renderProxyPassBlock(originURL string, cfg openRestyConfigSnapshot) string {
func renderProxyPassBlock(originURL string, upstreamConfig routeUpstreamConfig, cfg openRestyConfigSnapshot) string {
parsed, err := url.Parse(originURL)
if err != nil || parsed.Host == "" || parsed.Scheme == "" {
return fmt.Sprintf(" proxy_pass %s;\n", originURL)
}
if upstreamConfig.UsesNamedUpstream {
return fmt.Sprintf(" proxy_pass %s://%s;\n", upstreamConfig.Scheme, upstreamConfig.Name)
}
if !shouldUseRuntimeResolver(originURL, cfg.Resolvers) {
return fmt.Sprintf(" proxy_pass %s;\n", originURL)
}
@@ -949,6 +965,52 @@ func renderProxyPassBlock(originURL string, cfg openRestyConfigSnapshot) string
return builder.String()
}
func buildRouteUpstreamConfig(route *model.ProxyRoute, cfg openRestyConfigSnapshot) routeUpstreamConfig {
parsed, err := url.Parse(strings.TrimSpace(route.OriginURL))
if err != nil || parsed.Host == "" || parsed.Scheme == "" {
return routeUpstreamConfig{}
}
if shouldUseRuntimeResolver(route.OriginURL, cfg.Resolvers) {
return routeUpstreamConfig{}
}
if strings.TrimSpace(parsed.EscapedPath()) != "" && strings.TrimSpace(parsed.EscapedPath()) != "/" {
return routeUpstreamConfig{}
}
if parsed.RawQuery != "" {
return routeUpstreamConfig{}
}
return routeUpstreamConfig{
Name: buildRouteUpstreamName(route),
Scheme: parsed.Scheme,
Address: parsed.Host,
UsesNamedUpstream: true,
}
}
func buildRouteUpstreamName(route *model.ProxyRoute) string {
sanitized := strings.Map(func(r rune) rune {
switch {
case r >= 'a' && r <= 'z':
return r
case r >= 'A' && r <= 'Z':
return r + ('a' - 'A')
case r >= '0' && r <= '9':
return r
default:
return '_'
}
}, route.Domain)
sanitized = strings.Trim(sanitized, "_")
if sanitized == "" {
sanitized = "backend"
}
return fmt.Sprintf("backend_%s_%d", sanitized, route.ID)
}
func renderNamedUpstreamBlock(upstreamConfig routeUpstreamConfig) string {
return fmt.Sprintf("upstream %s {\n server %s max_fails=3 fail_timeout=10s;\n keepalive 128;\n}\n\n", upstreamConfig.Name, upstreamConfig.Address)
}
func shouldUseRuntimeResolver(originURL string, resolvers string) bool {
if strings.TrimSpace(resolvers) == "" {
return false
+24 -9
View File
@@ -141,8 +141,14 @@ func TestPublishConfigVersionRendersCustomHeaders(t *testing.T) {
if !strings.Contains(result.Version.RenderedConfig, "proxy_set_header Connection $connection_upgrade;") {
t.Fatal("expected rendered config to use normalized websocket connection header")
}
if !strings.Contains(result.Version.RenderedConfig, "proxy_pass https://origin.internal;") {
t.Fatal("expected rendered config to keep direct proxy_pass when no resolvers are configured")
if !strings.Contains(result.Version.RenderedConfig, "upstream backend_custom_example_com_1 {") {
t.Fatal("expected rendered config to define named upstream for simple origins")
}
if !strings.Contains(result.Version.RenderedConfig, "keepalive 128;") {
t.Fatal("expected rendered config to enable upstream keepalive")
}
if !strings.Contains(result.Version.RenderedConfig, "proxy_pass https://backend_custom_example_com_1;") {
t.Fatal("expected rendered config to proxy through named upstream when no resolver is required")
}
if strings.Contains(result.Version.RenderedConfig, "proxy_pass $openflare_upstream$request_uri;") {
t.Fatal("expected rendered config to avoid runtime-resolved proxy_pass when no resolvers are configured")
@@ -221,6 +227,9 @@ func TestPublishConfigVersionRendersRouteLevelCachePolicy(t *testing.T) {
if strings.Count(result.Version.RenderedConfig, "proxy_cache openflare_cache;") != 1 {
t.Fatal("expected only cache-enabled route to include proxy_cache directive")
}
if !strings.Contains(result.Version.RenderedConfig, "proxy_pass https://backend_static_example_com_1;") {
t.Fatal("expected cache-enabled route to proxy through named upstream")
}
if !strings.Contains(result.Version.SnapshotJSON, `"cache_enabled":true`) {
t.Fatal("expected snapshot to include route cache toggle")
}
@@ -255,8 +264,11 @@ func TestPublishConfigVersionOverridesOriginHostHeader(t *testing.T) {
if !strings.Contains(result.Version.RenderedConfig, `proxy_ssl_name "git.arctel.net";`) {
t.Fatal("expected rendered config to set proxy ssl name from origin host override")
}
if !strings.Contains(result.Version.RenderedConfig, "proxy_pass https://git.arctel.net;") {
t.Fatal("expected rendered config to keep direct proxy_pass for hostname origin when resolvers are blank")
if !strings.Contains(result.Version.RenderedConfig, "upstream backend_git_arctel_de_1 {") {
t.Fatal("expected rendered config to define named upstream for static hostname origins")
}
if !strings.Contains(result.Version.RenderedConfig, "proxy_pass https://backend_git_arctel_de_1;") {
t.Fatal("expected rendered config to proxy through named upstream for hostname origin when resolvers are blank")
}
if !strings.Contains(result.Version.SnapshotJSON, `"origin_host":"git.arctel.net"`) {
t.Fatal("expected snapshot to include origin_host override")
@@ -285,6 +297,9 @@ func TestPublishConfigVersionUsesRuntimeResolverWhenConfigured(t *testing.T) {
if !strings.Contains(result.Version.MainConfig, "resolver 1.1.1.1 8.8.8.8 valid=30s ipv6=off;") {
t.Fatal("expected main config to render configured resolver directive")
}
if strings.Contains(result.Version.RenderedConfig, "upstream backend_resolver_example_com_1 {") {
t.Fatal("expected runtime-resolved origin to avoid named upstream block")
}
if !strings.Contains(result.Version.RenderedConfig, `set $openflare_upstream "https://origin.internal";`) {
t.Fatal("expected rendered config to use runtime upstream variable when resolvers are configured")
}
@@ -309,8 +324,11 @@ func TestPublishConfigVersionKeepsDirectProxyPassForIPOrigins(t *testing.T) {
if err != nil {
t.Fatalf("PublishConfigVersion failed: %v", err)
}
if !strings.Contains(result.Version.RenderedConfig, "proxy_pass http://10.0.0.8:8080;") {
t.Fatal("expected rendered config to keep direct proxy_pass for IP origin")
if !strings.Contains(result.Version.RenderedConfig, "upstream backend_ip_origin_example_com_1 {") {
t.Fatal("expected rendered config to define named upstream for static IP origins")
}
if !strings.Contains(result.Version.RenderedConfig, "proxy_pass http://backend_ip_origin_example_com_1;") {
t.Fatal("expected rendered config to proxy through named upstream for IP origin")
}
if strings.Contains(result.Version.RenderedConfig, `set $openflare_upstream "http://10.0.0.8:8080"`) {
t.Fatal("expected rendered config to avoid runtime resolver variables for IP origin")
@@ -342,9 +360,6 @@ func TestPreviewConfigVersionCanDisableWebsocketHeaders(t *testing.T) {
if strings.Contains(preview.RenderedConfig, "proxy_set_header Upgrade $http_upgrade;") {
t.Fatal("expected preview config to omit websocket upgrade header when disabled")
}
if strings.Contains(preview.RenderedConfig, "proxy_set_header Connection $connection_upgrade;") {
t.Fatal("expected preview config to omit websocket connection header when disabled")
}
}
func TestPreviewAndDiffConfigVersion(t *testing.T) {