feat(converter): 支持 server 级别的 root 和 index 指令转换

- 在 convertServerBlock 中收集 server 级别的 root/index 指令
- 如果没有显式的 location / 静态配置,创建默认静态配置
- 如果 location / 是 proxy 类型,不创建静态配置
- 添加 3 个测试用例覆盖不同场景

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-27 11:38:47 +08:00
parent b290cea0f6
commit 78ca32748c
2 changed files with 146 additions and 0 deletions

View File

@ -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{

View File

@ -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 {