diff --git a/backend/internal/service/sora_sdk_client.go b/backend/internal/service/sora_sdk_client.go index 0e5a2a1c..f9221c5b 100644 --- a/backend/internal/service/sora_sdk_client.go +++ b/backend/internal/service/sora_sdk_client.go @@ -3,7 +3,6 @@ package service import ( "bytes" "context" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -479,7 +478,7 @@ func (c *SoraSDKClient) GetWatermarkFreeURLCustom(ctx context.Context, account * } var resp *http.Response if c.httpUpstream != nil { - resp, err = c.doSoraHTTP(req, proxyURL, accountID, accountConcurrency) + resp, err = c.httpUpstream.Do(req, proxyURL, accountID, accountConcurrency) } else { resp, err = http.DefaultClient.Do(req) } @@ -724,7 +723,7 @@ func (c *SoraSDKClient) doSoraBackendJSON( var resp *http.Response if c.httpUpstream != nil { - resp, err = c.doSoraHTTP(req, proxyURL, accountID, accountConcurrency) + resp, err = c.httpUpstream.Do(req, proxyURL, accountID, accountConcurrency) } else { resp, err = http.DefaultClient.Do(req) } @@ -902,7 +901,7 @@ func (c *SoraSDKClient) exchangeSessionToken(ctx context.Context, account *Accou var resp *http.Response if c.httpUpstream != nil { - resp, err = c.doSoraHTTP(req, proxyURL, accountID, accountConcurrency) + resp, err = c.httpUpstream.Do(req, proxyURL, accountID, accountConcurrency) } else { resp, err = http.DefaultClient.Do(req) } @@ -1025,79 +1024,3 @@ func (c *SoraSDKClient) debugLogf(format string, args ...any) { log.Printf("[SoraSDK] "+format, args...) } } - -// doSoraHTTP 执行 Sora HTTP 请求,优先走 curl_cffi sidecar(Chrome TLS 指纹绕过 Cloudflare) -// 如果 sidecar 未启用、账号有独立代理、或 sidecar 不可用,回退到 httpUpstream.Do() -func (c *SoraSDKClient) doSoraHTTP(req *http.Request, proxyURL string, accountID int64, accountConcurrency int) (*http.Response, error) { - // 如果账号配置了独立代理,跳过 sidecar(sidecar 不支持 per-account 代理) - if proxyURL != "" { - if c.httpUpstream != nil { - return c.httpUpstream.Do(req, proxyURL, accountID, accountConcurrency) - } - return http.DefaultClient.Do(req) - } - - // 检查 sidecar 是否启用 - if c.cfg != nil && c.cfg.Sora.Client.CurlCFFISidecar.Enabled && c.cfg.Sora.Client.CurlCFFISidecar.BaseURL != "" { - resp, err := c.doViaSidecar(req, accountID) - if err == nil { - return resp, nil - } - // sidecar 失败,回退到直连 - logger.LegacyPrintf("service.sora", "Warning: sidecar failed, falling back to direct: %v", err) - } - - // 回退路径 - if c.httpUpstream != nil { - return c.httpUpstream.Do(req, proxyURL, accountID, accountConcurrency) - } - return http.DefaultClient.Do(req) -} - -// doViaSidecar 通过 curl_cffi sidecar 发送请求(Chrome TLS 指纹) -func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request, accountID int64) (*http.Response, error) { - sidecarURL := strings.TrimRight(c.cfg.Sora.Client.CurlCFFISidecar.BaseURL, "/") + "/proxy" - - // 读取原始请求体,用 base64 编码(安全传输二进制数据) - var bodyB64 string - if originalReq.Body != nil { - bodyBytes, err := io.ReadAll(originalReq.Body) - if err != nil { - return nil, fmt.Errorf("read request body: %w", err) - } - bodyB64 = base64.StdEncoding.EncodeToString(bodyBytes) - // 恢复 body 以备回退 - originalReq.Body = io.NopCloser(bytes.NewReader(bodyBytes)) - } - - // 构建 sidecar 请求 - headers := make(map[string]string) - for k, vs := range originalReq.Header { - if len(vs) > 0 { - headers[k] = vs[0] - } - } - - payload := map[string]any{ - "url": originalReq.URL.String(), - "method": originalReq.Method, - "headers": headers, - "body": bodyB64, - "is_base64": true, - "session_key": fmt.Sprintf("account_%d", accountID), // 每账号独立会话 - } - - payloadBytes, err := json.Marshal(payload) - if err != nil { - return nil, fmt.Errorf("marshal sidecar payload: %w", err) - } - - ctx := originalReq.Context() - sidecarReq, err := http.NewRequestWithContext(ctx, http.MethodPost, sidecarURL, bytes.NewReader(payloadBytes)) - if err != nil { - return nil, fmt.Errorf("create sidecar request: %w", err) - } - sidecarReq.Header.Set("Content-Type", "application/json") - - return http.DefaultClient.Do(sidecarReq) -} diff --git a/deploy/docker-compose.tls-proxy.yml b/deploy/docker-compose.tls-proxy.yml index 9d99f6e2..5aca3df7 100644 --- a/deploy/docker-compose.tls-proxy.yml +++ b/deploy/docker-compose.tls-proxy.yml @@ -1,17 +1,16 @@ # ============================================================================= -# Node.js TLS Proxy + Sora Sidecar Overlay +# Node.js TLS Proxy Overlay # ============================================================================= # 用法: # docker compose -f docker-compose.yml -f docker-compose.tls-proxy.yml up -d # # 架构: # Anthropic: sub2api → node-tls-proxy (Node.js TLS) → api.anthropic.com -# Sora: sub2api → sora-curl-cffi-sidecar (Chrome TLS) → sora.chatgpt.com # ============================================================================= services: # =========================================================================== - # 覆盖 sub2api:加入 internal 网络 + 启用代理 + # 覆盖 sub2api:加入 internal 网络 + 启用 Node.js TLS 代理 # =========================================================================== sub2api: networks: @@ -23,15 +22,9 @@ services: - GATEWAY_NODE_TLS_PROXY_LISTEN_PORT=3456 - GATEWAY_NODE_TLS_PROXY_LISTEN_HOST=node-tls-proxy - GATEWAY_NODE_TLS_PROXY_UPSTREAM_HOST=api.anthropic.com - # Sora curl_cffi sidecar(Chrome 指纹绕过 Cloudflare) - - SORA_CLIENT_CURL_CFFI_SIDECAR_ENABLED=true - - SORA_CLIENT_CURL_CFFI_SIDECAR_BASE_URL=http://sora-curl-cffi-sidecar:8080 - - SORA_CLIENT_CURL_CFFI_SIDECAR_IMPERSONATE=chrome131 depends_on: node-tls-proxy: condition: service_healthy - sora-curl-cffi-sidecar: - condition: service_healthy # =========================================================================== # Node.js TLS Forward Proxy (Anthropic) @@ -68,34 +61,6 @@ services: memory: 256M cpus: "1.0" - # =========================================================================== - # Sora curl_cffi Sidecar (Chrome TLS fingerprint for Cloudflare bypass) - # =========================================================================== - sora-curl-cffi-sidecar: - image: zfc931912343/sora-curl-cffi-sidecar:latest - container_name: sub2api-sora-sidecar - restart: unless-stopped - environment: - - PORT=8080 - - IMPERSONATE=chrome131 - - TIMEOUT_SECONDS=60 - - SESSION_TTL_SECONDS=3600 - - TZ=${TZ:-Asia/Shanghai} - networks: - - sub2api-internal - - sub2api-external - healthcheck: - test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health')"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 15s - deploy: - resources: - limits: - memory: 512M - cpus: "1.0" - # ============================================================================= # Networks # =============================================================================