Server Sent Events in Elixir

Server Sent Events (SSE) is a HTML5 standard that allows pushing events from the server to the client. I think it is unfortunate that most people think of Web Sockets to solve this problem as Web Sockets is not only harder to implement and use correctly, but also throws away many of the benefits of HTTP. I think 80 % of the cases for Web Sockets could and should be implemented using Server Sent Events instead. Unfortunately, the best technology does not always win…

Anyway, Server Sent Events are really simple. Basically it is just a standard for long polling and a JavaScript API for consuming the events. The event stream is also trivial to consume using standard tools such as curl or directly from the browser without JavaScript. Using SSE means that each client has a connection open to the server and the server can send data whenever it likes. The server cannot have a dedicated thread per connection as this would be far to expensive. A perfect case for actors!

As I’m just learning Elixir I wanted to create a really simple example. No chat messages or other complicated things. My example is just sending an incrementing value every two seconds. I hope that this trivial example shows some of the elegance and power of Elixir. The code is available at GitHub. You probably want to look at elixirsse.ex which contains all the logic.

I decided to use Webmachine because I managed to find some example code in Erlang showing SSE. All I did was simplify and convert this example to Elixir using Ewebmachine.

Lets get started and see if I can explain the code. Please let me know if I’m wrong :-)

defmodule ElixirSSE do
  use Ewebmachine
  # ...

This defines a module called ElixirSSE and tells Elixir that it is using Ewebmachine module. Actually, use is not a keyword. Instead use is a macro that expands to something like Ewebmachine.__using__(). This allows the module I’m using to apply whatever to my module. In this case it is importing all functions and macros from Ewebmachine and some other stuff. At this point, don’t worry too much about this.

  resource [] do
    to_html do

" end end

Here I’m defining a root resource for http://localhost:8080/. The resource is simply returning some HTML. There are a couple of things going on here. Both resource and to_html are macros that expands to calling functions in Webmachine. This just makes the code nicer to look at. One of the great things with Webmachine is The Diagram that shows which methods are involved when handling a request. Webmachine has built-in support for to_html which will generate the content type “text/html”.

Hmm, but what will happen if we request something other that html? Lets test this:

curl -v -H "Accept: application/json" http://localhost:8080
> GET / HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: application/json
< HTTP/1.1 406 Not Acceptable
< Content-Type: text/html
< Content-Length: 165
* Connection #0 to host localhost left intact
406 Not Acceptable

Not Acceptable

Not Acceptable

mochiweb+webmachine web server

Nice! This is exactly what we want!

  resource ['events'] do
    content_types_provided do: [{'text/event-stream', :to_stream}]
    to_stream do
      {:stream, {<<>>, fn -> ElixirSSE.generate_stream(1) end}}

Here we define another resource: http://localhost:8080/events. We specify that the supported content types for this resource is only text/event-stream which is the mediatype for SSE. We also specify the atom :to_stream which tells Webmachine to use the method to_stream below. Have a look at Lispy Elixir to find out more about the relationship between atoms and methods.

The method to_stream looks a bit weird. Previously to_html just returned a string. In this case I return a tuple of 2 things: the atom :stream and another tuple. It is a common pattern in Erlang to use the first element in the tuple to specify what the rest means. In this case :stream lets Webmachine know that this should be a Streamed Body response.

The next weird thing is <<>>. This is a binary string. You can even do crazy things like pattern matching on bits! This allows you to easily implement low level binary protocols and still have really readable code.

Finally there is fn -> ElixirSSE.generate_stream(1) end. This creates an anonymous function that when called it will call generate_stream(1) and that will start generating the stream. A real gotcha here was that I needed to prefix the function with the module name ElixirSSE. I think the reason is the resource macro, because normally this prefix is not needed. Yeah, macros are not perfect.

  def generate_stream(counter) do
    receive do
    after 2000 -> {"data: " <> to_string(counter) <> "\n\n", fn -> generate_stream(counter+1) end}

This is what generates the events in the stream. Fairly straight forward:

The receive block is how you receive messages that are sent between actors (for now I refuse to call them processes as it only makes people confused). In this case I don’t send any messages and will therefore never receive a message. Instead I have the after 2000 -> block that will be called after 2000 milliseconds. But won’t the thread be blocked here? No, that is the nice thing with Erlang and the actor model!

Again I return a tuple of two elements. The first is a string that will be the body and the second a anonymous function to create the next part of the stream. Notice that <> is string concatenation in Elixir.

That’s it! What do you think?

This Post Has 3 Comments

  1. Yiyo Castillo

    This was a great article, everything works as expected!

    I only suggest write more articles exploring SSE because is an interesting alternative to web sockets.

Leave a Reply