fix(sora): 增强 Cloudflare 挑战识别并收敛 Sora 请求链路
- 在 failover 场景透传上游响应头并识别 Cloudflare challenge/cf-ray - 统一 Sora 任务请求的 UA 与代理使用,sentinel 与业务请求保持一致 - 修复流式错误事件 JSON 转义问题并补充相关单元测试
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -522,6 +523,7 @@ func (s *AccountTestService) testSoraAccountConnection(c *gin.Context, account *
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if isCloudflareChallengeResponse(resp.StatusCode, body) {
|
||||
s.logSoraCloudflareChallenge(account, proxyURL, soraMeAPIURL, resp.Header, body)
|
||||
return s.sendErrorAndEnd(c, formatCloudflareChallengeMessage("Sora request blocked by Cloudflare challenge (HTTP 403). Please switch to a clean proxy/network and retry.", resp.Header, body))
|
||||
}
|
||||
return s.sendErrorAndEnd(c, fmt.Sprintf("Sora API returned %d: %s", resp.StatusCode, truncateSoraErrorBody(body, 512)))
|
||||
@@ -567,6 +569,7 @@ func (s *AccountTestService) testSoraAccountConnection(c *gin.Context, account *
|
||||
}
|
||||
} else {
|
||||
if isCloudflareChallengeResponse(subResp.StatusCode, subBody) {
|
||||
s.logSoraCloudflareChallenge(account, proxyURL, soraBillingAPIURL, subResp.Header, subBody)
|
||||
s.sendEvent(c, TestEvent{Type: "content", Text: formatCloudflareChallengeMessage("Subscription check blocked by Cloudflare challenge (HTTP 403)", subResp.Header, subBody)})
|
||||
} else {
|
||||
s.sendEvent(c, TestEvent{Type: "content", Text: fmt.Sprintf("Subscription check returned %d", subResp.StatusCode)})
|
||||
@@ -824,6 +827,75 @@ func extractCloudflareRayID(headers http.Header, body []byte) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractSoraEgressIPHint(headers http.Header) string {
|
||||
if headers == nil {
|
||||
return "unknown"
|
||||
}
|
||||
candidates := []string{
|
||||
"x-openai-public-ip",
|
||||
"x-envoy-external-address",
|
||||
"cf-connecting-ip",
|
||||
"x-forwarded-for",
|
||||
}
|
||||
for _, key := range candidates {
|
||||
if value := strings.TrimSpace(headers.Get(key)); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func sanitizeProxyURLForLog(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return "<invalid_proxy_url>"
|
||||
}
|
||||
if u.User != nil {
|
||||
u.User = nil
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func endpointPathForLog(endpoint string) string {
|
||||
parsed, err := url.Parse(strings.TrimSpace(endpoint))
|
||||
if err != nil || parsed.Path == "" {
|
||||
return endpoint
|
||||
}
|
||||
return parsed.Path
|
||||
}
|
||||
|
||||
func (s *AccountTestService) logSoraCloudflareChallenge(account *Account, proxyURL, endpoint string, headers http.Header, body []byte) {
|
||||
accountID := int64(0)
|
||||
platform := ""
|
||||
proxyID := "none"
|
||||
if account != nil {
|
||||
accountID = account.ID
|
||||
platform = account.Platform
|
||||
if account.ProxyID != nil {
|
||||
proxyID = fmt.Sprintf("%d", *account.ProxyID)
|
||||
}
|
||||
}
|
||||
cfRay := extractCloudflareRayID(headers, body)
|
||||
if cfRay == "" {
|
||||
cfRay = "unknown"
|
||||
}
|
||||
log.Printf(
|
||||
"[SoraCFChallenge] account_id=%d platform=%s endpoint=%s path=%s proxy_id=%s proxy_url=%s cf_ray=%s egress_ip_hint=%s",
|
||||
accountID,
|
||||
platform,
|
||||
endpoint,
|
||||
endpointPathForLog(endpoint),
|
||||
proxyID,
|
||||
sanitizeProxyURLForLog(proxyURL),
|
||||
cfRay,
|
||||
extractSoraEgressIPHint(headers),
|
||||
)
|
||||
}
|
||||
|
||||
func truncateSoraErrorBody(body []byte, max int) string {
|
||||
if max <= 0 {
|
||||
max = 512
|
||||
|
||||
Reference in New Issue
Block a user