---
title: Chat Example App Using Server-Sent Events
teaser:
tags: web,rails,redis
author: Mason Fischer
published_on: 2013-07-14
---

Rails 4 can stream data to the browser with [ActionController::Live](http://api.rubyonrails.org/classes/ActionController/Live.html) using [server-sent events (SSEs)](http://en.wikipedia.org/wiki/Server-sent_events). I was curious how server sent events worked so I decided to use them to implement a simple chat application. Tiny-chat is a chat app I built using [Goliath](https://github.com/postrank-labs/goliath), [Redis Pub/Sub](http://redis.io/topics/pubsub) and of course [server-sent events](https://developer.mozilla.org/en-US/docs/Server-sent_events).

## Subscribing to Events

    require 'goliath'
    require 'redis'

    class Subscribe < Goliath::API
      def response(env)
        EM.synchrony do
          @redis = Redis.new(Options::redis)
            channel = env["REQUEST_PATH"].sub(/^\/subscribe\//, '')

            # We pass the subscribe method a block which describes what to
            # do when we receive an event.
            # This block writes the message formatted as a server sent event
            # to the HTTP stream.
            @redis.subscribe(channel) do |on|
              on.message do |channel, message|
                @message = message
                env.stream_send(payload)
              end
            end
          end
        end
        streaming_response(200, { 'Content-Type' => "text/event-stream" })
       end
      end

      def on_close(env)
        @redis.disconnect
      end

      def payload
        "id: #{Time.now}\n" +
        "data: #{@message}" +
        "\r\n\n"
      end
     end
    end

When tiny-chat connects to the server it sends a GET request to `/subscribe/everyone` where _everyone_ is the name of the channel and with the “Accept” header set to `text/event-stream`. The streaming middleware (above) receives this request and subscribes to a redis Pub/Sub channel. Since Goliath is non-blocking multiple clients can be listening for events without tying up a Heroku dyno. The payload of a server sent event looks like this:

    id: 1361572294
    data: {"sender":"mason", "message":"hi"}

## Receiving messages

    class Receive < Goliath::API
      @@redis = Redis.new(Options::redis)
      def response(env)
        channel = env["REQUEST_PATH"][1..-1]
        message = Rack::Utils.escape_html(params["message"])
        @@redis.publish(channel, {sender: params["sender"], message: message}.to_json)
        [ 200, { }, [ ] ]
      end
    end

In the tiny-chat app, the client POSTs to `/everyone` with the message and the name of the sender encoded in JSON. The `Receive` middleware takes this request and publishes it to the appropriate redis Pub/Sub channel.

## The Client

    var source = new EventSource('/subscribe/everyone');
    source.addEventListener('message', function(event) {
      message = JSON.parse(event.data);
      $('.messages').append(
        "<div class="sender">"+message.sender+"</div>"+
        "<div class="message">"+message.message+"</div>");
    });

The client is a single page of static <abbr title="HyperText Markup Language">HTML</abbr>, <abbr title="Cascading Style Sheets">CSS</abbr> and Javascript. First we instantiate an [EventSource object](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). The EventSource object is built into the latest Firefox, Safari and Chrome.  There are also [polyfills](http://www.eventsourcehq.com/browser-support) available which bring support to other browsers including Internet Explorer.

Next, we add an event listener for the “message” event. “message” is the default event but you could also define your own custom events and bind event listeners to those. For example if the SSE payload was:

    event: userJoined
    data: {"username": "mason"}

You could bind to the custom event like this:

    source.addEventListener('userJoined', function(event) {
      username = JSON.parse(event.data).username;
      alert(user + " joined the room!");
    });

To send messages we do a regular ajax POST, prevent the default behavior of the form and reset the message field to be empty.

    $('form').submit(function(e){
      e.preventDefault();
      $.post($('.room option:selected').val(),
      {
        sender: $('.name').text(),
        message: $('form .message').val()
      })
      $('form .message').val('')
    });

That’s it! We now have a fully functional chat application. Tiny-chat is setup to run on Heroku with the Redis To Go plugin. There is a [demo running on Heroku](http://tiny-chat.herokuapp.com/). And the source is available on [GitHub](https://github.com/masonforest/tiny_chat). Try forking it and adding multiple chat rooms!

## Some useful links

* [[https://github.com/npj/sse-goliath](https://github.com/npj/sse-goliath)](https://github.com/npj/sse-goliath) - Another example app which tiny-chat is based on
* [[http://stackoverflow.com/a/5326159](http://stackoverflow.com/a/5326159)](http://stackoverflow.com/a/5326159) - A nice list of pros and cons of server sent events vs web sockets
