feat: Sora curl_cffi sidecar — Chrome TLS 指纹绕过 Cloudflare
- 新增 sora-curl-cffi-sidecar 容器(Python + curl_cffi + chrome131) - docker-compose.tls-proxy.yml 集成 sidecar,sub2api 自动连接 - 会话池复用,避免重复 TLS 握手 - 镜像 zfc931912343/sora-curl-cffi-sidecar:latest (amd64+arm64)
This commit is contained in:
parent
7185630ba1
commit
0bfd6edde6
@ -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
|
# 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
|
# Anthropic: sub2api → node-tls-proxy (Node.js TLS) → api.anthropic.com
|
||||||
#
|
# Sora: sub2api → sora-curl-cffi-sidecar (Chrome TLS) → sora.chatgpt.com
|
||||||
# 网络隔离:
|
|
||||||
# - sub2api 仅连接 internal + sub2api-network(访问 pg/redis,但无外网)
|
|
||||||
# - node-tls-proxy 双栈网络(internal + external),唯一的出站通道
|
|
||||||
# - IPv6 内核级禁用
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# 覆盖 sub2api:加入 internal 网络 + 启用 Node.js TLS 代理
|
# 覆盖 sub2api:加入 internal 网络 + 启用代理
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
sub2api:
|
sub2api:
|
||||||
networks:
|
networks:
|
||||||
- sub2api-internal
|
- sub2api-internal
|
||||||
- sub2api-network # 保留:访问 postgres/redis
|
- sub2api-network # 保留:访问 postgres/redis
|
||||||
environment:
|
environment:
|
||||||
# 启用 Node.js TLS 代理
|
# Node.js TLS 代理(Anthropic)
|
||||||
- GATEWAY_NODE_TLS_PROXY_ENABLED=true
|
- GATEWAY_NODE_TLS_PROXY_ENABLED=true
|
||||||
- GATEWAY_NODE_TLS_PROXY_LISTEN_PORT=3456
|
- GATEWAY_NODE_TLS_PROXY_LISTEN_PORT=3456
|
||||||
- GATEWAY_NODE_TLS_PROXY_LISTEN_HOST=node-tls-proxy
|
- GATEWAY_NODE_TLS_PROXY_LISTEN_HOST=node-tls-proxy
|
||||||
- GATEWAY_NODE_TLS_PROXY_UPSTREAM_HOST=api.anthropic.com
|
- 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:
|
depends_on:
|
||||||
node-tls-proxy:
|
node-tls-proxy:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
sora-curl-cffi-sidecar:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# Node.js TLS Forward Proxy
|
# Node.js TLS Forward Proxy (Anthropic)
|
||||||
# 直接拉取预构建镜像,支持 amd64/arm64
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
node-tls-proxy:
|
node-tls-proxy:
|
||||||
image: zfc931912343/sub2api-tls-proxy:latest
|
image: zfc931912343/sub2api-tls-proxy:latest
|
||||||
@ -49,14 +48,12 @@ services:
|
|||||||
- PROXY_PORT=3456
|
- PROXY_PORT=3456
|
||||||
- PROXY_HOST=0.0.0.0
|
- PROXY_HOST=0.0.0.0
|
||||||
- UPSTREAM_HOST=api.anthropic.com
|
- UPSTREAM_HOST=api.anthropic.com
|
||||||
# 可选:经过外部代理出站(HTTP CONNECT 隧道)
|
|
||||||
- UPSTREAM_PROXY=${TLS_PROXY_UPSTREAM_PROXY:-}
|
- UPSTREAM_PROXY=${TLS_PROXY_UPSTREAM_PROXY:-}
|
||||||
- TZ=${TZ:-Asia/Shanghai}
|
- TZ=${TZ:-Asia/Shanghai}
|
||||||
networks:
|
networks:
|
||||||
- sub2api-internal # sub2api 可以访问
|
- sub2api-internal
|
||||||
- sub2api-external # 可以访问外网
|
- sub2api-external
|
||||||
sysctls:
|
sysctls:
|
||||||
# 内核级禁用 IPv6(防 IPv6 泄露)
|
|
||||||
- net.ipv6.conf.all.disable_ipv6=1
|
- net.ipv6.conf.all.disable_ipv6=1
|
||||||
- net.ipv6.conf.default.disable_ipv6=1
|
- net.ipv6.conf.default.disable_ipv6=1
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@ -71,12 +68,40 @@ services:
|
|||||||
memory: 256M
|
memory: 256M
|
||||||
cpus: "1.0"
|
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
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
networks:
|
networks:
|
||||||
sub2api-internal:
|
sub2api-internal:
|
||||||
internal: true # 关键:无外网访问
|
internal: true
|
||||||
driver: bridge
|
driver: bridge
|
||||||
sub2api-external:
|
sub2api-external:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
27
tools/sora-curl-cffi-sidecar/Dockerfile
Normal file
27
tools/sora-curl-cffi-sidecar/Dockerfile
Normal file
@ -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"]
|
||||||
91
tools/sora-curl-cffi-sidecar/app.py
Normal file
91
tools/sora-curl-cffi-sidecar/app.py
Normal file
@ -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)
|
||||||
3
tools/sora-curl-cffi-sidecar/requirements.txt
Normal file
3
tools/sora-curl-cffi-sidecar/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
flask>=3.0
|
||||||
|
curl_cffi>=0.7
|
||||||
|
gunicorn>=22.0
|
||||||
Loading…
x
Reference in New Issue
Block a user