mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Streaming docs.
This commit is contained in:
parent
46611a995d
commit
8dbee3aba6
3 changed files with 202 additions and 42 deletions
|
@ -2,7 +2,207 @@ require 'active_support/core_ext/file/path'
|
||||||
require 'rack/chunked'
|
require 'rack/chunked'
|
||||||
|
|
||||||
module ActionController #:nodoc:
|
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:
|
||||||
|
#
|
||||||
|
# <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 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:
|
||||||
|
#
|
||||||
|
# <html>
|
||||||
|
# <head><title>Main</title></head>
|
||||||
|
# <body>Hello</body>
|
||||||
|
# </html>
|
||||||
|
#
|
||||||
|
# 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:
|
||||||
|
#
|
||||||
|
# "><script type="text/javascript">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 :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
|
module Streaming
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,6 @@ module ActionView #:nodoc:
|
||||||
|
|
||||||
# How to complete the streaming when an exception occurs.
|
# How to complete the streaming when an exception occurs.
|
||||||
# This is our best guess: first try to close the attribute, then the tag.
|
# 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
|
cattr_accessor :streaming_completion_on_exception
|
||||||
@@streaming_completion_on_exception = %("><script type="text/javascript">window.location = "/500.html"</script></html>)
|
@@streaming_completion_on_exception = %("><script type="text/javascript">window.location = "/500.html"</script></html>)
|
||||||
|
|
||||||
|
|
|
@ -4,50 +4,11 @@
|
||||||
require 'fiber' if defined?(Fiber)
|
require 'fiber' if defined?(Fiber)
|
||||||
|
|
||||||
module ActionView
|
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
|
# == TODO
|
||||||
#
|
#
|
||||||
# * Support streaming from child templates, partials and so on.
|
# * Support streaming from child templates, partials and so on.
|
||||||
# * Integrate exceptions with exceptron
|
# * Integrate exceptions with exceptron
|
||||||
|
# * Rack::Cache needs to support streaming bodies
|
||||||
class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
|
class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
|
||||||
# A valid Rack::Body (i.e. it responds to each).
|
# A valid Rack::Body (i.e. it responds to each).
|
||||||
# It is initialized with a block that, when called, starts
|
# It is initialized with a block that, when called, starts
|
||||||
|
|
Loading…
Reference in a new issue