How do server-sent events actually work?

Server Sent-Events

Server Sent-Events Problem Overview


So I understand the concept of server-sent events (EventSource):

  • A client connects to an endpoint via EventSource
  • Client just listens to messages sent from the endpoint

The thing I'm confused about is how it works on the server. I've had a look at different examples, but the one that comes to mind is Mozilla's: http://hacks.mozilla.org/2011/06/a-wall-powered-by-eventsource-and-server-sent-events/

Now this may be just a bad example, but it kinda makes sense how the server side would work, as I understand it:

  • Something changes in a datastore, such as a database
  • A server-side script polls the datastore every Nth second
  • If the polling script notices a change, a server-sent event is fired to the clients

Does that make sense? Is that really how it works from a barebones perspective?

Server Sent-Events Solutions


Solution 1 - Server Sent-Events

The HTML5 doctor site has a great write-up on server-sent events, but I'll try to provide a (reasonably) short summary here as well.

Server-sent events are, at its core, a long running http connection, a special mime type (text/event-stream) and a user agent that provides the EventSource API. Together, these make the foundation of a unidirectional connection between a server and a client, where messages can be sent from server to client.

On the server side, it's rather simple. All you really need to do is set the following http headers:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

Be sure to respond with the code 200 and not 204 or any other code, as this will cause compliant user agents to disconnect. Also, make sure to not end the connection on the server side. You are now free to start pushing messages down that connection. In nodejs (using express), this might look something like the following:

app.get("/my-stream", function(req, res) {
    res.status(200)
       .set({ "content-type"  : "text/event-stream"
            , "cache-control" : "no-cache"
            , "connection"    : "keep-alive"
            })

    res.write("data: Hello, world!\n\n")
})

On the client, you just use the EventSource API, as you noted:

var source = new EventSource("/my-stream")
source.addEventListener("message", function(message) {
    console.log(message.data)
})

And that's it, basically.

Now, in practice, what actually happens here is that the connection is kept alive by the server and the client by means of a mutual contract. The server will keep the connection alive for as long as it sees fit. Should it want to, it may terminate the connection and respond with a 204 No Content next time the client tries to connect. This will cause the client to stop trying to reconnect. I'm not sure if there's a way to end the connection in a way that the client is told not to reconnect at all, thereby skipping the client trying to reconnect once.

As mentioned client will keep the connection alive as well, and try to reconnect if it is dropped. The algorithm to reconnect is specified in the spec, and is fairly straight forward.

One super important bit that I've so far barely touched on however is the mime type. The mime type defines the format of the message coming down the connecting. Note however that it doesn't dictate the format of the contents of the messages, just the structure of the messages themselves. The mime type is extremely straight forward. Messages are essentially key/value pairs of information. The key must be one of a predefined set:

  • id - the id of the message
  • data - the actual data
  • event - the event type
  • retry - milleseconds the user agent should wait before retrying a failed connection

Any other keys should be ignored. Messages are then delimited by the use of two newline characters: \n\n

The following is a valid message: (last new line characters added for verbosity)

data: Hello, world!
\n

The client will see this as: Hello, world!.

As is this:

data: Hello,
data: world!
\n

The client will see this as: Hello,\nworld!.

That pretty much sums up what server-sent events are: a long running non-cached http connection, a mime type and a simple javascript API.

For more information, I strongly suggest reading the specification. It's small and describes things very well (although the requirements of the server side could possibly be summarized a bit better.) I highly suggest reading it for the expected behavior with certain http status codes, for instance.

Solution 2 - Server Sent-Events

You also need to make sure to call res.flushHeaders(), otherwise Node.js won't send the HTTP headers until you call res.end(). See this tutorial for a complete example.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAhmed NuamanView Question on Stackoverflow
Solution 1 - Server Sent-EventsMarcus StadeView Answer on Stackoverflow
Solution 2 - Server Sent-Eventsvkarpov15View Answer on Stackoverflow