fix(http): 用requests+trust_env=False修复SSL EOF问题

根因:Clash代理(127.0.0.1:7890)在处理TLS 1.3+后量子密钥交换时
不兼容,导致SSL EOF错误。requests默认trust_env=True会读取系统
代理配置,通过代理转发HTTPS请求时触发问题。

修复:使用requests.Session(trust_env=False)绕过系统代理,
直连目标服务器。无需降级urllib3版本。

影响文件:
- rotation/simple_rotation.py
- datasource/flask_api_source.py
This commit is contained in:
2026-06-03 00:35:49 +08:00
parent a2b4289080
commit d1139a9ee9
2 changed files with 42 additions and 64 deletions

View File

@@ -14,8 +14,7 @@ import sys
import math
import json
import time
import urllib3
import urllib.parse
import requests
import numpy as np
import pandas as pd
from pathlib import Path
@@ -28,31 +27,17 @@ sys.path.insert(0, str(PROJECT_ROOT))
from rotation.config_loader import load_rotation_config, RotationStrategyConfig
# ============================================================
# HTTP client (urllib3 替代 requests修复 SSL EOF 问题)
# HTTP client (requests + trust_env=False绕过系统代理避免 SSL EOF)
# ============================================================
_http_pool = urllib3.PoolManager(
maxsize=16, # 支持并行连接
timeout=urllib3.Timeout(connect=10, read=120)
)
# Clash 等代理在处理 TLS 1.3 + 后量子密钥交换时会触发 SSL EOF 错误
# trust_env=False 让 requests 忽略环境变量中的代理配置,直连目标服务器
_session = requests.Session()
_session.trust_env = False
class _HttpResponse:
"""urllib3 响应包装,提供 requests 兼容接口"""
def __init__(self, resp):
self.status_code = resp.status
self._data = resp.data
self._json = None
def json(self):
if self._json is None:
self._json = json.loads(self._data)
return self._json
def _http_get(url: str, params: dict = None, timeout: int = 120) -> _HttpResponse:
"""使用 urllib3 发起 GET 请求(替代 requests.get修复 OpenSSL 3.5 + Caddy 的 SSL EOF 问题)"""
if params:
url = url + '?' + urllib.parse.urlencode(params)
resp = _http_pool.request('GET', url, timeout=urllib3.Timeout(connect=10, read=timeout))
return _HttpResponse(resp)
def _http_get(url: str, params: dict = None, timeout: int = 120) -> requests.Response:
"""使用 requests 发起 GET 请求trust_env=False 绕过系统代理)"""
return _session.get(url, params=params, timeout=timeout)
# ============================================================
@@ -177,7 +162,7 @@ class DataCache:
self._save_premium_cache(code, df.attrs['premium_series'])
print(f" + {code}: {len(df)} rows ({adj})")
return df
except (urllib3.exceptions.TimeoutError, urllib3.exceptions.SSLError, urllib3.exceptions.MaxRetryError, urllib3.exceptions.ProtocolError) as e:
except (requests.exceptions.Timeout, requests.exceptions.SSLError, requests.exceptions.ConnectionError) as e:
# 网络相关错误超时、SSL、连接断开都进行重试
if attempt < 2:
time.sleep(1 + attempt) # 递增延迟: 1s, 2s
@@ -255,7 +240,7 @@ class DataCache:
self._save_premium_cache(code, self.premium_data[code])
print(f" + premium {code}: +{len(new_data)} days (total {len(self.premium_data[code])})")
return
except (urllib3.exceptions.TimeoutError, urllib3.exceptions.SSLError, urllib3.exceptions.MaxRetryError, urllib3.exceptions.ProtocolError):
except (requests.exceptions.Timeout, requests.exceptions.SSLError, requests.exceptions.ConnectionError):
if attempt < 2:
time.sleep(1 + attempt)
continue
@@ -285,7 +270,7 @@ class DataCache:
result = pd.DatetimeIndex(dates)
print(f" + {market}: {len(result)} trading days ({start_date} ~ {end_date})")
return result
except (urllib3.exceptions.TimeoutError, urllib3.exceptions.SSLError, urllib3.exceptions.MaxRetryError, urllib3.exceptions.ProtocolError) as e:
except (requests.exceptions.Timeout, requests.exceptions.SSLError, requests.exceptions.ConnectionError) as e:
if attempt < 2:
time.sleep(1 + attempt)
continue