From 78ca32748c2d59ffb0819f7cc297356eb4fccdfe Mon Sep 17 00:00:00 2001 From: xfy Date: Mon, 27 Apr 2026 11:38:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(converter):=20=E6=94=AF=E6=8C=81=20server?= =?UTF-8?q?=20=E7=BA=A7=E5=88=AB=E7=9A=84=20root=20=E5=92=8C=20index=20?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 convertServerBlock 中收集 server 级别的 root/index 指令 - 如果没有显式的 location / 静态配置,创建默认静态配置 - 如果 location / 是 proxy 类型,不创建静态配置 - 添加 3 个测试用例覆盖不同场景 Co-Authored-By: Claude Opus 4.7 --- internal/converter/nginx/converter.go | 38 ++++++++ internal/converter/nginx/converter_test.go | 108 +++++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/internal/converter/nginx/converter.go b/internal/converter/nginx/converter.go index 327aeb7..5ac1ae9 100644 --- a/internal/converter/nginx/converter.go +++ b/internal/converter/nginx/converter.go @@ -200,6 +200,8 @@ func convertUpstreamServer(d *Directive) config.ProxyTarget { func convertServerBlock(d *Directive, upstreams map[string]*upstreamInfo, result *ConvertResult) config.ServerConfig { server := config.ServerConfig{} var sslDetected bool + var serverRoot string + var serverIndex []string for i := range d.Block { bd := &d.Block[i] @@ -211,6 +213,12 @@ func convertServerBlock(d *Directive, upstreams map[string]*upstreamInfo, result } case "server_name": parseServerName(bd, &server) + case "root": + if len(bd.Args) > 0 { + serverRoot = bd.Args[0] + } + case "index": + serverIndex = append(serverIndex, bd.Args...) case "ssl_certificate": if len(bd.Args) > 0 { server.SSL.Cert = bd.Args[0] @@ -273,6 +281,36 @@ func convertServerBlock(d *Directive, upstreams map[string]*upstreamInfo, result } } + // If server-level root is defined but no explicit location / static config exists, + // create a default static configuration for "/". + // However, if location / is a proxy, don't create static config. + if serverRoot != "" { + hasRootLocation := false + // Check if there's already a static config for "/" + for _, s := range server.Static { + if s.Path == "/" { + hasRootLocation = true + break + } + } + // Check if location / is a proxy + if !hasRootLocation { + for _, p := range server.Proxy { + if p.Path == "/" { + hasRootLocation = true + break + } + } + } + if !hasRootLocation { + server.Static = append(server.Static, config.StaticConfig{ + Path: "/", + Root: serverRoot, + Index: serverIndex, + }) + } + } + // Warn if SSL was detected (listen ... ssl) but cert/key are not configured. if sslDetected && (server.SSL.Cert == "" || server.SSL.Key == "") { result.Warnings = append(result.Warnings, Warning{ diff --git a/internal/converter/nginx/converter_test.go b/internal/converter/nginx/converter_test.go index 085d5ea..fd663f3 100644 --- a/internal/converter/nginx/converter_test.go +++ b/internal/converter/nginx/converter_test.go @@ -5,6 +5,8 @@ import ( "strings" "testing" "time" + + "rua.plus/lolly/internal/config" ) // helper: parse nginx config string and convert. @@ -127,6 +129,112 @@ http { } } +func TestConvertServerLevelRoot(t *testing.T) { + input := ` +http { + server { + listen 80; + root /var/www/html; + index index.html index.htm index.php; + } +} +` + result, err := convertString(t, input) + if err != nil { + t.Fatalf("convert error: %v", err) + } + + s := result.Config.Servers[0] + if len(s.Static) != 1 { + t.Fatalf("expected 1 static, got %d", len(s.Static)) + } + + st := s.Static[0] + if st.Path != "/" { + t.Errorf("expected path /, got %s", st.Path) + } + if st.Root != "/var/www/html" { + t.Errorf("expected root /var/www/html, got %s", st.Root) + } + if len(st.Index) != 3 || st.Index[0] != "index.html" || st.Index[1] != "index.htm" || st.Index[2] != "index.php" { + t.Errorf("expected [index.html index.htm index.php], got %v", st.Index) + } +} + +func TestConvertServerLevelRootWithLocation(t *testing.T) { + // When both server-level root and location /static exist, both should be converted + input := ` +http { + server { + listen 80; + root /var/www/html; + index index.html; + location /static/ { + root /var/www/static; + } + } +} +` + result, err := convertString(t, input) + if err != nil { + t.Fatalf("convert error: %v", err) + } + + s := result.Config.Servers[0] + if len(s.Static) != 2 { + t.Fatalf("expected 2 static, got %d", len(s.Static)) + } + + // Find the root location + var rootStatic, staticLoc *config.StaticConfig + for i := range s.Static { + if s.Static[i].Path == "/" { + rootStatic = &s.Static[i] + } else if s.Static[i].Path == "/static/" { + staticLoc = &s.Static[i] + } + } + + if rootStatic == nil { + t.Error("expected root static config at path /") + } else if rootStatic.Root != "/var/www/html" { + t.Errorf("expected root /var/www/html, got %s", rootStatic.Root) + } + + if staticLoc == nil { + t.Error("expected static config at path /static/") + } else if staticLoc.Root != "/var/www/static" { + t.Errorf("expected root /var/www/static, got %s", staticLoc.Root) + } +} + +func TestConvertServerLevelRootNotCreatedForProxy(t *testing.T) { + // When location / is a proxy, server-level root should NOT create a static config + input := ` +http { + server { + listen 80; + root /var/www/html; + location / { + proxy_pass http://backend; + } + } +} +` + result, err := convertString(t, input) + if err != nil { + t.Fatalf("convert error: %v", err) + } + + s := result.Config.Servers[0] + if len(s.Static) != 0 { + t.Errorf("expected 0 static (location / is proxy), got %d", len(s.Static)) + } + if len(s.Proxy) != 1 { + t.Errorf("expected 1 proxy, got %d", len(s.Proxy)) + } +} + func TestConvertUpstream(t *testing.T) { input := ` upstream backend {