修复 Pages 站点在未启用 SPA Fallback 时访问根路径 / 返回 404:OpenResty 渲染增加 location = / 精确匹配,通过 try_files 提供入口文件(index 指令在 try_files ... =404 场景下不会作用于根路径)。

This commit is contained in:
ryan
2026-06-21 12:18:20 +08:00
parent 6ed3c0c81f
commit ee047cb351
4 changed files with 150 additions and 5 deletions
+20 -2
View File
@@ -360,7 +360,7 @@ func renderPagesAPIProxyLocationBlock(deployment *PagesDeployment) string {
}
func renderHTTPPagesServer(serverNames string, siteName string, deployment *PagesDeployment, limitConfig routeLimitConfig, powEnabled bool, basicAuthEnabled bool, basicAuthUsername string, basicAuthPassword string) string {
return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n%s%s root %s;\n index %s;%s\n\n location / {\n%s%s }\n%s}\n\n", serverNames, renderAccessBlock(siteName, powEnabled), renderPowLocationBlocks(powEnabled), quoteNginxStringLiteral(pagesDeploymentRoot(deployment)), quoteNginxStringLiteral(pagesEntryFile(deployment)), renderPagesAPIProxyLocationBlock(deployment), renderBasicAuthBlock(basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderPagesLocationBlock(deployment, limitConfig), renderPowStaticLocationBlock(powEnabled))
return fmt.Sprintf("server {\n listen 80;\n server_name %s;\n%s%s root %s;\n index %s;%s%s\n\n location / {\n%s%s }\n%s}\n\n", serverNames, renderAccessBlock(siteName, powEnabled), renderPowLocationBlocks(powEnabled), quoteNginxStringLiteral(pagesDeploymentRoot(deployment)), quoteNginxStringLiteral(pagesEntryFile(deployment)), renderPagesAPIProxyLocationBlock(deployment), renderPagesRootLocationBlock(deployment, limitConfig, basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderBasicAuthBlock(basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderPagesLocationBlock(deployment, limitConfig), renderPowStaticLocationBlock(powEnabled))
}
func renderHTTPRedirectServer(serverNames string) string {
@@ -388,7 +388,25 @@ func renderHTTPSPagesServer(serverNames string, siteName string, certificateID u
h3Listen = " listen 443 quic;\n"
h3Header = " add_header Alt-Svc 'h3=\":443\"; ma=86400';\n"
}
return fmt.Sprintf("server {\n listen 443 ssl;\n%s http2 on;\n server_name %s;\n ssl_certificate %s;\n ssl_certificate_key %s;\n%s%s%s root %s;\n index %s;%s\n\n location / {\n%s%s }\n%s}\n\n", h3Listen, serverNames, certPath, keyPath, h3Header, renderAccessBlock(siteName, powEnabled), renderPowLocationBlocks(powEnabled), quoteNginxStringLiteral(pagesDeploymentRoot(deployment)), quoteNginxStringLiteral(pagesEntryFile(deployment)), renderPagesAPIProxyLocationBlock(deployment), renderBasicAuthBlock(basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderPagesLocationBlock(deployment, limitConfig), renderPowStaticLocationBlock(powEnabled))
return fmt.Sprintf("server {\n listen 443 ssl;\n%s http2 on;\n server_name %s;\n ssl_certificate %s;\n ssl_certificate_key %s;\n%s%s%s root %s;\n index %s;%s%s\n\n location / {\n%s%s }\n%s}\n\n", h3Listen, serverNames, certPath, keyPath, h3Header, renderAccessBlock(siteName, powEnabled), renderPowLocationBlocks(powEnabled), quoteNginxStringLiteral(pagesDeploymentRoot(deployment)), quoteNginxStringLiteral(pagesEntryFile(deployment)), renderPagesAPIProxyLocationBlock(deployment), renderPagesRootLocationBlock(deployment, limitConfig, basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderBasicAuthBlock(basicAuthEnabled, basicAuthUsername, basicAuthPassword), renderPagesLocationBlock(deployment, limitConfig), renderPowStaticLocationBlock(powEnabled))
}
func renderPagesRootLocationBlock(deployment *PagesDeployment, limitConfig routeLimitConfig, basicAuthEnabled bool, basicAuthUsername string, basicAuthPassword string) string {
tryFile := pagesRootTryFile(deployment)
var builder strings.Builder
builder.WriteString("\n location = / {\n")
builder.WriteString(renderBasicAuthBlock(basicAuthEnabled, basicAuthUsername, basicAuthPassword))
builder.WriteString(renderRouteLimitBlock(limitConfig))
fmt.Fprintf(&builder, " try_files %s =404;\n", tryFile)
builder.WriteString(" }\n")
return builder.String()
}
func pagesRootTryFile(deployment *PagesDeployment) string {
if deployment != nil && deployment.SPAFallbackEnabled {
return pagesFallbackPath(deployment)
}
return "/" + pagesEntryFile(deployment)
}
func renderPagesLocationBlock(deployment *PagesDeployment, limitConfig routeLimitConfig) string {
+127
View File
@@ -222,3 +222,130 @@ func TestRenderPagesAPIProxyLocationBlock(t *testing.T) {
})
}
}
func TestRenderPagesRootLocationBlock(t *testing.T) {
tests := []struct {
name string
deployment *PagesDeployment
expected []string
unexpected []string
}{
{
name: "spa fallback disabled serves entry file at root",
deployment: &PagesDeployment{
SPAFallbackEnabled: false,
EntryFile: "index.html",
},
expected: []string{
"location = / {",
"try_files /index.html =404;",
},
},
{
name: "spa fallback disabled with custom entry file",
deployment: &PagesDeployment{
SPAFallbackEnabled: false,
EntryFile: "app.html",
},
expected: []string{
"location = / {",
"try_files /app.html =404;",
},
},
{
name: "spa fallback enabled serves fallback file at root",
deployment: &PagesDeployment{
SPAFallbackEnabled: true,
SPAFallbackPath: "/index.html",
},
expected: []string{
"location = / {",
"try_files /index.html =404;",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := renderPagesRootLocationBlock(tt.deployment, routeLimitConfig{}, false, "", "")
if len(tt.expected) == 1 && tt.expected[0] == "" {
if got != "" {
t.Fatalf("expected empty output, got: %q", got)
}
return
}
for _, exp := range tt.expected {
if !strings.Contains(got, exp) {
t.Errorf("expected output to contain %q, but got:\n%s", exp, got)
}
}
for _, unexp := range tt.unexpected {
if strings.Contains(got, unexp) {
t.Errorf("expected output NOT to contain %q, but got:\n%s", unexp, got)
}
}
})
}
}
func TestRenderRouteConfigPagesWithoutSPAFallbackServesRoot(t *testing.T) {
doc := Document{
Routes: []Route{
{
ID: 1,
Domain: "speedtest.example.com",
UpstreamType: "pages",
EnableHTTPS: false,
PagesDeployment: &PagesDeployment{
LocalRoot: "/data/var/lib/openflare/pages/deployments/1/current",
EntryFile: "index.html",
SPAFallbackEnabled: false,
},
},
},
}
routeConfig, err := RenderRouteConfig(doc, nil)
if err != nil {
t.Fatalf("RenderRouteConfig() error = %v", err)
}
if !strings.Contains(routeConfig, "location = / {") {
t.Fatalf("expected root location block, got:\n%s", routeConfig)
}
if !strings.Contains(routeConfig, "try_files /index.html =404;") {
t.Fatalf("expected root try_files for entry file, got:\n%s", routeConfig)
}
if !strings.Contains(routeConfig, "try_files $uri $uri/ =404;") {
t.Fatalf("expected static file try_files in location /, got:\n%s", routeConfig)
}
}
func TestRenderRouteConfigPagesWithSPAFallbackServesRoot(t *testing.T) {
doc := Document{
Routes: []Route{
{
ID: 1,
Domain: "speedtest.example.com",
UpstreamType: "pages",
EnableHTTPS: false,
PagesDeployment: &PagesDeployment{
LocalRoot: "/data/var/lib/openflare/pages/deployments/1/current",
EntryFile: "index.html",
SPAFallbackEnabled: true,
SPAFallbackPath: "/index.html",
},
},
},
}
routeConfig, err := RenderRouteConfig(doc, nil)
if err != nil {
t.Fatalf("RenderRouteConfig() error = %v", err)
}
if !strings.Contains(routeConfig, "location = / {") {
t.Fatalf("expected root location block for spa fallback, got:\n%s", routeConfig)
}
if !strings.Contains(routeConfig, "try_files $uri $uri/ /index.html;") {
t.Fatalf("expected spa fallback try_files in location /, got:\n%s", routeConfig)
}
}