Reputation: 51
We want to add Server Side Events in our application, but currently the while True loop runs indefinitely and client disconnect is not causing the task to stop.
Reproducible example:
@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",
},
)
Above example will print Working... until server is closed (even if client closes connection and is not receiving the data).
Any way to detect client disconnect to stop the while True loop?
Already tried to return simple HttpResponse, putting code in try..except, creating def close, async def close methods on StreamingHttpResponse.
Upvotes: 3
Views: 267
Reputation: 1078
modifies your code to handle client disconnects .
i. Create an ASGI middleware to detect disconnects.
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)
Add this middleware to your ASGI application in 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())
after that ,
ii. Modify your event generator to respect disconnects.
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",
},
)
This setup ensures that your event generator stops when the client disconnects.
Upvotes: 1
Reputation: 51
So handling client disconnects should happen automatically, unfortunately Django < 5 has a bug in ASGIHandler
(https://code.djangoproject.com/ticket/34752)
In Django version 5.0.6
this issue is fixed.
Upvotes: 2