From ee047cb35133085fbc34cc8addeb81401b428005 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 21 Jun 2026 12:18:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Pages=20=E7=AB=99=E7=82=B9?= =?UTF-8?q?=E5=9C=A8=E6=9C=AA=E5=90=AF=E7=94=A8=20SPA=20Fallback=20?= =?UTF-8?q?=E6=97=B6=E8=AE=BF=E9=97=AE=E6=A0=B9=E8=B7=AF=E5=BE=84=20`/`=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=20404=EF=BC=9AOpenResty=20=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20`location=20=3D=20/`=20=E7=B2=BE=E7=A1=AE?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=EF=BC=8C=E9=80=9A=E8=BF=87=20`try=5Ffiles`?= =?UTF-8?q?=20=E6=8F=90=E4=BE=9B=E5=85=A5=E5=8F=A3=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=88`index`=20=E6=8C=87=E4=BB=A4=E5=9C=A8=20`try=5Ffiles?= =?UTF-8?q?=20...=20=3D404`=20=E5=9C=BA=E6=99=AF=E4=B8=8B=E4=B8=8D?= =?UTF-8?q?=E4=BC=9A=E4=BD=9C=E7=94=A8=E4=BA=8E=E6=A0=B9=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog/index.md | 2 + .../proxy-routes/detail/page-client.tsx | 4 +- pkg/render/openresty/render.go | 22 ++- pkg/render/openresty/render_test.go | 127 ++++++++++++++++++ 4 files changed, 150 insertions(+), 5 deletions(-) diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 566d32d8..3154b23f 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -22,6 +22,8 @@ sidebar: false ### 修复 +- 修复 Pages 站点根路径 `/` 访问异常:OpenResty 渲染增加 `location = /` 精确匹配;未启用 SPA Fallback 时直接提供入口文件(`index` 指令在 `try_files ... =404` 场景下不生效);启用 SPA Fallback 时避免 `try_files $uri $uri/ /index.html` 因 `$uri/` 命中站点根目录触发内部重定向循环而返回 500。 + - 修复代理路由详情认证配置 Tab:移除 PoW 配置(PoW 仅在 WAF 规则组中设置);保留 Basic Auth 保存能力;移除页头重复的「保存当前分区」按钮。 - 修复 Pages 路由发布失败并报 `pages module is not available`:配置快照发布流程补齐 Pages 项目激活部署解析与 `pages_deployment` 写入。 diff --git a/frontend/app/(main)/proxy-routes/detail/page-client.tsx b/frontend/app/(main)/proxy-routes/detail/page-client.tsx index d18501d7..fca80eec 100644 --- a/frontend/app/(main)/proxy-routes/detail/page-client.tsx +++ b/frontend/app/(main)/proxy-routes/detail/page-client.tsx @@ -29,7 +29,6 @@ export function ProxyRouteDetailPageClient() { const [route, setRoute] = useState(null); const [loading, setLoading] = useState(true); const [loadError, setLoadError] = useState(null); - const [sectionSaving, setSectionSaving] = useState(false); const handleSectionChange = useCallback( (section: ProxyRouteConfigSection) => { @@ -113,7 +112,6 @@ export function ProxyRouteDetailPageClient() { const sectionProps = { route, onRouteUpdate: handleRouteUpdate, - onSavingChange: setSectionSaving, }; return ( @@ -150,7 +148,7 @@ export function ProxyRouteDetailPageClient() { - + diff --git a/pkg/render/openresty/render.go b/pkg/render/openresty/render.go index 44a47076..6f8c99b0 100644 --- a/pkg/render/openresty/render.go +++ b/pkg/render/openresty/render.go @@ -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 { diff --git a/pkg/render/openresty/render_test.go b/pkg/render/openresty/render_test.go index de6f2daf..57e19fc5 100644 --- a/pkg/render/openresty/render_test.go +++ b/pkg/render/openresty/render_test.go @@ -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) + } +}