diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 129e056f..982a4840 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -10,6 +10,9 @@ sidebar: false ## [Unreleased] +### ✨ 新功能 +- 代理路由详情支持单独关闭 HTTP/2,默认仍启用;关闭后该路由生成的 HTTPS 配置不再启用 HTTP/2。 + ### 🛠 修复 - 节点配置应用失败后,Agent 会按指数退避自动强制重试,最长间隔为 5 分钟;成功后停止重试,重启后也会恢复未完成的重试。 diff --git a/docs/docs.go b/docs/docs.go index dfc63fa3..bdbd6bec 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -19426,6 +19426,9 @@ const docTemplate = `{ "$ref": "#/definitions/proxy_route.CustomHeaderInput" } }, + "enable_http2": { + "type": "boolean" + }, "enable_https": { "type": "boolean" }, @@ -19542,6 +19545,9 @@ const docTemplate = `{ "custom_headers": { "type": "string" }, + "enable_http2": { + "type": "boolean" + }, "enable_https": { "type": "boolean" }, diff --git a/docs/swagger.json b/docs/swagger.json index 737dae8c..ce901abb 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -19419,6 +19419,9 @@ "$ref": "#/definitions/proxy_route.CustomHeaderInput" } }, + "enable_http2": { + "type": "boolean" + }, "enable_https": { "type": "boolean" }, @@ -19535,6 +19538,9 @@ "custom_headers": { "type": "string" }, + "enable_http2": { + "type": "boolean" + }, "enable_https": { "type": "boolean" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index e8a039aa..c240bfc2 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -3063,6 +3063,8 @@ definitions: items: $ref: '#/definitions/proxy_route.CustomHeaderInput' type: array + enable_http2: + type: boolean enable_https: type: boolean enabled: @@ -3140,6 +3142,8 @@ definitions: type: array custom_headers: type: string + enable_http2: + type: boolean enable_https: type: boolean enabled: diff --git a/frontend/app/(main)/proxy-routes/components/helpers.ts b/frontend/app/(main)/proxy-routes/components/helpers.ts index aaa79c8e..33fd51fb 100644 --- a/frontend/app/(main)/proxy-routes/components/helpers.ts +++ b/frontend/app/(main)/proxy-routes/components/helpers.ts @@ -380,6 +380,7 @@ export function buildPayloadFromRoute( upstreams: (route.upstream_list ?? []).slice(1), enabled: route.enabled, enable_https: route.enable_https, + enable_http2: route.enable_http2, redirect_http: route.redirect_http, limit_conn_per_server: route.limit_conn_per_server, limit_conn_per_ip: route.limit_conn_per_ip, diff --git a/frontend/app/(main)/proxy-routes/detail/components/proxy-section.tsx b/frontend/app/(main)/proxy-routes/detail/components/proxy-section.tsx index 96805d37..8ee5dfa9 100644 --- a/frontend/app/(main)/proxy-routes/detail/components/proxy-section.tsx +++ b/frontend/app/(main)/proxy-routes/detail/components/proxy-section.tsx @@ -24,6 +24,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import type { ProxyRouteItem } from '@/lib/services/openflare'; import { NodeService, PagesService } from '@/lib/services/openflare'; @@ -50,6 +51,7 @@ type ReverseProxyValues = { tunnel_target_protocol?: 'http' | 'https'; pages_project_id?: string; custom_headers_text: string; + enable_http2: boolean; }; interface ProxySectionProps { @@ -74,6 +76,7 @@ export function ProxySection({ tunnel_target_protocol: z.enum(['http', 'https']).optional(), pages_project_id: z.string().optional(), custom_headers_text: z.string(), + enable_http2: z.boolean(), }) .superRefine((value, context) => { if (value.upstream_type === 'direct') { @@ -174,6 +177,7 @@ export function ProxySection({ ? String(route.pages_project_id) : '', custom_headers_text: customHeadersToText(route.custom_header_list), + enable_http2: route.enable_http2 ?? true, }, }); @@ -190,6 +194,7 @@ export function ProxySection({ ? String(route.pages_project_id) : '', custom_headers_text: customHeadersToText(route.custom_header_list), + enable_http2: route.enable_http2 ?? true, }); }, [form, route]); @@ -267,6 +272,7 @@ export function ProxySection({ values.upstream_type === 'pages' && values.pages_project_id ? Number(values.pages_project_id) : null, + enable_http2: values.enable_http2, }, t('proxySaved'), ); @@ -306,6 +312,25 @@ export function ProxySection({ )} /> + ( + +
+ {t('enableHttp2')} + {t('enableHttp2Desc')} +
+ + + +
+ )} + /> + {upstreamType === 'direct' ? ( 0 { - builder.WriteString(renderHTTPSPagesServer(renderServerNames(assignedDomains), displayName, certID, route.PagesDeployment, limitConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, routeSWEnabled(assignedDomains, cfg), cfg)) + builder.WriteString(renderHTTPSPagesServer(renderServerNames(assignedDomains), displayName, certID, route.PagesDeployment, limitConfig, powEnabled, route.EnableHTTP2, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, routeSWEnabled(assignedDomains, cfg), cfg)) } } } @@ -101,7 +101,7 @@ func renderProxyRouteHTTPS( } for _, certID := range certIDs { if assignedDomains := partition.domainsByCertID[certID]; len(assignedDomains) > 0 { - builder.WriteString(renderHTTPSServer(renderServerNames(assignedDomains), displayName, route.OriginURL, route.OriginHost, certID, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, routeSWEnabled(assignedDomains, cfg), cfg)) + builder.WriteString(renderHTTPSServer(renderServerNames(assignedDomains), displayName, route.OriginURL, route.OriginHost, certID, route.CustomHeaders, cacheConfig, limitConfig, upstreamConfig, powEnabled, route.EnableHTTP2, route.BasicAuthEnabled, route.BasicAuthUsername, route.BasicAuthPassword, routeSWEnabled(assignedDomains, cfg), cfg)) } } } diff --git a/pkg/render/openresty/service_worker_test.go b/pkg/render/openresty/service_worker_test.go index 7da60f18..1567f334 100644 --- a/pkg/render/openresty/service_worker_test.go +++ b/pkg/render/openresty/service_worker_test.go @@ -130,8 +130,8 @@ func TestRenderServiceWorkerChallengerHTTPExclusion(t *testing.T) { for name, rendered := range map[string]string{ "proxy": renderHTTPProxyServer("example.com", "example.com", "http://127.0.0.1:8080", "", nil, routeCacheConfig{}, routeLimitConfig{}, routeUpstreamConfig{}, false, false, "", "", false, cfg), "pages": renderHTTPPagesServer("example.com", "example.com", nil, routeLimitConfig{}, false, false, "", "", false, cfg), - "https": renderHTTPSServer("example.com", "example.com", "http://127.0.0.1:8080", "", 1, nil, routeCacheConfig{}, routeLimitConfig{}, routeUpstreamConfig{}, false, false, "", "", true, cfg), - "hpages": renderHTTPSPagesServer("example.com", "example.com", 1, nil, routeLimitConfig{}, false, false, "", "", true, cfg), + "https": renderHTTPSServer("example.com", "example.com", "http://127.0.0.1:8080", "", 1, nil, routeCacheConfig{}, routeLimitConfig{}, routeUpstreamConfig{}, false, true, false, "", "", true, cfg), + "hpages": renderHTTPSPagesServer("example.com", "example.com", 1, nil, routeLimitConfig{}, false, true, false, "", "", true, cfg), } { if strings.Contains(rendered, "access_by_lua_block") && strings.Count(rendered, "access_by_lua_block") != 1 { t.Fatalf("%s: expected at most one access block, got:\n%s", name, rendered) @@ -145,13 +145,13 @@ func TestRenderServiceWorkerChallengerHTTPExclusion(t *testing.T) { if strings.Contains(httpPages, "sw.runtime") || strings.Contains(httpPages, "openflare_sw_challenge") || strings.Contains(httpPages, "location = /sw.js") { t.Fatalf("HTTP pages server must not carry SW intercept, got:\n%s", httpPages) } - httpsProxy := renderHTTPSServer("example.com", "example.com", "http://127.0.0.1:8080", "", 1, nil, routeCacheConfig{}, routeLimitConfig{}, routeUpstreamConfig{}, false, false, "", "", true, cfg) + httpsProxy := renderHTTPSServer("example.com", "example.com", "http://127.0.0.1:8080", "", 1, nil, routeCacheConfig{}, routeLimitConfig{}, routeUpstreamConfig{}, false, true, false, "", "", true, cfg) for _, want := range []string{"sw.runtime", "location = /sw.js", "location = /offline.html", "__openflare_sw_challenge"} { if !strings.Contains(httpsProxy, want) { t.Fatalf("HTTPS proxy server missing %q, got:\n%s", want, httpsProxy) } } - httpsPages := renderHTTPSPagesServer("example.com", "example.com", 1, nil, routeLimitConfig{}, false, false, "", "", true, cfg) + httpsPages := renderHTTPSPagesServer("example.com", "example.com", 1, nil, routeLimitConfig{}, false, true, false, "", "", true, cfg) for _, want := range []string{"sw.runtime", "location = /sw.js", "location = /offline.html", "__openflare_sw_challenge"} { if !strings.Contains(httpsPages, want) { t.Fatalf("HTTPS pages server missing %q, got:\n%s", want, httpsPages) @@ -179,7 +179,7 @@ func TestRouteSWEnabled(t *testing.T) { func TestRenderHTTPSServerSWScope(t *testing.T) { render := func(swEnabled bool) string { - return renderHTTPSServer("example.com", "example.com", "http://127.0.0.1:8080", "", 1, nil, routeCacheConfig{}, routeLimitConfig{}, routeUpstreamConfig{}, false, false, "", "", swEnabled, ConfigSnapshot{SWOfflineEnabled: true}) + return renderHTTPSServer("example.com", "example.com", "http://127.0.0.1:8080", "", 1, nil, routeCacheConfig{}, routeLimitConfig{}, routeUpstreamConfig{}, false, true, false, "", "", swEnabled, ConfigSnapshot{SWOfflineEnabled: true}) } hit := render(routeSWEnabled([]string{"example.com"}, ConfigSnapshot{SWOfflineEnabled: true, SWOfflineDomains: []string{"example.com"}})) for _, want := range []string{`require("sw.runtime").check()`, "location = /sw.js", "location = /offline.html", "__openflare_sw_challenge"} { @@ -193,7 +193,7 @@ func TestRenderHTTPSServerSWScope(t *testing.T) { t.Fatalf("out-of-scope HTTPS server must not carry %q, got:\n%s", notWant, miss) } } - if miss != renderHTTPSServer("example.com", "example.com", "http://127.0.0.1:8080", "", 1, nil, routeCacheConfig{}, routeLimitConfig{}, routeUpstreamConfig{}, false, false, "", "", false, ConfigSnapshot{}) { + if miss != renderHTTPSServer("example.com", "example.com", "http://127.0.0.1:8080", "", 1, nil, routeCacheConfig{}, routeLimitConfig{}, routeUpstreamConfig{}, false, true, false, "", "", false, ConfigSnapshot{}) { t.Fatalf("out-of-scope HTTPS server must match pre-feature bytes, got:\n%s", miss) } } diff --git a/pkg/render/openresty/types.go b/pkg/render/openresty/types.go index 4def7a9b..c7fe0cc5 100644 --- a/pkg/render/openresty/types.go +++ b/pkg/render/openresty/types.go @@ -165,6 +165,7 @@ type Route struct { Upstreams []string `json:"upstreams,omitempty"` Enabled bool `json:"enabled"` EnableHTTPS bool `json:"enable_https"` + EnableHTTP2 bool `json:"enable_http2"` DomainCertIDs []uint `json:"domain_cert_ids,omitempty"` RedirectHTTP bool `json:"redirect_http"` LimitConnPerServer int `json:"limit_conn_per_server,omitempty"`