From 8dbee3aba6920edcae20abf3af7f803e528f9ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 14:50:42 +0200 Subject: [PATCH] Streaming docs. --- .../lib/action_controller/metal/streaming.rb | 202 +++++++++++++++++- actionpack/lib/action_view/base.rb | 1 - .../renderer/streaming_template_renderer.rb | 41 +--- 3 files changed, 202 insertions(+), 42 deletions(-) diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 5b8111f30d..0dd847f967 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -2,7 +2,207 @@ require 'active_support/core_ext/file/path' require 'rack/chunked' module ActionController #:nodoc: - # Methods for sending streaming templates back to the client. + # Allow 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 first chunk of response is sent to the client + # just 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). + # + # == Examples + # + # Streaming can be added to a controller easily, all you need to do is + # call stream at the controller class: + # + # class PostsController + # stream + # end + # + # The +stream+ method accepts the same options as +before_filter+ and friends: + # + # class PostsController + # stream :only => :index + # end + # + # You can also selectively turn on streaming for specific actions: + # + # class PostsController + # def index + # @post = Post.scoped + # render :stream => true + # end + # end + # + # == When to use streaming + # + # Streaming may be considering an overkill for common actions like + # new or edit. The real benefit of streaming is on expensive actions + # that, for example, does 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 + # most of streaming, you would want to rewrite it as: + # + # def dashboard + # # Allow lazily execution of the query + # @posts = Post.scoped + # @pages = Page.scoped + # @articles = Article.scoped + # render :stream => true + # end + # + # == Communication between layout and template + # + # When streaming, the layout is rendered first than the template. + # This means that, if your application currently rely on 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 if 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: + # + # + # <%= yield :title %> + # <%= yield %> + # + # + # You would use +content_for+ in your template to specify the title: + # + # <%= content_for :title, "Main" %> + # Hello + # + # And the final result would be: + # + # + # Main + # Hello + # + # + # 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: + # + # + # Main page + # Hello + # + # + # 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 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 below, using +provide+: + # + # <%= provide :title, "Main" %> + # Hello + # <%= content_for :title, " page" %> + # + # Has as final result: + # + # + # Main + # Hello + # + # + # That said, when streaming, you need to properly check your templates + # and chose 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 start rendering will not propagate + # to the client. + # + # If you try to modify cookies, session or flash, a ClosedError will be + # raised, showing those objects are closed for modification. + # + # == 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, Rack::Bug won't work when streaming as it + # needs to inject contents in the HTML body. + # + # Also Rack::Cache won't work with streaming as it does not support + # streaming bodies yet. So, 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: + # + # "> + # + # 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 :tcp_nodelay. 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 diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 1890259ca0..baa2c5729f 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -139,7 +139,6 @@ module ActionView #:nodoc: # How to complete the streaming when an exception occurs. # This is our best guess: first try to close the attribute, then the tag. - # Currently this is private API and may be changed at *any* time. cattr_accessor :streaming_completion_on_exception @@streaming_completion_on_exception = %(">) diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb index d987bc122a..1ccf5a8ddb 100644 --- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb @@ -4,50 +4,11 @@ require 'fiber' if defined?(Fiber) module ActionView - # Consider the following layout: - # - # <%= yield :header %> - # 2 - # <%= yield %> - # 5 - # <%= yield :footer %> - # - # And template: - # - # <%= provide :header, "1" %> - # 3 - # 4 - # <%= provide :footer, "6" %> - # - # It will stream: - # - # "1\n", "2\n", "3\n4\n", "5\n", "6\n" - # - # Notice that once you <%= yield %>, it will render the whole template - # before streaming again. In the future, we can also support streaming - # from the template and not only the layout. - # - # Also, notice we use +provide+ instead of +content_for+, as +provide+ - # gives the control back to the layout as soon as it is called. - # With +content_for+, it would render all the template to find all - # +content_for+ calls. For instance, consider this layout: - # - # <%= yield :header %> - # - # With this template: - # - # <%= content_for :header, "1" %> - # <%= provide :header, "2" %> - # <%= provide :header, "3" %> - # - # It will return "12\n" because +content_for+ continues rendering the - # template but it is returns back to the layout as soon as it sees the - # first +provide+. - # # == TODO # # * Support streaming from child templates, partials and so on. # * Integrate exceptions with exceptron + # * Rack::Cache needs to support streaming bodies class StreamingTemplateRenderer < TemplateRenderer #:nodoc: # A valid Rack::Body (i.e. it responds to each). # It is initialized with a block that, when called, starts