From 4024c85a0cc91bbcbc17a9291a170a6d9f1f82cb Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 15 Mar 2026 13:57:32 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E6=B7=BB=E5=8A=A0=20Web?= =?UTF-8?q?Socket=20=E8=BF=9E=E6=8E=A5=E5=8D=87=E7=BA=A7=E6=98=A0=E5=B0=84?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=E4=B8=BB=E9=85=8D=E7=BD=AE=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=92=8C=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- atsf_server/common/constants.go | 2 +- atsf_server/service/config_version.go | 26 ++++++++++++++++++++++-- atsf_server/service/https_phase1_test.go | 16 +++++++++++++-- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/atsf_server/common/constants.go b/atsf_server/common/constants.go index 3d652208..5a74b6ad 100644 --- a/atsf_server/common/constants.go +++ b/atsf_server/common/constants.go @@ -101,7 +101,7 @@ events { http { include mime.types; default_type application/octet-stream; - log_format atsflare_json escape=json '{"ts":"$time_iso8601","host":"$host","path":"$request_uri","remote_addr":"$remote_addr","status":$status,"request_time":$request_time,"bytes_sent":$body_bytes_sent,"request_length":$request_length}'; +{{OpenRestyWebsocketConnectionMap}} log_format atsflare_json escape=json '{"ts":"$time_iso8601","host":"$host","path":"$request_uri","remote_addr":"$remote_addr","status":$status,"request_time":$request_time,"bytes_sent":$body_bytes_sent,"request_length":$request_length}'; access_log {{OpenRestyAccessLogPath}} atsflare_json; sendfile on; tcp_nopush on; diff --git a/atsf_server/service/config_version.go b/atsf_server/service/config_version.go index bc401a4d..0d46db36 100644 --- a/atsf_server/service/config_version.go +++ b/atsf_server/service/config_version.go @@ -121,7 +121,7 @@ const ( nginxAccessLogPlaceholder = "__ATSF_ACCESS_LOG__" nginxLuaDirPlaceholder = "__ATSF_LUA_DIR__" nginxObservabilityListenPlaceholder = "__ATSF_OBSERVABILITY_LISTEN__" - nginxObservabilityPortPlaceholder = "__ATSF_OBSERVABILITY_PORT__" + openRestyWebsocketConnectionMapTag = "{{OpenRestyWebsocketConnectionMap}}" ) var requiredMainConfigTemplatePlaceholders = []string{ @@ -643,11 +643,13 @@ func defaultOpenRestyMainConfigTemplate() string { } func renderMainConfigTemplate(templateText string, cfg openRestyConfigSnapshot) string { + templateText = ensureWebsocketConnectionMapPlaceholder(templateText) replacer := strings.NewReplacer( "{{OpenRestyWorkerProcesses}}", cfg.WorkerProcesses, "{{OpenRestyWorkerConnections}}", fmt.Sprintf("%d", cfg.WorkerConnections), "{{OpenRestyWorkerRlimitNofile}}", fmt.Sprintf("%d", cfg.WorkerRlimitNofile), "{{OpenRestyAccessLogPath}}", nginxAccessLogPlaceholder, + openRestyWebsocketConnectionMapTag, renderWebsocketConnectionMap(cfg.WebsocketEnabled), "{{OpenRestyEventsUseDirective}}", renderTemplateDirective(cfg.EventsUse != "", fmt.Sprintf("use %s;", cfg.EventsUse)), "{{OpenRestyEventsMultiAcceptDirective}}", renderTemplateDirective(cfg.EventsMultiAcceptEnabled, "multi_accept on;"), "{{OpenRestyKeepaliveTimeout}}", fmt.Sprintf("%d", cfg.KeepaliveTimeout), @@ -681,6 +683,26 @@ func renderTemplateDirective(enabled bool, statement string) string { return fmt.Sprintf(" %s\n", statement) } +func ensureWebsocketConnectionMapPlaceholder(templateText string) string { + if strings.Contains(templateText, openRestyWebsocketConnectionMapTag) { + return templateText + } + return strings.Replace(templateText, "{{OpenRestyRouteConfigInclude}}", openRestyWebsocketConnectionMapTag+" include {{OpenRestyRouteConfigInclude}}", 1) +} + +func renderWebsocketConnectionMap(enabled bool) string { + if !enabled { + return "" + } + return stringsJoinLines( + " map $http_upgrade $connection_upgrade {", + " default upgrade;", + " '' close;", + " }", + "", + ) +} + func renderOpenRestyCacheTemplateBlock(cfg openRestyConfigSnapshot) string { lines := make([]string, 0, 8) if !cfg.CacheEnabled { @@ -769,7 +791,7 @@ func renderProxyHeaderBlock(customHeaders []ProxyRouteCustomHeaderInput) string if common.OpenRestyWebsocketEnabled { builder.WriteString(" proxy_http_version 1.1;\n") builder.WriteString(" proxy_set_header Upgrade $http_upgrade;\n") - builder.WriteString(" proxy_set_header Connection $http_connection;\n") + builder.WriteString(" proxy_set_header Connection $connection_upgrade;\n") } for _, header := range customHeaders { builder.WriteString(fmt.Sprintf(" proxy_set_header %s %s;\n", header.Key, quoteNginxHeaderValue(header.Value))) diff --git a/atsf_server/service/https_phase1_test.go b/atsf_server/service/https_phase1_test.go index 37d82072..7e55d8d4 100644 --- a/atsf_server/service/https_phase1_test.go +++ b/atsf_server/service/https_phase1_test.go @@ -63,6 +63,9 @@ func TestCreateTLSCertificateAndRenderHTTPSConfig(t *testing.T) { if !strings.Contains(result.Version.MainConfig, "listen __ATSF_OBSERVABILITY_LISTEN__;") { t.Fatal("expected main config to include managed openresty observability listen placeholder") } + if !strings.Contains(result.Version.MainConfig, "map $http_upgrade $connection_upgrade {") { + t.Fatal("expected main config to include websocket connection upgrade map") + } if strings.Contains(result.Version.MainConfig, "allow 127.0.0.1;") { t.Fatal("expected main config to avoid hard-coded allow rules on observability server") } @@ -132,7 +135,7 @@ func TestPublishConfigVersionRendersCustomHeaders(t *testing.T) { if !strings.Contains(result.Version.RenderedConfig, "proxy_set_header Upgrade $http_upgrade;") { t.Fatal("expected rendered config to forward websocket upgrade header") } - if !strings.Contains(result.Version.RenderedConfig, "proxy_set_header Connection $http_connection;") { + if !strings.Contains(result.Version.RenderedConfig, "proxy_set_header Connection $connection_upgrade;") { t.Fatal("expected rendered config to forward websocket connection header") } } @@ -162,9 +165,12 @@ 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 $http_connection;") { + if strings.Contains(preview.RenderedConfig, "proxy_set_header Connection $connection_upgrade;") { t.Fatal("expected preview config to omit websocket connection header when disabled") } + if strings.Contains(preview.MainConfig, "map $http_upgrade $connection_upgrade {") { + t.Fatal("expected preview main config to omit websocket connection map when disabled") + } } func TestPreviewAndDiffConfigVersion(t *testing.T) { @@ -326,6 +332,9 @@ func TestCreateTLSCertificateRejectsInvalidPEM(t *testing.T) { func TestOpenRestyMainConfigTemplateRenderAndValidate(t *testing.T) { setupServiceTestDB(t) + if err := model.UpdateOption("OpenRestyWebsocketEnabled", "true"); err != nil { + t.Fatalf("UpdateOption OpenRestyWebsocketEnabled failed: %v", err) + } customTemplate := strings.ReplaceAll( common.OpenRestyMainConfigTemplate, @@ -355,6 +364,9 @@ func TestOpenRestyMainConfigTemplateRenderAndValidate(t *testing.T) { if !strings.Contains(preview.MainConfig, "access_log __ATSF_ACCESS_LOG__ atsflare_json;") { t.Fatal("expected preview main config to preserve managed access log placeholder") } + if !strings.Contains(preview.MainConfig, "map $http_upgrade $connection_upgrade {") { + t.Fatal("expected preview main config to include websocket connection map") + } invalidTemplate := strings.ReplaceAll( common.OpenRestyMainConfigTemplate,