SSE or a WebSocket
We now have two ways for a server to keep sending data on an open connection: server-sent events and WebSockets. They differ in four ways.
- Direction. A WebSocket carries messages both ways. With SSE, only the server sends events. The client sends its data in ordinary requests.
- What they are built on. An SSE stream is an ordinary HTTP response. It has a status code and headers, and the server authenticates its request like any other. Once buffering is turned off, it passes through proxies like any other response. A WebSocket starts as an HTTP request and then switches to its own protocol.
- The data. An event carries text. A WebSocket message can carry text or binary data.
- Reconnecting.
EventSourcereconnects on its own and sendsLast-Event-ID. For the WebSocket in Courselore, we wrote the reconnecting and the catching up ourselves.
Choosing one
Ask what the server sends, and why.
- If the server sends the response to a request, and the response takes a while, stream the response. CourseChat’s reply is this case.
- If the server sends events that no request asked for, and the client sends little, an SSE stream that stays open is enough. The client opens it once with
EventSource, and the server writes an event each time something happens. A page where a Shopend merchant sees new orders as they arrive is this case. - If the server sends events that no request asked for, and the client also sends often, use a WebSocket. Courselore’s chat, where clients send typing events, is this case.
Courselore’s chat with SSE
Courselore’s chat could also have used SSE, with POST requests for what the clients send. Marcus’s client would open one stream for course 7:
const source = new EventSource("/courses/7/live");
The response never ends. The server writes a message.created event each time a message is posted in one of Marcus’s rooms, and a typing event each time someone types in one. Priya’s client sends each typing event as a POST, and each message as the POST it already sends.
Recall the request count for typing indicators with long polling: about 220 requests a second. About 200 of them were new long polls: about 170 after a typing event ended the clients’ long polls, about 20 after the 30-second limit, and about 10 after a new message. A stream does not end when the server sends an event, so these 200 requests go away. What is left is the POST requests: about 17 typing events and 1 message a second. One server handles that easily.
Catching up after a dropped connection also gets simpler. If each message.created event’s id: is the message’s identifier, EventSource sends the last one it received in Last-Event-ID when it reconnects, and the server sends the messages posted after it. In the WebSocket design, the client did this itself: after it reconnected, it sent GET /rooms/12/messages?after=61851.
A stream that stays open for a long time can also be closed by a proxy. When nobody posts or types, the stream carries nothing. Recall from long polling that proxies such as nginx close a connection after 60 seconds with nothing sent, by default. So the server sends a comment every 30 seconds or so: a line that starts with a colon, such as : ping. EventSource ignores comments, but the connection is no longer quiet.
So at Courselore’s load, SSE and POST requests would also have met chat’s requirements. The WebSocket is still cheaper for each typing event. On the WebSocket, a typing event is one small frame. With SSE, it is a full request, with 700 to 800 bytes of headers, and the server authenticates it. That difference grows with the number of events the clients send.
Recall that over HTTP/1.1, a browser opens only about six connections to one server. That was one reason Marcus’s client kept one long poll for the whole course, not one per room. An open SSE stream holds one of those connections for as long as it is open. If Marcus has Courselore open in six tabs, the six streams take all six connections. Then the requests of a seventh tab wait until one of the streams closes. HTTP/2 carries many requests and responses on one connection, so with HTTP/2 this limit no longer applies. The limit also matters less for CourseChat’s replies, because each stream ends after about 10 seconds.