Реал-тайм через SSE (push-to-invalidate): живые приглашения/партии, авто-обновление без перезагрузки
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
"""SSE: поток событий «данные изменились» для текущего пользователя.
|
||||
|
||||
Клиент (EventSource) держит одно соединение и по событию точечно перезапрашивает данные
|
||||
(push-to-invalidate). Мутации остаются обычным REST.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from app.auth.deps import get_current_user
|
||||
from app.core.events import hub
|
||||
from app.models import User
|
||||
|
||||
router = APIRouter(tags=["events"])
|
||||
|
||||
_HEARTBEAT_SECONDS = 25
|
||||
|
||||
|
||||
@router.get("/events")
|
||||
async def events(request: Request, user: User = Depends(get_current_user)) -> StreamingResponse:
|
||||
queue = hub.subscribe(user.id) # type: ignore[arg-type]
|
||||
|
||||
async def stream():
|
||||
try:
|
||||
yield ": connected\n\n"
|
||||
while True:
|
||||
if await request.is_disconnected():
|
||||
break
|
||||
try:
|
||||
event = await asyncio.wait_for(queue.get(), timeout=_HEARTBEAT_SECONDS)
|
||||
yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
|
||||
except asyncio.TimeoutError:
|
||||
yield ": ping\n\n" # heartbeat против idle-таймаутов прокси/туннеля
|
||||
finally:
|
||||
hub.unsubscribe(user.id, queue) # type: ignore[arg-type]
|
||||
|
||||
return StreamingResponse(
|
||||
stream(),
|
||||
media_type="text/event-stream",
|
||||
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
||||
)
|
||||
Reference in New Issue
Block a user