Как обнаружить отключение клиента в StreamingHttpResponse (события на стороне сервера) с помощью Django/Django-Ninja?
Мы хотим добавить события на стороне сервера в наше приложение, но в настоящее время цикл while True выполняется бесконечно, и отключение клиента не приводит к остановке задачи.
Воспроизводимый пример:
@api.get("/events")
async def get_events(request: ASGIRequest) -> StreamingHttpResponse:
async def generate_message() -> AsyncGenerator[str, None]:
while True:
await asyncio.sleep(1)
yield 'data: {"test": "testvalue"}\n\n'
print("Working...")
return StreamingHttpResponse(
generate_message(),
content_type="text/event-stream",
headers={
"X-Accel-Buffering": "no",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
)
Приведенный выше пример будет печатать Working... до тех пор, пока сервер не будет закрыт (даже если клиент закрыл соединение и не получает данные).
Есть ли способ обнаружить отключение клиента, чтобы остановить цикл while True?
Уже пытался вернуть простой HttpResponse, помещая код в try...except, создавая методы def close, async def close на StreamingHttpResponse.
- django-ninja==0.22.2
- django==4.2.13
- uvicorn[standard]==0.29.0
Таким образом, обработка отключения клиента должна происходить автоматически, к сожалению, Django < 5 имеет ошибку в ASGIHandler
(https://code.djangoproject.com/ticket/34752)
В версии Django 5.0.6
эта проблема исправлена.
изменяет ваш код для обработки отключений клиента .
i. Создайте промежуточное ПО ASGI для обнаружения разъединений.
в файле middleware.py
from typing import Callable
class SSEDisconnectMiddleware:
def __init__(self, app: Callable):
self.app = app
async def __call__(self, scope, receive, send):
if scope['type'] == 'http':
async def receive_wrapper():
message = await receive()
if message['type'] == 'http.disconnect':
scope['client_disconnected'] = True
return message
scope['client_disconnected'] = False
await self.app(scope, receive_wrapper, send)
else:
await self.app(scope, receive, send)
Добавьте это промежуточное ПО в ваше ASGI-приложение в asgi.py
import os
from django.core.asgi import get_asgi_application
from .middleware import SSEDisconnectMiddleware
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings')
application = SSEDisconnectMiddleware(get_asgi_application())
после этого,
ii. Измените генератор событий так, чтобы он учитывал отключения.
в файле views.py
from django.http import StreamingHttpResponse
from django_ninja import NinjaAPI
import asyncio
from typing import AsyncGenerator
api = NinjaAPI()
@api.get("/events")
async def get_events(request) -> StreamingHttpResponse:
async def generate_message() -> AsyncGenerator[str, None]:
while True:
await asyncio.sleep(1)
if hasattr(request, 'scope') and request.scope.get('client_disconnected'):
print("Client disconnected.")
break
yield 'data: {"test": "testvalue"}\n\n'
print("Working...")
return StreamingHttpResponse(
generate_message(),
content_type="text/event-stream",
headers={
"X-Accel-Buffering": "no",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
)
Эта настройка гарантирует, что ваш генератор событий остановится, когда клиент отключится.