Skip to content
This repository
branch: master
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 222 lines (217 sloc) 7.695 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
require 'rack/chunked'

module ActionController #:nodoc:
  # Allows views to be streamed back to the client as they are rendered.
  #
  # The default way Rails renders views is 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 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+.
  #
  # == 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: http://unicorn.bogomips.org/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.
  #
  module Streaming
    extend ActiveSupport::Concern

    protected

      # Set proper cache control and transfer encoding when streaming
      def _process_options(options) #:nodoc:
        super
        if options[:stream]
          if env["HTTP_VERSION"] == "HTTP/1.0"
            options.delete(:stream)
          else
            headers["Cache-Control"] ||= "no-cache"
            headers["Transfer-Encoding"] = "chunked"
            headers.delete("Content-Length")
          end
        end
      end

      # Call render_body if we are streaming instead of usual +render+.
      def _render_template(options) #:nodoc:
        if options.delete(:stream)
          Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
        else
          super
        end
      end
  end
end
Something went wrong with that request. Please try again.