# frozen_string_literal: truerequire"rack/chunked"moduleActionController# :nodoc:# Allows views to be streamed back to the client as they are rendered.## By default, Rails renders views by first rendering the template# and then the layout. The response is sent to the client after the whole# template is rendered, all queries are made, and the layout is processed.## Streaming inverts the rendering flow by rendering the layout first and# streaming each part of the layout as they are processed. This allows the# header of the HTML (which is usually in the layout) to be streamed back# to client very quickly, allowing JavaScripts and stylesheets to be loaded# earlier than usual.## This approach was introduced in Rails 3.1 and is still improving. Several# Rack middlewares may not work and you need to be careful when streaming.# Those points are going to be addressed soon.## In order to use streaming, you will need to use a Ruby version that# supports fibers (fibers are supported since version 1.9.2 of the main# Ruby implementation).## Streaming can be added to a given template easily, all you need to do is# to pass the +:stream+ option.## class PostsController# def index# @posts = Post.all# render stream: true# end# end## == When to use streaming## Streaming may be considered to be overkill for lightweight actions like# +new+ or +edit+. The real benefit of streaming is on expensive actions# that, for example, do a lot of queries on the database.## In such actions, you want to delay queries execution as much as you can.# For example, imagine the following +dashboard+ action:## def dashboard# @posts = Post.all# @pages = Page.all# @articles = Article.all# end## Most of the queries here are happening in the controller. In order to benefit# from streaming you would want to rewrite it as:## def dashboard# # Allow lazy execution of the queries# @posts = Post.all# @pages = Page.all# @articles = Article.all# render stream: true# end## Notice that +:stream+ only works with templates. Rendering +:json+# or +:xml+ with +:stream+ won't work.## == Communication between layout and template## When streaming, rendering happens top-down instead of inside-out.# Rails starts with the layout, and the template is rendered later,# when its +yield+ is reached.## This means that, if your application currently relies on instance# variables set in the template to be used in the layout, they won't# work once you move to streaming. The proper way to communicate# between layout and template, regardless of whether you use streaming# or not, is by using +content_for+, +provide+, and +yield+.## Take 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>## You would use +content_for+ in your template 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 <code>yield :title</code> 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 purpose, you can use# a 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+.## == Headers, cookies, session, and flash## When streaming, the HTTP headers are sent to the client right before# it renders the first line. This means that, modifying headers, cookies,# session or flash after the template starts rendering will not propagate# to the client.## == Middlewares## Middlewares that need to manipulate the body won't work with streaming.# You should disable those middlewares whenever streaming in development# or production. For instance, <tt>Rack::Bug</tt> won't work when streaming as it# needs to inject contents in the HTML body.## Also <tt>Rack::Cache</tt> won't work with streaming as it does not support# streaming bodies yet. Whenever streaming Cache-Control is automatically# set to "no-cache".## == Errors## When it comes to streaming, exceptions get a bit more complicated. This# happens because part of the template was already rendered and streamed to# the client, making it impossible to render a whole exception page.## Currently, when an exception happens in development or production, Rails# will automatically stream to the client:## "><script>window.location = "/500.html"</script></html>## The first two characters (">) are required in case the exception happens# while rendering attributes for a given tag. You can check the real cause# for the exception in your logger.## == Web server support## Not all web servers support streaming out-of-the-box. You need to check# the instructions for each of them.## ==== Unicorn## Unicorn supports streaming but it needs to be configured. For this, you# need to create a config file as follow:## # unicorn.config.rb# listen 3000, tcp_nopush: false## And use it on initialization:## unicorn_rails --config-file unicorn.config.rb## You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.# Please check its documentation for more information: https://bogomips.org/unicorn/Unicorn/Configurator.html#method-i-listen## If you are using Unicorn with NGINX, you may need to tweak NGINX.# Streaming should work out of the box on Rainbows.## ==== Passenger## To be described.#moduleStreamingprivate# Set proper cache control and transfer encoding when streamingdef_process_options(options)superifoptions[:stream]ifrequest.version=="HTTP/1.0"options.delete(:stream)elseheaders["Cache-Control"]||="no-cache"headers["Transfer-Encoding"]="chunked"headers.delete("Content-Length")endendend# Call render_body if we are streaming instead of usual +render+.def_render_template(options)ifoptions.delete(:stream)Rack::Chunked::Body.newview_renderer.render_body(view_context,options)elsesuperendendendend