ForcePilot/backend/package/yuxi/channels/adapters/signal/sse_reconnect.py
Kris 1f78c44b03 refactor: 整理并清理项目中的冗余代码与格式问题
这是一个批量整理提交,包含以下主要改动:
1.  删除多处冗余的空行和未使用的导入
2.  修复文件末尾缺少换行符的问题
3.  调整部分模块的导入顺序与代码排版
4.  修复部分配置默认值与策略逻辑
5.  新增多个功能模块与辅助工具
6.  完善异常处理与日志记录
7.  修复速率限制、消息缓存、权限校验等逻辑bug
8.  废弃部分旧有API与配置项并添加警告提示
2026-05-12 14:51:53 +08:00

85 lines
3.2 KiB
Python

from __future__ import annotations
import asyncio
import logging
import random
from collections.abc import Callable, Awaitable
import aiohttp
logger = logging.getLogger(__name__)
MAX_RECONNECT_DELAY = 60.0
INITIAL_RECONNECT_DELAY = 1.0
JITTER = 0.1
MAX_RECONNECT_COUNT = 20
async def sse_event_stream(
url: str,
params: dict,
on_event: Callable[[str], Awaitable[None]],
) -> None:
reconnect_delay = INITIAL_RECONNECT_DELAY
last_event_id: str | None = None
unauth_backoff = False
retry_count = 0
while retry_count < MAX_RECONNECT_COUNT:
try:
headers: dict[str, str] = {"Accept": "text/event-stream"}
if last_event_id:
headers["Last-Event-ID"] = last_event_id
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params, headers=headers) as response:
if response.status == 401:
unauth_backoff = True
logger.error("SSE connection received 401 Unauthorized, backing off for 30s")
await asyncio.sleep(30.0)
reconnect_delay = INITIAL_RECONNECT_DELAY
retry_count += 1
continue
if response.status != 200:
logger.error(f"SSE connection failed: {response.status}")
await asyncio.sleep(reconnect_delay)
reconnect_delay = min(reconnect_delay * 2, MAX_RECONNECT_DELAY)
retry_count += 1
continue
if unauth_backoff:
logger.info("SSE reconnected successfully after 401 backoff")
unauth_backoff = False
reconnect_delay = INITIAL_RECONNECT_DELAY
retry_count = 0
async for line in response.content:
line_text = line.decode("utf-8").strip()
if line_text.startswith("id:"):
last_event_id = line_text.removeprefix("id:").strip()
continue
if line_text.startswith("data:"):
event_data = line_text.removeprefix("data:").strip()
if event_data:
try:
await on_event(event_data)
except Exception:
logger.exception("Error handling SSE event")
except (TimeoutError, aiohttp.ClientError) as e:
logger.warning(f"SSE connection lost: {e}, reconnecting in {reconnect_delay:.1f}s")
jitter_ms = reconnect_delay * JITTER * random.random()
await asyncio.sleep(reconnect_delay + jitter_ms)
reconnect_delay = min(reconnect_delay * 2, MAX_RECONNECT_DELAY)
retry_count += 1
except asyncio.CancelledError:
logger.info("SSE event stream cancelled")
break
if retry_count >= MAX_RECONNECT_COUNT:
logger.error(f"SSE reconnect limit ({MAX_RECONNECT_COUNT}) reached, giving up")