As described in brief live streaming is a way for Rails to stream content directly to a client through the response
object.
There are a multitude of use cases for this. This type of interactivity is the part of the raison d'ĂȘtre of many frameworks such Node.js and Ember.js.
One of the simpler, conceptually, yet ideal uses for live streaming is the sending of server sent events, or SSE, from a Rails controller to a client.
A very simple application which put together for demonstration purposes is the Big Clock.
A bit heavy-handed to do with a Rails app but what Big Clock does is to display a large, real time clock on a web page.
Server sent events are defined as part of the HTML5 specification and are a relatively simple concept. The short version is that they are messages sent from a EventSource
object which might be a CGI script; a PHP script; or, as in our case, a Rails controller method. Messages sent from an EventSource
follow a simple form.
event: event_type
data: some data which is sent from the server
SSE messages must be of the text/event-stream
MIME type.
event
: Tags the event with an event type which can be listened for by an event listener.
data
: The data payload of the event. If multiple data:
lines are present the will be concatenated with a newline.
id
: The event ID to set the EventSource
object's last ID value to.
retry
: The reconnection time, in milliseconds to use when attempting to send the event.
Any of the fields may be omitted. Any line beginning with a colon is ignored
With an overview of what an SSE is we can now go about using live streaming to implement it in a Rails app.
require 'json'
module ServerSentEvent
class SSE
def initialize io
@io = io
end
def write object, options = {}
options.each do |k,v|
@io.write "#{k}: #{v}\n"
end
@io.write "data: #{JSON.dump(object)}\n\n"
end
def close
@io.close
end
end
end
The initialize
and close
methods are straight forward, the only real meat comes from the write
method.
def write object, options = {}
options.each do |k,v|
@io.write "#{k}: #{v}\n"
end
@io.write "data: #{JSON.dump(object)}\n\n"
end
With this class we can now send a JSON object as the data portion of a SSE message with the options; id
, event
, and/or retry
added to the message before the payload. We can now create an EventSource
which can crank out push notifications.
As mentioned before an EventSource is code running on a server which is capable of sending data to clients. This sounds like a pretty good match for a Rails controller. In fact here it is.
require 'server_sent_event/sse'
class BigClockController < ApplicationController
include ActionController::Live
def index
response.headers['Content-Type'] = 'text/event-stream'
sse = ServerSentEvent::SSE.new( response.stream )
begin
loop do
time = "#{Time.now.strftime('%I:%M:%S %p')}\n"
sse.write( {:time => time}, :event => 'refresh' )
sleep 1
end
rescue IOError
ensure
event_streamer.close
end
end
end
What that index action does is; set the headers to the correct MIME type, create a new SSE object which wraps the response live stream and kick off a loop which
The resulting SSE message would then look something like
event: refresh
data: {"time":"11:13:56 AM\n"}
event: refresh
data: {"time":"11:13:57 AM\n"}
...
The last piece of the live streaming puzzle is to have something at the other end of the stream listening for the SSE messages our index action is now pushing out. This being a Rails app jQuery is readily available to us and since SSE were designed to work hand in hand with JavaScript integrating the two should be fairly painless. Here it is
jQuery(document).ready(function() {
setTimeout(function() {
var source = new EventSource('/browser');
source.addEventListener('refresh', function(e) {
var obj = JSON.parse(e.data)
$('div#face > p').text(obj['time']);
});
source.addEventListener('close', function(sse) {
self.close();
});
}, 1);
});
There are essentially three things happening here.
First: We wrap everything in a call to setTimeout with a delay of 1ms to give the EventSource a chance to 'prime the pump'.
Second: We instantiate our EventSource, our BigClock index action is mapped to /browser
in our routes file.
Third: We create a function which parses the message and then updates the contents of a paragraph element with the data passed as 'time' in the JSON object. We bind this function to the refresh events being pushed out from our BigClock#index
We also register a simple wrapper function around a 'close' event which instructs the EventSource
to stop sending messages. This message is not sent in the index method featured in these slides.
That is just about the simplest 'practical' use of the new live streaming functionality in Rails 4.
As you can imagine there are many and varied uses for the new functionality. A possible simple use case is using live streaming to give a user real-time feed back on the progress of a long running task. Naturally there are many, many more use cases as the popularity of frameworks like Node.js and Ember.js confirms.