diff --git a/content/django-server-sent-events.md b/content/django-server-sent-events.md index 2a08825..52de095 100644 --- a/content/django-server-sent-events.md +++ b/content/django-server-sent-events.md @@ -330,6 +330,56 @@ And that's it! We can now open two browser windows and see the messages appear i Check out the repo for the full code where I've also added a simple form for submitting new messages. +### Dealing with reconnections + +One of the nice things about SSE is that it will automatically reconnect if the connection is lost. It even has a +mechanism for dealing with the fact that the client might have missed some events while it was disconnected. + +This is done by sending a `Last-Event-ID` header with the request. The value of this header is the `id` of the last +event that the client received. The server can then use this to determine which events to send to the client. + +To deal with this we can expand on our `stream_messages` function and view as follows: + + :::python + async def stream_messages(last_id: int | None = None) -> AsyncGenerator[str, None]: + connection_params = connection.get_connection_params() + connection_params.pop('cursor_factory') + aconnection = await psycopg.AsyncConnection.connect( + **connection_params, + autocommit=True, + ) + channel_name = "lobby" + + if last_id: + messages = ChatMessage.objects.filter(id__gt=last_id) + async for message in messages: + yield f"id: {message.id}\nevent: message_created\ndata: {message.as_json()}\n\n" + + async with aconnection.cursor() as acursor: + await acursor.execute(f"LISTEN {channel_name}") + gen = aconnection.notifies() + async for notify in gen: + payload = json.loads(notify.payload) + event = payload.get("event") + event_id = payload.get("event_id") + data = payload.get("data") + yield f"id: {event_id}\nevent: {event}\ndata: {data}\n\n" + + + async def stream_messages_view( + request: HttpRequest, + ) -> StreamingHttpResponse: + last_id = request.headers.get("Last-Event-ID") + return StreamingHttpResponse( + streaming_content=stream_messages(last_id=last_id), + content_type="text/event-stream", + ) + +We now send the `id` of each message, and whenever a (re)connection is made we check if the client sent a `Last-Event-ID` +and if so we send all messages with an `id` greater than that. + +This change does also require some changes to our utility functions and model. Those are to be found in the git repo. + ### Conclusion Django is boring, which is a good thing, to the degree where it is always the safe option. But with the advances in