CSC/ECE 517 Spring 2015/ch1a 12 LS
Live Streaming
Introduction
The purpose of live streaming is to send data over the Internet in real-time. This requires a data source, a data encoder, a media publisher and a proper network.
The concept of streaming first appeared in Rails 3.1, and became more optimus with the publish of Rails 4. Now Rails 4 supports live streaming, which uses handling I/O object and can send data from server to client in real-time.
Details
Template streaming
Since Rail 3.1, ::Streaming module has been added to ActionController. For example, the loading progress in a traditional Rails client-side may look like this<ref> https://github.com/oggy/template_streaming</ref>:
This is because that layouts are rendered before the content, thus causing control flow to be altered to render the page in the order that the client needs (layout first).But for some resources which are static, if they could be loaded while waiting for the server response, a lot of time can be saved.
The module ::Streaming inverts the regular Rails process of rendering the layout and template. Using ::Streaming, Rails will render template first and then layout. It will first run method yield and load up the template, and then renders the assets and layouts. In this way, the progress with streaming looks like this:
When to use streaming
For actions like new or edit, it may not be that necessary to use streaming. But for expensive actions like database query and generation of large amount of data, streaming can be extremely important and effective. Take the following action as an example:
def storyboard @stories = Story.all @developers = Page.all @comments = Comment.all end
Since most of the queries here are executing in the controller, it might take a while before them to finish, thus causing the front end (user) to wait for long. We can solve this problem and enhance user experience, the above code can be rewrite as follows,
def storyboard # Allow lazy execution of the queries @stories = Story.all @developers = Page.all @comments = Comment.all render :stream => true end
(Note: :Streaming only works with templates. It doesn’t work with :json or :xml)<ref>http://apidock.com/rails/ActionController/Streaming</ref>
Tweaking views
When using streaming, instance variables that are set in the template and are used in the layout won’t need some tweak to work well. This is done by using content_for, provide and yield. Here’s a simple example where the layout expects the template to tell which title to use:
<html> <head><title><%= yield :title %></title></head> <body><%= yield %></body> </html>
Instead of yield, we can also use content_for to specify the title:
<%= content_for :title, "Main" %> Hello
And the final result would be:
<html> <head><title>Main</title></head> <body>Hello</body> </html>
However, if content_for is called several times, the final result would have all calls concatenated. For instance, if we have the following template:
<%= content_for :title, "Main" %> Hello <%= content_for :title, " page" %>
The final result would be:
<html> <head><title>Main page</title></head> <body>Hello</body> </html>
This means that, if you have yield :title in your layout and you want to use streaming, you would have to render the whole template (and eventually trigger all queries) before streaming the title and all assets, which kills the purpose of streaming. For this reason Rails 3.1 introduces a new helper called provide that does the same as content_for but tells the layout to stop searching for other entries and continue rendering.
For instance, the template above using provide would be:
<%= provide :title, "Main" %> Hello <%= content_for :title, " page" %>
Giving:
<html> <head><title>Main</title></head> <body>Hello</body> </html>
That said, when streaming, you need to properly check your templates and choose when to use provide and content_for.
Live Streaming
The module ::Live was then added to ActionController at Rail 4. The difference between Streaming and Live is that Streaming is implicitly included whenever you create a new controller class, while ::Live requires explicit include.
Traditional Response
In this type of communication between server and client, data will be buffered until all render works are finished, and everything will be sent to the client at the same time. And the stream object behaves just like an IO object. Here’s the sample code:
class MessageController < ApplicationController def message response.headers["Content-Type"] = "text/event-stream" 10.times { response.stream.write "This is a response message!\n" sleep 1 } ensure response.stream.close end end
When we run the code above, the web page will remain blank for 10 seconds, and shows all 10 lines of “This is a response message!” altogether.
Live Streaming
In order to enable live streaming, we need to include a module named ActionController::Live. When this module is mixed into the controller, all actions within that controller will be able to stream data to the client in real time, like the following code:
class MessageController < ApplicationController #here we enabled live streaming include ActionController::Live def message response.headers["Content-Type"] = "text/event-stream" 10.times { response.stream.write "This is a live streaming message!\n" sleep 1 } ensure response.stream.close end end
The above code looks exactly the same as the traditional response, except the newly included module ActionController::Live. Now data can be streamed to the client each time when write method is called. When we run the code above, the web page will print one line “This is a response message!” for every seconds and repeats 10 times.
Server-Sent Event
Server-sent event (SSE) is a technology where browser can automatically receive update data from a server via HTTP connection. Whenever server sends data, the SSE method in the browser will trigger an event.The SSE EventSource API is standardized as part of HTML5 by the W3C. It can be used together with the Live API to achieve full-duplex communication.
A simple SSE looks like the following :
require 'json' module ServerSide 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: #{object}nn" end def close @io.close end end end
This module assigns the I/O stream object to a hash and converts it into a key-value pair so that it is easy to read, store, and send it back in JSON format.
Now we can wrap stream object inside the SSE class. By doing this, we need to include SSE module inside the controller, so that the opening and closing of connections will be managed by SSE module. And we also add the ensure clause to make sure that the connection will always be closed after execution.
require 'sse' class MessagingController < ApplicationController include ActionController::Live def stream response.headers['Content-Type'] = 'text/event-stream' sse = SSE.new(response.stream) begin 5.times { sse.write(“Streaming using SSE!”) sleep 1 } ensure sse.close end end end
The code above will print the sentence “Streaming using SSE!” once per second, until the loop ends.
Here’s another sample showing how to use SSE to stream data (more information can be found here):
# This class provides the ability to write an SSE (Server Sent Event) # to an IO stream. The class is initialized with a stream and can be used # to either write a JSON string or an object which can be converted to JSON. # # Writing an object will convert it into standard SSE format with whatever # options you have configured. You may choose to set the following options: # # 1) Event. If specified, an event with this name will be dispatched on # the browser. # 2) Retry. The reconnection time in milliseconds used when attempting # to send the event. # 3) Id. If the connection dies while sending an SSE to the browser, then # the server will receive a +Last-Event-ID+ header with value equal to +id+. # # After setting an option in the constructor of the SSE object, all future # SSEs sent across the stream will use those options unless overridden. # # Example Usage: # # class MyController < ActionController::Base # include ActionController::Live # # def index # response.headers['Content-Type'] = 'text/event-stream' # sse = SSE.new(response.stream, retry: 300, event: "event-name") # sse.write({ name: 'John'}) # sse.write({ name: 'John'}, id: 10) # sse.write({ name: 'John'}, id: 10, event: "other-event") # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500) # ensure # sse.close # end # end # # Note: SSEs are not currently supported by IE. However, they are supported # by Chrome, Firefox, Opera, and Safari.
Note: By default Rails provides a one-way communication process by writing the stream to the client when data is available. However, if we can add SSEs, we can enable events and responses, thus making it two-way.(引用)
Troubleshooting
references
<references>