What is streaming?
Streaming has been around Rails since version 3.2, but it was limited solely to
streaming templates . Rails 4 came out with more mature streaming functionality in real time. In essence, this means that Rails is now capable of natively processing I / O objects and sending data to the client in real time.
Streaming and Live are two separate modules implemented inside ActionController. Streaming is enabled by default, while Live must be explicitly added directly to the controller.
')
The main api streaming uses the Fiber class (available from ruby ​​version 1.9.2). Fayber provide tools for thread-like concurrency in ruby. Fiber allows threads to pause and resume work at the request of the programmer, and not to be essentially proactive.
Streaming templates
Streaming reverses the usual layout and template rendering. By default, Rails renders a template first, and then a layout. The first thing he does is run
yield
and load the template. After this, asses and layout are rendered.
Consider an action that makes many requests, for example:
class TimelineController def index @users = User.all @tickets = Ticket.all @attachments = Attachment.all end end
In this case streaming is well suited. This is a common situation for Rails, the page loads longer than usual, because you need to get all the data first.
Let's add streaming:
class TimelineController def index @users = User.all @tickets = Ticket.all @attachments = Attachment.all render stream: true end end
The
render stream: true
method will lazily load all requests and allow them to be executed after the assets and layout have been rendered. Streaming works with templates and only with them (but not with json or xml). This provides a good way to transfer priority to templates based on the type of page and its content.
Add something inside
Streaming changes the way in which the template and layout are rendered, which leads to the logical question: what about the use of instance variables in templates?
After all, since the database has not yet returned an answer at the time of rendering templates, an attempt to access instance variables will result in an error.
Consequently, in order to load attributes such as title and meta, you need to use
content_for
instead of the usual
yield
. However,
yield
will still work for body.
Previously, our method looked something like this:
<%= yield :title %>
Now it will look like this:
<%= content_for :title, "My Awesome Title" %>
Getting Alive with the Live API
Live is a special module included in ActionController. It allows Rails to explicitly open and close streams. Let's write a simple application and see how it works and how to access the stream from the outside.
Since we work in the context of streaming and parallelism, WEBrick is not a friend to us here. We will use Puma for it can work with threads.
Add the cougar to the Gemfile and run the bundle.
gem "puma"
:~/testapp$ bundle install
Puma integrates well with Rails, so if you now run
rails s
, Puma will run on the same port as WEBRick.
:~/testapp$ rails s => Booting Puma => Rails 4.0.0 application starting in development on http://0.0.0.0:3000 => Run `rails server -h` for more startup options => Ctrl-C to shutdown server Puma 2.3.0 starting... * Min threads: 0, max threads: 16 * Environment: development * Listening on tcp://0.0.0.0:3000
Let's quickly generate a controller to send messages.
:~/testapp$ rails g controller messaging
And add a simple method for streaming them.
class MessagingController < ApplicationController include ActionController::Live def send_message response.headers['Content-Type'] = 'text/event-stream' 10.times { response.stream.write "This is a test Message\n" sleep 1 } response.stream.close end end
Add a route in routes.rb:
get 'messaging' => 'messaging#send_message'
Now we can access stream for example using curl:
:~/testapp$ curl -i http://localhost:3000/messaging HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-UA-Compatible: chrome=1 Content-Type: text/event-stream Cache-Control: no-cache Set-Cookie: request_method=GET; path=/ X-Request-Id: 68c6b7c7-4f5f-46cc-9923-95778033eee7 X-Runtime: 0.846080 Transfer-Encoding: chunked This is a test message This is a test message This is a test message This is a test message
Each time the
send_message
method is
send_message
, Puma creates a new stream, in which it streams the data for an individual client. By default, Puma is configured to handle up to 16 parallel threads, which means 16 simultaneously connected clients. Of course, this number can be increased, but this will slightly increase the memory consumption.
Let's create a form and see if we can send any data to the view:
def send_message response.headers['Content-Type'] = 'text/event-stream' 10.times { response.stream.write "
We make a form to send data to the stream:
<%= form_tag messaging_path, :method => 'get' do %> <%= text_field_tag :message, params[:message] %> <%= submit_tag "Post Message" %> <% end %>
And configure the route:
get 'messaging' => 'messaging#send_message', :as => 'messaging'
As soon as you enter the message and click “Post Message”, the browser will receive a streaming response in the form of a downloadable text file that contains your message, logged 10 times.

Here, however, the stream does not know where to send the data and in what format, so he writes them to a text file on the server.
You can also check work with curl by passing parameters:
:~/testapp$ curl -i http://localhost:3000/messaging?message="awesome" HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-UA-Compatible: chrome=1 Content-Type: text/event-stream Cache-Control: no-cache Set-Cookie: request_method=GET; path=/ X-Request-Id: 382bbf75-7d32-47c4-a767-576ec59cc364 X-Runtime: 0.055470 Transfer-Encoding: chunked awesome awesome
Server Side Events
HTML5 provides a method called Server Side Events (SSE). SSE is a method available to the browser that recognizes and triggers events each time the server sends data.
We can use SSE in conjunction with the Live API to establish a two-way server connection with the client.
By default, Rails provides only one-way communication — it allows you to stream data to a client as soon as it becomes available. However, if we add SSE, we can use the events and responses in two-way mode.
A simple SSE looks like this:
require 'json' module ServerSide class SSE def initialize io @io = io end def write object, options = {} options.each do |k,v| @io.write "
This module receives an I / O stream object in a hash and converts it into a key-value pair so that it can be easily read, stored and sent back in JSON format.
Now we can wrap our stream object in SSE. First, we connect the SSE module to the controller. Now the opening and closing of the stream is regulated by the SSE module. Also, if you do not complete it explicitly, the loop will repeat indefinitely and the connection will always be open, so we will add a
ensure
condition to make sure that the stream is closed.
require 'server_side/sse' class MessagingController < ApplicationController include ActionController::Live def stream response.headers['Content-Type'] = 'text/event-stream' sse = ServerSide::SSE.new(response.stream) begin loop do sse.write({ :message => "
This code will give an answer like this:
:~/testapp$ curl -i http://localhost:3000/messaging?message="awesome" HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-UA-Compatible: chrome=1 Content-Type: text/event-stream Cache-Control: no-cache Set-Cookie: request_method=GET; path=/ X-Request-Id: b922a2eb-9358-429b-b1bb-015421ab8526 X-Runtime: 0.067414 Transfer-Encoding: chunked data: {:message=>"awesome"} data: {:message=>"awesome"}
Underwater rocks
Be careful, there are a couple of pitfalls (where do without them):
- All streams must be closed explicitly, otherwise they will always be open.
- You must make sure that your code is thread-safe, since the controller always spawns a new thread when the method starts.
- After the first portion of the response, heders cannot be changed in
write
or close
Conclusion
This is an opportunity that many people have been looking for in Rails for a long time, because it can significantly increase application performance (streaming templates) and seriously compete with node.js (Live).
Some are already conducting benchmarks, comparing, but I think that this is only the very beginning and time must pass (read a few releases) for this feature to mature, so to speak. Now it is a good start and an exciting opportunity to try everything in action.
PS: this is my first translation experience, I will be very grateful for the comments in PM. Thank.
The original is
here .