Django 5.0: Elevating Experiences with Server-Sent Events

This video features melhin at DjangoCon Europe 2024 in Vigo, Spain.

Django 5.0: Elevating Experiences with Server-Sent Events
0:22:58
Published July 10, 2024
1,264 views

Talk: Django 5.0: Elevating Experiences with Server-Sent Events - A Journey from Polling to Real-Time Vibes by melhin

https://pretalx.evolutio.pt/djangocon-europe-2024/talk/MP98WP/

Summary

Server-Sent Events (SSE) provide a simple, unidirectional real-time channel over HTTP: an async Django view holds a connection, yields UTF-8 event-stream messages, and handles client cancellation for cleanup. The speaker shows how to add SSE alongside an existing synchronous Django application, using Redis Streams as a shared channel so background work and notification records can reach connected clients without replacing current endpoints. They cover named events, heartbeats, connection and disconnection status, stream expiry, multi-device listeners, authentication, and a small HTMX-based frontend, while noting practical limits such as browser connection caps, middleware and settings compatibility, load balancer timeouts, and infrastructure considerations.

Key takeaways

  • SSE is unidirectional and runs over a single long-lived HTTP connection, unlike bidirectional WebSockets.
  • Django async generators and StreamingHttpResponse can yield named events incrementally, while cancellation handling supports cleanup when clients disconnect.
  • A separate async SSE service can coexist with a synchronous Django app, communicating through Redis channels or Redis Streams.
  • Redis Streams support multiple feeds, per-user or per-device listeners, stream IDs, and expiry for notifications that no longer need delivery.
  • Heartbeats help keep connections alive through load balancers, and browser limits on concurrent SSE connections may require a shared JavaScript connection and client-side broadcasting.
  • Production deployments need to account for async-compatible middleware and installed apps, authentication, worker behavior, and compression or infrastructure support.

Summarised automatically from the transcript.

Chapters

  1. 0:00 Introduction The speaker introduces herself and outlines the talk's focus on Server-Sent Events in Django.
  2. 1:32 Polling, WebSockets, and Server-Sent Events An overview of polling and WebSockets leads into the unidirectional HTTP model used by Server-Sent Events.
  3. 3:48 SSE Streams in Django The speaker demonstrates event-stream formatting, named events, async generators, and Django's StreamingHttpResponse.
  4. 5:21 Async Connections and Local Development This section covers cancellation handling, cleanup, and configuring Uvicorn for development with long-lived connections.
  5. 7:40 Real-Time Notifications in Django The talk explains where real-time behavior adds value and how notifications can improve otherwise synchronous applications.
  6. 9:13 Sync and Async Application Architecture A proposed architecture keeps the existing synchronous Django app while adding an asynchronous service that publishes and delivers notifications.
  7. 11:28 Redis Streams Redis is introduced as the shared messaging layer, with streams supporting multiple feeds, devices, and event sources.
  8. 13:00 Notification Feed Demonstration A real-world-style demo shows user feeds, mentions, online events, and disconnection notifications.
  9. 15:20 Event Data Patterns and Implementation The speaker defines a shared server-client data format and walks through publishing notifications and consuming multiple Redis streams.
  10. 19:14 Heartbeats and Client Integration This section covers heartbeats for load balancers, offline events, and connecting SSE responses to a front end with HTMX.
  11. 20:46 WebSockets and Deployment Considerations The talk compares SSE with WebSockets and discusses settings, middleware, installed apps, browser connection limits, and Django's async compatibility.

Transcript

3,648 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:00

Hi. Thank you for being on my talk. Elevating experiences with service and events. Let me start by saying it's really a honor to talk because over the years I've been um watching a lot of DjangoCon videos and PyCon videos and it has been a big part of my um learning experience. So who am I? Um I'm Ellen. I have been a software engineer for a while now, predominantly fumbling at building software. I work at uh QMontes, which is a marketplace for chemicals. Uh I'm a software engineer over there and I lead one of the scrum teams. Um I'm always tinkering with um some kind of technologies and also frameworks

0:46

and uh the talk over here is part of one of the explorations that I've done. So I've not run it on production, so with codes. And uh I also have a lot of incomplete projects in GitHub. Uh this is my GitHub handle, so you can see it there. Um before I start, um how many of you have attended my talk in PyCon Italy? Yeah, okay. So um there's gonna be some reputation but the examples are gonna change a little bit. So uh let's let's start, right? So I don't w I want to set the base first, so I'm gonna go with some mandatory definitions. Um some of them is a very common known, but just to set the base. So following.

1:32

Predominantly very simple, client at every interval just asks the the server whether there's an update or not. There are two kinds, they're short and long. Yeah, the short ones are uh you know it's just instant and the long ones hold the connection, wait for an update or release it. How many of you still use polling? Me too. Yeah. All right. Um then WebSockets. Um now web sockets are bi-directional in nature, right? So the messages flow from both sides, uh from both the clients. Client and server. It's done over TCP protocol and it's not part of the normal HTTP flow. Then we come to server send

2:17

events. Now server send events are unidirectional over a single HTTP connection. So the client Connects to the server and then the server holds the connection and keeps sending the data back. Uh this is what we're going to talk about. So I'm going to look at uh um uh how is this done, right? So there's actually an event stream format and the event stream format is pretty s pretty basic. It's just like a a stream of text data which is UTF eight encoded. Um and the messages are separated by a pair of new lines. So it's basically slash and slash and so sends a message to the client. So this is an example. There there you could also see that um this colon and the colon is a place to

3:03

have um a section known as comments. So you you could see that data over here is like a comment saying that this is This is the data. Um event stream format also has um another form, it's known as named events, and this is particularly useful because now you can also express what kind of event it is, just not You don't have to write kind of fit it in the data. It's just there. So for instance, there's an example, I hope you can see. Yeah. Uh yeah? Okay. So there's like a user connection and a user disconnection event. And then the client Can actually react differently. So I'm gonna explain stuff using code. I hope I'm not covering the screen.

3:48

Um do not use this code in production. It is not uh it doesn't work. Uh It's just easy for me to explain it like that. Alright, so let's uh let's look at a simple streaming view. And the stream simple streaming views doesn't do much. Um what it does is it basically uh sleeps For some time and then sends account. So let's take it apart. So the first part is the async generator, and this forms the main component of the streaming HTTP of the server sent event Um and the async generator is very simple. What over here what it does is it just sleeps for a second, increments the sound, uh count sorry, increments the count, uh, and then yields the count. Yeah, so the server just sends the count back.

4:36

And the This is what I explained earlier with the named event, so you can actually have this is how the event is formed, right? So event is new and the event has a data which is the count. And how is this done? So streaming HTTP response is how this is done. Um and streaming HP HTTP response has been there in Django for a while, right? So it's basically a mechanism where Django uses where it holds the connection. connection and sense data. In the sync world, in the RISCI applications usually what we do is uh you use it for like sending over huge C S Vs or um um Excel sheets and stuff like that where you don't want to store it in memory. Um but what happens there is

5:21

the worker um the worker connection is actually held, right? So your worker is occupied at disk. Now in the a async side of things, when we use ASCII, it's part of the event loop. So the worker is kind of not held and then you can actually use you can actually have multiple of these, right, using one worker. Um another part that um I want to show and this is if If I'm not mistaken, kind of introduced in 5. 0, where uh you actually get to catch the cancellation error. Now, this is important because what it happens is you know your view now gets to know that. there is actually a disconnection happened like if someone closes a tab or a browser or something like that

6:08

without explicitly uh sending a disconnect event, right? And the browsers is uh in the browsers it's always a bit tricky. Um it's also a place to have cleanup, right? you I don't know you opened a Redis connection or you know connection to a different database or something like that then you can just release that Um so uh uh uh how how is it how do you develop this on uh local? So I was using UV-Con mostly. Um and uh there are two things that I want to point out specifically. is the reload so that it behaves like run server and the timeout grapes will shut down. And if it it's not there, then when you reload, uh so when you code and you save, it doesn't reload because the connection is still held.

6:55

So this is an This this uh improves the experience. I'm bad at doing demos, so I uh have bought off screenshots. Is it okay? So I'll try to explain. So on the left side you see uh the server log logs. That's predominantly what it is. And on the right side you have um uh the curl. It's so you can actually test this using a simple curl command. Um you don't need any other tools. Now, why are we doing this, right? So um our primary focus, right, always revolves around providing user value. And um over the years uh the synchronous Django has been around for

7:40

twenty years or something like that. And it has like a stable um upgrade history, has a lot of good third party ecosystems and things like that. So you have your Django synchronous app because it's solid, right? But you could always do with some kind of real-time in um interactivity. And there are certain sets of applications that have real-time interactivity as their core principles, right? So like messaging apps or collaborative tools. or live streaming platforms and online games and real time dashboards. This is just some examples. But most of your applications um might not need it. And but there is some there is still

8:25

something that you can use to kind of improve uh the overall user experience and that's notifications. Notifications is a place where you can actually you know just add it in any app that you have. And uh Um it also allows us to offload heavy processes. So you can just offload it to a salary worker and then as and when things happen you can actually send the response back. Now let's imagine how do we do this synchronously, right? using polling. So um the normal pattern is just oh so this is the notification system. Um so any interaction creates a record on your notification system. Like in your model you just have user A send a message to user B, right?

9:13

And then since we are polling, we are just asking repeatedly, is there a new update? Is there a new update? And uh when there is one, you send it back to the client, the client reacts. Now let's make this l uh real time. Now this is a suggestion so and also an exploration. So we keep the asynchronous app running as it is. So you have all the endpoints that create messages. and the polling endpoints are all there. Um you then just add listeners towards the end of the notification. So if a record is ge getting created, then you basically add a listener over there, publish To a common channel, and

9:58

then you have your asynchronous app running separately. So your synchronous world is as it is, nothing changes. You're just adding another asynchronous app, and then the asynchronous Asynchronous app lists on the common channel so if there is any kind of uh messages that are coming in, the asynchronous app picks it up and sends it back to the client There is actually a very good talk about running sync and async app uh separately. I'm gonna point to a link towards the end of my talk. Um so just representing what I just said in pictures. Uh so you see the synchronous app, there's an async app. Now it also uses the same ecosystem, right? So it uses Redis

10:43

and DB, uh whatever you're using. And then um this is like the connection. So the moment there's a connection from the client The async app then starts listening on a stream. I'll I'll come to the stream. Um and whenever a sync app has an interaction, it publishes publishes this to a Reddit channel, and then the async app just sends it back. Now Redis is a common channel. So Redis predominantly is deployed along with most of our applications, so it's always readily available. You don't have to change or add a new infrastructure as it is. It also might be available. On your local machines. And one important thing, and thing that Django channels uses this a lot, is the Redis data structures.

11:28

There are a lot of Redis data structures that you can actually use. And one interesting data structure is Redis. streams. It was introduced in 5. 0 I guess. And it it has a lot of use cases like you know event sourcing and logs and also notifications. So how do we let's conceptually look at how do we use red streams here right so you have your listener right and then you listen on multiple streams So this is a feature of the Redis uh stream, right? So you can have multiple of these streams. You can have let's say a post stream, and then you have a user stream, and something comes in a post, like a new mention

12:14

and let's say something comes from the user stream where someone says, Hey, this user is offline. Now with one listener, you can actually have uh multiple um you can actually listen to multiple of these streams. So you the listener can be configured however you want. It's a very interesting concept. Also you can use it for multi-device. So let's say you have I don't know phones, tablets, or uh laptops. So you see your tablet is now listening. Um there's a message coming in, the tablet gets it. Um then the phone comes in now the phone also doesn't get the old message because it's just linked there then the new message goes both to tablet and the phone

13:00

so so on and so forth So now let's look at an example. So usually you show the code first or you do the code and then show the demo. I'm doing it a little differently Hopefully it works. Um so what do we have here? So uh we're going to mimic a real-world application. And how are we going to mimic it? We have three streams so you have three uh users and all of them have their own feeds right and So this is now they're listening on their feet, right? They're just staying there.

13:46

And you can see that they have their whenever someone mentions their let's say email address, then um It comes to a feed. Now immediately you can see something over here. So we are not just mention listening on uh messages but we are also listening on events of a different type and that's basically uh an online event, right? So this is how you do the registry thing. This is um so someone posts, yeah, you get it on the feed and without doing anything else. Um and w wh how is it related to your real world? So let's say the sync app has all this capability, right? And whenever there's a notification that says, hey there is a new message, you call your

14:35

endpoint. So you can see yeah Oracle Neo and Trinity are talking to each other in the feed. There's also one more thing that uh so we I I explained before about the whole uh cancel thingy. So here let's say uh we go ahead and uh close the feed. Yeah, now we're gonna close the feed. The moment we close the feed, you can immediately see that there are like an offline message coming in both of them. Yeah? So that's that's what what I'm gonna show. uh upfront, uh sorry, but that's what I'm gonna show now. Uh how do we do it in code, right?

15:20

So before we move into the details of it, it's very important that we select a common data pattern. Okay, so you have to have an agreed um data pattern between server and a client. And it's basically just to standardize the stream response and you know also add more visibility to the fields that are present. And you could also Do something w about the APS spec over here. So in our example, um I've I'm using the same code, right? So I've showed you the demo. This the example you're using an event type, an event at and a text. Okay? So event type could be User being online, user disconnecting, or there's a new message.

16:06

The sync part of the application uh looks something like this. Um so this is the interaction that I was mentioning before. So the on the notification side Towards the end, you're actually just doing this, just this part. All the other sync part just remains as it is, right? You're not touching any of the sync applications, you're just adding this. And what happens here is you're taking a user ID, putting it as the stream name so that it belongs to a particular user, you're adding the message to the top of the ready stream, and then you're adding an expiry. Now expiry is an interesting concept over here. Why? Why? Because um sometimes the clients are disconnected and then they don't need to be notified. And in that case, it's always better to just put an expiry and it just expires.

16:55

Redis takes care of it itself. And on the async side you have an SSE view, just the Don't look at the authentication code, but I'm just saying you can use the same authentication that you're using in your sync apps, yeah. The cookie authentication also. And um here, what you do is whenever you're connected, and that's what you saw over there. when someone is connected it shows online. And that's how how you send it is you send a status when you connect and then you listen on the streams. So you can listen to multiple streams. It's just a method we go in detail over there. Uh another stuff is actually reacting to events, right? So if you have multiple streams that have multiple like categories. One is a connection stream and another one is uh

17:41

like a new notification, you can react differently. But you should follow The same data pattern, don't look what I did over here. I just put HTML there. But you should follow a pattern over there. And the next one is basically a heartbeat. Now, this is also an interesting concept. So when When you're listening on streams, you're also giving uh timeouts. And this is important because the moment um someone you know like it's it's important to send a heartbeat for load balancers because some of the load balancers that are configured Some of them, not all. Uh disconnect the connection at idle time and uh you might have to tweak your infrastructure. So what we are doing is maximum not touching the existing system

18:26

but just adding the functionality, right? And hardbeat could also be used for multiple. things you can actually use it like a user counter saying that how many users are online and there are multiple things that you can do with that. When finally you send the offline event, right? So whenever someone cancels you say they're offline. Now, this is the listener, and the listener is very straightforward. It what it does is it listens to uh multiple streams. You can see towards the bottom, there's a user key and a connection stream, and there's also something known as a last ID return. So that's what is used to pick the reference in the Redis streams. Um so there is a special thing in Redis streams where you can actually do a dollar and then it always places on the top of the stream.

19:14

So you you can just ignore all the other messages. Now we're going to look at some front end. Um I've d uh the demo that I showed you was done mostly using HCMX, but I'm not good at HCMX. But just for representational purposes, so this is how it looks like. Of course, there's CSS and um small body HTML, but this is predominantly how the connection looks like. So This is the connection block, so you see the connection block, and within that there are two things. So there is one where you're actually listening to a post, and the moment there is something that comes up, like an update, you're triggering another URL.

19:59

uh from there. And then you also have the status. In this case I'm just injecting whatever comes in into the HTML. You could do different things with that. A very s simple thing. So this is just like a you know, a developer tool view of how things are. So you can see uh you can't exactly see but the last one was the stream connection. Um and whenever a new notification comes in, you just call your sync app to get the content out of it. Now why not just use WebSockets, right? So again WebSockets has its geuse, but not a lot of our use cases have bidirectional communication, real-time communication. You don't need to change anything

20:46

on your infra. Again, we're just trying to keep things as it is by adding more functionality to it. Compressions are also not supported out of the box. There's a good talk about uh SSE web sockets and low long pro. I'll post the link again just uh uh uh uh towards the end of my talk. So does everything work? Yeah, so in my experiments um there are things that you'll have to uh be aware of. And one of them is settings. And since you're running both the sync and the async uh app together, you have to always l be aware that there is some things in the settings that you've put like five years ago and forgotten about it, which is not Uh uh I think

21:31

compatible middlewares, again, same problem. Uh install apps, that's something that you have. Um on your like each of your applications would have a lot of uh install apps. That might behave differently. But the main limitation is connection. So browsers by d generally have a connection limit of six SSE connections. And you will have to get over. that. And to do that you might have to use some of the JavaScript functionalities like having like one place to all the SSC connection and then use broadcast and things like that. So this is something that you'll have to be uh careful before you Um do the SEC.

22:17

And to look at middlewares, uh it's always good to look at how Django does, because Django has a lot of middlewares which it has uh made both sync and async compliant and it uses uh um an interesting technique to do that. Um predominantly this is something uh of a detail, but uh it's also good to look at Django, how does it use the thread pool ex executor as executor, especially in the ASCII ref. That's it.

Questions this talk answers

What are server-sent events, and how are they different from polling and WebSockets?

Server-sent events use a single HTTP connection that the server keeps open and uses to send data in one direction. Polling repeatedly asks for updates, while WebSockets provide bidirectional communication over TCP outside the normal HTTP flow.

Discussed at 2:17

How do you implement server-sent events in Django?

Use an asynchronous generator with `StreamingHttpResponse`: the generator waits for or produces data and yields event-stream messages, such as named events containing a count or notification. Django’s async handling lets multiple streaming connections share an event-loop worker instead of occupying one synchronous worker each.

Discussed at 3:48

How can a Django SSE view detect when a client disconnects?

The view can catch the cancellation error, which is raised when a browser tab or connection closes without sending an explicit disconnect event. That gives the application a place to clean up resources such as Redis or database connections.

Discussed at 5:21

How can I add real-time notifications to an existing synchronous Django app?

Keep the existing synchronous endpoints and polling system, then add an asynchronous SSE app alongside them. When the synchronous app creates a notification, it publishes an event to a shared Redis channel or stream, and the async app forwards it to connected clients.

Discussed at 9:13

How can Redis Streams support multiple notification feeds or devices?

A listener can subscribe to multiple Redis Streams, such as post and user streams, and react to different event categories while using one SSE connection. Separate devices can also listen to the relevant stream and receive new events without replaying older ones.

Discussed at 11:28

Why should an SSE connection send heartbeat events?

Heartbeats prevent infrastructure such as some load balancers from treating an otherwise idle connection as dead and disconnecting it. They can also carry useful information, such as the number of users currently online.

Discussed at 17:41

When should I use server-sent events instead of WebSockets?

SSE is a good fit when communication only needs to flow from the server to the browser, such as notifications, and when you want to add real-time behavior without changing the existing infrastructure. WebSockets are more appropriate for genuinely bidirectional real-time applications.

Discussed at 19:59

What limitations should I consider before using SSE in Django?

Running synchronous and asynchronous applications together requires checking settings, middleware, and installed apps for async compatibility. Browsers also commonly limit a domain to about six SSE connections, so applications may need to centralize connections and distribute events with JavaScript.

Discussed at 21:31

Presenters

Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.

More videos from DjangoCon Europe