fix(worker): 香港站点直连 Gemini 官方平台

官方 Gemini 平台配置的共享 HTTP Proxy 会拒绝香港出口的 CONNECT 请求,导致香港 Worker 的真实 VEO 与图片任务进入 upstream_submission_unknown。

新增仅按平台 UUID 生效的代理直连白名单,并只在香港 Worker 为官方 Gemini 平台启用;宁波与其他平台继续使用原代理策略。

验证:API 全量 go test、代理路由单测、bash -n、ShellCheck、cluster release helper、manual release 与差异检查均通过。
This commit is contained in:
2026-08-04 21:40:32 +08:00
parent 60d16b11ba
commit 4b639df30a
5 changed files with 61 additions and 0 deletions
+2
View File
@@ -55,6 +55,7 @@ type Config struct {
CORSAllowedOrigin string CORSAllowedOrigin string
GlobalHTTPProxy string GlobalHTTPProxy string
GlobalHTTPProxySource string GlobalHTTPProxySource string
PlatformProxyBypassIDs string
LogLevel slog.Level LogLevel slog.Level
BillingEngineMode string BillingEngineMode string
ProcessRole string ProcessRole string
@@ -155,6 +156,7 @@ func Load() Config {
CORSAllowedOrigin: env("CORS_ALLOWED_ORIGIN", "http://localhost:5178,http://127.0.0.1:5178"), CORSAllowedOrigin: env("CORS_ALLOWED_ORIGIN", "http://localhost:5178,http://127.0.0.1:5178"),
GlobalHTTPProxy: globalProxy.HTTPProxy, GlobalHTTPProxy: globalProxy.HTTPProxy,
GlobalHTTPProxySource: globalProxy.Source, GlobalHTTPProxySource: globalProxy.Source,
PlatformProxyBypassIDs: env("AI_GATEWAY_PLATFORM_PROXY_BYPASS_IDS", ""),
LogLevel: logLevel(env("LOG_LEVEL", "info")), LogLevel: logLevel(env("LOG_LEVEL", "info")),
BillingEngineMode: strings.ToLower(env("BILLING_ENGINE_MODE", "observe")), BillingEngineMode: strings.ToLower(env("BILLING_ENGINE_MODE", "observe")),
ProcessRole: strings.ToLower(strings.TrimSpace(env("AI_GATEWAY_PROCESS_ROLE", "all"))), ProcessRole: strings.ToLower(strings.TrimSpace(env("AI_GATEWAY_PROCESS_ROLE", "all"))),
+20
View File
@@ -38,6 +38,13 @@ func (s *Service) httpClientForCandidate(candidate store.RuntimeModelCandidate,
if simulated { if simulated {
return s.httpClients.none, nil return s.httpClients.none, nil
} }
// Some Worker sites have direct provider egress but are not authorized to
// use a platform's shared proxy. Keep this override explicitly scoped to
// platform UUIDs so one site's routing exception cannot bypass proxies for
// unrelated providers or platforms.
if platformIDListed(s.cfg.PlatformProxyBypassIDs, candidate.PlatformID) {
return s.httpClients.none, nil
}
config, err := netproxy.Normalize(netproxy.FromPlatformConfig(candidate.PlatformConfig)) config, err := netproxy.Normalize(netproxy.FromPlatformConfig(candidate.PlatformConfig))
if err != nil { if err != nil {
return nil, &clients.ClientError{Code: "invalid_proxy", Message: err.Error(), Retryable: false} return nil, &clients.ClientError{Code: "invalid_proxy", Message: err.Error(), Retryable: false}
@@ -55,6 +62,19 @@ func (s *Service) httpClientForCandidate(candidate store.RuntimeModelCandidate,
} }
} }
func platformIDListed(raw string, platformID string) bool {
platformID = strings.TrimSpace(platformID)
if platformID == "" {
return false
}
for value := range strings.SplitSeq(raw, ",") {
if strings.TrimSpace(value) == platformID {
return true
}
}
return false
}
func (c *httpClientCache) customClient(rawProxy string) (*http.Client, error) { func (c *httpClientCache) customClient(rawProxy string) (*http.Client, error) {
normalized, proxyURL, err := netproxy.ParseHTTPProxy(rawProxy) normalized, proxyURL, err := netproxy.ParseHTTPProxy(rawProxy)
if err != nil { if err != nil {
+35
View File
@@ -101,6 +101,41 @@ func TestPlatformProxyModeCustomUsesConfiguredHTTPProxy(t *testing.T) {
} }
} }
func TestPlatformProxyBypassIDUsesDirectConnection(t *testing.T) {
var targetHits int
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
targetHits++
_, _ = w.Write([]byte("target"))
}))
defer target.Close()
var proxyHits int
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
proxyHits++
w.WriteHeader(http.StatusProxyAuthRequired)
}))
defer proxy.Close()
client, err := testProxyService(config.Config{
PlatformProxyBypassIDs: "other-platform, official-gemini-platform ",
}).httpClientForCandidate(store.RuntimeModelCandidate{
PlatformID: "official-gemini-platform",
PlatformConfig: map[string]any{"networkProxy": map[string]any{"mode": "custom", "httpProxy": proxy.URL}},
}, false)
if err != nil {
t.Fatalf("build bypassed http client: %v", err)
}
resp, err := client.Get(target.URL)
if err != nil {
t.Fatalf("get target directly: %v", err)
}
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
if resp.StatusCode != http.StatusOK || targetHits != 1 || proxyHits != 0 {
t.Fatalf("unexpected status=%d targetHits=%d proxyHits=%d", resp.StatusCode, targetHits, proxyHits)
}
}
func TestPlatformProxyModeGlobalUsesConfiguredGlobalHTTPProxy(t *testing.T) { func TestPlatformProxyModeGlobalUsesConfiguredGlobalHTTPProxy(t *testing.T) {
var targetHits int var targetHits int
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -715,6 +715,8 @@ spec:
fieldPath: metadata.uid fieldPath: metadata.uid
- name: EASYAI_SITE - name: EASYAI_SITE
value: hongkong value: hongkong
- name: AI_GATEWAY_PLATFORM_PROXY_BYPASS_IDS
value: 99372d7c-f2a4-472a-987f-30cb76c7962c
ports: ports:
- name: health - name: health
containerPort: 8088 containerPort: 8088
@@ -116,5 +116,7 @@ fi
hongkong_strategy=$(deployment_strategy easyai-worker-hongkong) hongkong_strategy=$(deployment_strategy easyai-worker-hongkong)
grep -Fq ' replicas: 1' <<<"$hongkong_strategy" grep -Fq ' replicas: 1' <<<"$hongkong_strategy"
grep -Fq 'easyai.io/worker-only' <<<"$hongkong_strategy" grep -Fq 'easyai.io/worker-only' <<<"$hongkong_strategy"
grep -Fq 'AI_GATEWAY_PLATFORM_PROXY_BYPASS_IDS' <<<"$hongkong_strategy"
grep -Fq '99372d7c-f2a4-472a-987f-30cb76c7962c' <<<"$hongkong_strategy"
echo 'cluster_release_helper_tests=PASS' echo 'cluster_release_helper_tests=PASS'