From 0bfd6edde601c072f28ec4e198da76ca6c188dcf Mon Sep 17 00:00:00 2001 From: win Date: Sun, 22 Mar 2026 03:31:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Sora=20curl=5Fcffi=20sidecar=20?= =?UTF-8?q?=E2=80=94=20Chrome=20TLS=20=E6=8C=87=E7=BA=B9=E7=BB=95=E8=BF=87?= =?UTF-8?q?=20Cloudflare?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 sora-curl-cffi-sidecar 容器(Python + curl_cffi + chrome131) - docker-compose.tls-proxy.yml 集成 sidecar,sub2api 自动连接 - 会话池复用,避免重复 TLS 握手 - 镜像 zfc931912343/sora-curl-cffi-sidecar:latest (amd64+arm64) --- deploy/docker-compose.tls-proxy.yml | 61 +++++++++---- tools/sora-curl-cffi-sidecar/Dockerfile | 27 ++++++ tools/sora-curl-cffi-sidecar/app.py | 91 +++++++++++++++++++ tools/sora-curl-cffi-sidecar/requirements.txt | 3 + 4 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 tools/sora-curl-cffi-sidecar/Dockerfile create mode 100644 tools/sora-curl-cffi-sidecar/app.py create mode 100644 tools/sora-curl-cffi-sidecar/requirements.txt diff --git a/deploy/docker-compose.tls-proxy.yml b/deploy/docker-compose.tls-proxy.yml index e48e4072..9d99f6e2 100644 --- a/deploy/docker-compose.tls-proxy.yml +++ b/deploy/docker-compose.tls-proxy.yml @@ -1,41 +1,40 @@ # ============================================================================= -# Node.js TLS Proxy Overlay +# Node.js TLS Proxy + Sora Sidecar Overlay # ============================================================================= -# 在现有 docker-compose.yml 基础上增加 Node.js TLS 代理。 -# # 用法: # docker compose -f docker-compose.yml -f docker-compose.tls-proxy.yml up -d # # 架构: -# sub2api (Go) → HTTP 明文 → node-tls-proxy → HTTPS (原生 TLS) → api.anthropic.com -# -# 网络隔离: -# - sub2api 仅连接 internal + sub2api-network(访问 pg/redis,但无外网) -# - node-tls-proxy 双栈网络(internal + external),唯一的出站通道 -# - IPv6 内核级禁用 +# 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 网络 + 启用 Node.js TLS 代理 + # 覆盖 sub2api:加入 internal 网络 + 启用代理 # =========================================================================== sub2api: networks: - sub2api-internal - sub2api-network # 保留:访问 postgres/redis environment: - # 启用 Node.js TLS 代理 + # Node.js TLS 代理(Anthropic) - GATEWAY_NODE_TLS_PROXY_ENABLED=true - 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 - # 直接拉取预构建镜像,支持 amd64/arm64 + # Node.js TLS Forward Proxy (Anthropic) # =========================================================================== node-tls-proxy: image: zfc931912343/sub2api-tls-proxy:latest @@ -49,14 +48,12 @@ services: - PROXY_PORT=3456 - PROXY_HOST=0.0.0.0 - UPSTREAM_HOST=api.anthropic.com - # 可选:经过外部代理出站(HTTP CONNECT 隧道) - UPSTREAM_PROXY=${TLS_PROXY_UPSTREAM_PROXY:-} - TZ=${TZ:-Asia/Shanghai} networks: - - sub2api-internal # sub2api 可以访问 - - sub2api-external # 可以访问外网 + - sub2api-internal + - sub2api-external sysctls: - # 内核级禁用 IPv6(防 IPv6 泄露) - net.ipv6.conf.all.disable_ipv6=1 - net.ipv6.conf.default.disable_ipv6=1 healthcheck: @@ -71,12 +68,40 @@ 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 # ============================================================================= networks: sub2api-internal: - internal: true # 关键:无外网访问 + internal: true driver: bridge sub2api-external: driver: bridge diff --git a/tools/sora-curl-cffi-sidecar/Dockerfile b/tools/sora-curl-cffi-sidecar/Dockerfile new file mode 100644 index 00000000..c6843160 --- /dev/null +++ b/tools/sora-curl-cffi-sidecar/Dockerfile @@ -0,0 +1,27 @@ +FROM python:3.12-slim + +LABEL description="Sora curl_cffi sidecar - Chrome TLS fingerprint for Cloudflare bypass" + +WORKDIR /app + +# 安装依赖(curl_cffi 需要编译环境) +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc libffi-dev && \ + rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY app.py . + +ENV PORT=8080 +ENV IMPERSONATE=chrome131 +ENV TIMEOUT_SECONDS=60 +ENV SESSION_TTL_SECONDS=3600 + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health')" || exit 1 + +CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8080", "--timeout", "120", "app:app"] diff --git a/tools/sora-curl-cffi-sidecar/app.py b/tools/sora-curl-cffi-sidecar/app.py new file mode 100644 index 00000000..c79f780a --- /dev/null +++ b/tools/sora-curl-cffi-sidecar/app.py @@ -0,0 +1,91 @@ +""" +Sora curl_cffi sidecar — 用 Chrome 131 TLS 指纹绕过 Cloudflare +sub2api 通过 HTTP 调用此服务转发 Sora 请求 +""" +from flask import Flask, request, Response +from curl_cffi import requests as cffi_requests +import json, os, time, threading + +app = Flask(__name__) + +IMPERSONATE = os.environ.get("IMPERSONATE", "chrome131") +TIMEOUT = int(os.environ.get("TIMEOUT_SECONDS", "60")) + +# 会话池:按 session_key 复用,避免每次请求都做 TLS 握手 +sessions = {} +sessions_lock = threading.Lock() +SESSION_TTL = int(os.environ.get("SESSION_TTL_SECONDS", "3600")) + + +def get_session(session_key="default"): + with sessions_lock: + entry = sessions.get(session_key) + if entry and (time.time() - entry["created"]) < SESSION_TTL: + return entry["session"] + s = cffi_requests.Session(impersonate=IMPERSONATE) + sessions[session_key] = {"session": s, "created": time.time()} + return s + + +@app.route("/health", methods=["GET"]) +def health(): + return json.dumps({"status": "ok", "impersonate": IMPERSONATE}), 200 + + +@app.route("/proxy", methods=["POST"]) +def proxy(): + """ + 接收 sub2api 的代理请求,用 curl_cffi + Chrome 指纹转发到目标 URL + 请求体 JSON: + { + "url": "https://sora.chatgpt.com/backend/me", + "method": "GET", + "headers": {"Authorization": "Bearer xxx", ...}, + "body": "...", + "session_key": "account_123" // 可选 + } + """ + try: + data = request.get_json(force=True) + except Exception: + return json.dumps({"error": "invalid json"}), 400 + + url = data.get("url", "") + method = data.get("method", "GET").upper() + headers = data.get("headers", {}) + body = data.get("body") + session_key = data.get("session_key", "default") + + if not url: + return json.dumps({"error": "url required"}), 400 + + try: + sess = get_session(session_key) + resp = sess.request( + method=method, + url=url, + headers=headers, + data=body.encode("utf-8") if isinstance(body, str) else body, + timeout=TIMEOUT, + allow_redirects=True, + ) + + # 透传响应 + excluded_headers = {"transfer-encoding", "content-encoding", "connection"} + resp_headers = { + k: v for k, v in resp.headers.items() + if k.lower() not in excluded_headers + } + + return Response( + response=resp.content, + status=resp.status_code, + headers=resp_headers, + ) + except Exception as e: + return json.dumps({"error": str(e)}), 502 + + +if __name__ == "__main__": + port = int(os.environ.get("PORT", "8080")) + app.run(host="0.0.0.0", port=port) diff --git a/tools/sora-curl-cffi-sidecar/requirements.txt b/tools/sora-curl-cffi-sidecar/requirements.txt new file mode 100644 index 00000000..effe3fd4 --- /dev/null +++ b/tools/sora-curl-cffi-sidecar/requirements.txt @@ -0,0 +1,3 @@ +flask>=3.0 +curl_cffi>=0.7 +gunicorn>=22.0