fix: 双模型审查 Critical 修复
1. Sora session_key 按 accountID 隔离(消除跨账号指纹关联) 2. 有 per-account 代理的 Sora 账号跳过 sidecar(保持代理 IP) 3. 请求体用 base64 编码传输(防止二进制数据损坏) 4. Node.js 代理 Body 用 GetBody 安全复制(修复重试时 Body 枯竭)
This commit is contained in:
parent
4a92f1903f
commit
3c8ffd3efc
@ -286,7 +286,12 @@ func (s *httpUpstreamService) doViaNodeTLSProxy(req *http.Request, proxyURL stri
|
|||||||
|
|
||||||
// 克隆请求,避免修改原始 req(重试时需要原始 URL)
|
// 克隆请求,避免修改原始 req(重试时需要原始 URL)
|
||||||
proxyReq := req.Clone(req.Context())
|
proxyReq := req.Clone(req.Context())
|
||||||
proxyReq.Body = req.Body // Clone 不复制 Body
|
// 安全复制 Body:优先用 GetBody 工厂方法
|
||||||
|
if req.GetBody != nil {
|
||||||
|
proxyReq.Body, _ = req.GetBody()
|
||||||
|
} else {
|
||||||
|
proxyReq.Body = req.Body
|
||||||
|
}
|
||||||
|
|
||||||
// 保存原始目标主机,通过自定义头传给 Node.js 代理
|
// 保存原始目标主机,通过自定义头传给 Node.js 代理
|
||||||
originalHost := req.URL.Host
|
originalHost := req.URL.Host
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -1026,11 +1027,19 @@ func (c *SoraSDKClient) debugLogf(format string, args ...any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// doSoraHTTP 执行 Sora HTTP 请求,优先走 curl_cffi sidecar(Chrome TLS 指纹绕过 Cloudflare)
|
// doSoraHTTP 执行 Sora HTTP 请求,优先走 curl_cffi sidecar(Chrome TLS 指纹绕过 Cloudflare)
|
||||||
// 如果 sidecar 未启用或不可用,回退到 httpUpstream.Do() / http.DefaultClient
|
// 如果 sidecar 未启用、账号有独立代理、或 sidecar 不可用,回退到 httpUpstream.Do()
|
||||||
func (c *SoraSDKClient) doSoraHTTP(req *http.Request, proxyURL string, accountID int64, accountConcurrency int) (*http.Response, error) {
|
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 是否启用
|
// 检查 sidecar 是否启用
|
||||||
if c.cfg != nil && c.cfg.Sora.Client.CurlCFFISidecar.Enabled && c.cfg.Sora.Client.CurlCFFISidecar.BaseURL != "" {
|
if c.cfg != nil && c.cfg.Sora.Client.CurlCFFISidecar.Enabled && c.cfg.Sora.Client.CurlCFFISidecar.BaseURL != "" {
|
||||||
resp, err := c.doViaSidecar(req)
|
resp, err := c.doViaSidecar(req, accountID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
@ -1046,17 +1055,17 @@ func (c *SoraSDKClient) doSoraHTTP(req *http.Request, proxyURL string, accountID
|
|||||||
}
|
}
|
||||||
|
|
||||||
// doViaSidecar 通过 curl_cffi sidecar 发送请求(Chrome TLS 指纹)
|
// doViaSidecar 通过 curl_cffi sidecar 发送请求(Chrome TLS 指纹)
|
||||||
func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request) (*http.Response, error) {
|
func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request, accountID int64) (*http.Response, error) {
|
||||||
sidecarURL := strings.TrimRight(c.cfg.Sora.Client.CurlCFFISidecar.BaseURL, "/") + "/proxy"
|
sidecarURL := strings.TrimRight(c.cfg.Sora.Client.CurlCFFISidecar.BaseURL, "/") + "/proxy"
|
||||||
|
|
||||||
// 读取原始请求体
|
// 读取原始请求体,用 base64 编码(安全传输二进制数据)
|
||||||
var bodyStr string
|
var bodyB64 string
|
||||||
if originalReq.Body != nil {
|
if originalReq.Body != nil {
|
||||||
bodyBytes, err := io.ReadAll(originalReq.Body)
|
bodyBytes, err := io.ReadAll(originalReq.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read request body: %w", err)
|
return nil, fmt.Errorf("read request body: %w", err)
|
||||||
}
|
}
|
||||||
bodyStr = string(bodyBytes)
|
bodyB64 = base64.StdEncoding.EncodeToString(bodyBytes)
|
||||||
// 恢复 body 以备回退
|
// 恢复 body 以备回退
|
||||||
originalReq.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
originalReq.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||||
}
|
}
|
||||||
@ -1073,8 +1082,9 @@ func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request) (*http.Response,
|
|||||||
"url": originalReq.URL.String(),
|
"url": originalReq.URL.String(),
|
||||||
"method": originalReq.Method,
|
"method": originalReq.Method,
|
||||||
"headers": headers,
|
"headers": headers,
|
||||||
"body": bodyStr,
|
"body": bodyB64,
|
||||||
"session_key": fmt.Sprintf("account_%d", 0), // 简单的 session key
|
"is_base64": true,
|
||||||
|
"session_key": fmt.Sprintf("account_%d", accountID), // 每账号独立会话
|
||||||
}
|
}
|
||||||
|
|
||||||
payloadBytes, err := json.Marshal(payload)
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ sub2api 通过 HTTP 调用此服务转发 Sora 请求
|
|||||||
"""
|
"""
|
||||||
from flask import Flask, request, Response
|
from flask import Flask, request, Response
|
||||||
from curl_cffi import requests as cffi_requests
|
from curl_cffi import requests as cffi_requests
|
||||||
import json, os, time, threading
|
import json, os, time, threading, base64
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@ -54,18 +54,30 @@ def proxy():
|
|||||||
method = data.get("method", "GET").upper()
|
method = data.get("method", "GET").upper()
|
||||||
headers = data.get("headers", {})
|
headers = data.get("headers", {})
|
||||||
body = data.get("body")
|
body = data.get("body")
|
||||||
|
is_base64 = data.get("is_base64", False)
|
||||||
session_key = data.get("session_key", "default")
|
session_key = data.get("session_key", "default")
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
return json.dumps({"error": "url required"}), 400
|
return json.dumps({"error": "url required"}), 400
|
||||||
|
|
||||||
|
# 解码 base64 编码的请求体(安全传输二进制数据)
|
||||||
|
req_data = None
|
||||||
|
if body:
|
||||||
|
if is_base64:
|
||||||
|
try:
|
||||||
|
req_data = base64.b64decode(body)
|
||||||
|
except Exception:
|
||||||
|
req_data = body.encode("utf-8") if isinstance(body, str) else body
|
||||||
|
else:
|
||||||
|
req_data = body.encode("utf-8") if isinstance(body, str) else body
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sess = get_session(session_key)
|
sess = get_session(session_key)
|
||||||
resp = sess.request(
|
resp = sess.request(
|
||||||
method=method,
|
method=method,
|
||||||
url=url,
|
url=url,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
data=body.encode("utf-8") if isinstance(body, str) else body,
|
data=req_data,
|
||||||
timeout=TIMEOUT,
|
timeout=TIMEOUT,
|
||||||
allow_redirects=True,
|
allow_redirects=True,
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user