An HTTP redirect is sent from a server to a client in response to a request. In effect, it says, “I’m done processing this request, and you should go here to see the results.” The redirect response includes a URL that the client should try next along with some status information saying whether this redirection is permanent (status code 301) or temporary (307). Redirects are sometimes used when web pages are reorganized; clients accessing pages in the old locations will get referred to the page’s new home. More commonly, Rails applications use redirects to pass the processing of a request off to some other action。同时,redirect也被用于解决“refresh导致重复提交”问题

trace一下redirect_to (来自rails guide第一篇)

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    binding.trace_tree(html: true, tmp: ['rails', 'redirect_to_article.html']) do
      redirect_to article_path(@article)
    end
  end

  private

  def comment_params
    params.require(:comment).permit(:commenter, :body)
  end
end


从所得调用栈中,发现有多个redirect_to



先看第一个,turbolinks-5.0.1/lib/turbolinks/redirection.rb,它是个module,其redirect_to会调super

module Turbolinks
  module Redirection
    extend ActiveSupport::Concern

    included do
      before_action :set_turbolinks_location_header_from_session if respond_to?(:before_action)
    end

    def redirect_to(url = {}, options = {})
      turbolinks = options.delete(:turbolinks)

      super.tap do
        if turbolinks != false && request.xhr? && !request.get?
          visit_location_with_turbolinks(location, turbolinks)
        else
          if request.headers["Turbolinks-Referrer"]
            store_turbolinks_location_in_session(location)
          end
        end
      end
    end


估计调用栈上其它redirect_to也是这种做法,于是查找下controller的继承链上哪些地方有定义redirect_to

irb(main):003:0> CommentsController.ancestors.select{|klass| klass.instance_methods.include? :redirect_to}
=> [CommentsController, ApplicationController, ActionController::Base, Turbolinks::Redirection, ActionController::Instrumentation, ActionController::Flash, ActionController::Redirecting]
irb(main):004:0> CommentsController.ancestors.select{|klass| klass.instance_methods(false).include? :redirect_to}
=> [Turbolinks::Redirection, ActionController::Instrumentation, ActionController::Flash, ActionController::Redirecting]


与调用栈顺序一致,应该就是不断地super

其中最核心的应该是actionpack-5.0.2/lib/action_controller/metal/redirecting.rb,它所做的就是在controller自身保存status、location、body,以备controller的调用者再挖出来拼接成响应报文

def redirect_to(options = {}, response_status = {}) #:doc:
  raise ActionControllerError.new("Cannot redirect to nil!") unless options
  raise AbstractController::DoubleRenderError if response_body

  self.status        = _extract_redirect_to_status(options, response_status)
  self.location      = _compute_redirect_to_location(request, options)
  self.response_body = "You are being redirected."
end


源码还带有一些用例

# === Examples:
#
#   redirect_to action: "show", id: 5
#   redirect_to post
#   redirect_to "http://www.rubyonrails.org"
#   redirect_to "/images/screenshot.jpg"
#   redirect_to articles_url
#   redirect_to proc { edit_post_url(@post) }


_compute_redirect_to_location能处理各种类型的参数,以便设置LOCATION,令用例得以实现

def _compute_redirect_to_location(request, options) #:nodoc:
  case options
  # The scheme name consist of a letter followed by any combination of
  # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
  # characters; and is terminated by a colon (":").
  # See http://tools.ietf.org/html/rfc3986#section-3.1
  # The protocol relative scheme starts with a double slash "//".
  when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
    options
  when String
    request.protocol + request.host_with_port + options
  when :back
    ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
      `redirect_to :back` is deprecated and will be removed from Rails 5.1.
      Please use `redirect_back(fallback_location: fallback_location)` where
      `fallback_location` represents the location to use if the request has
      no HTTP referer information.
    MESSAGE
    request.headers["Referer"] or raise RedirectBackError
  when Proc
    _compute_redirect_to_location request, options.call
  else
    url_for(options)
  end.delete("\0\r\n")
end


这里面的:back,主要用于form被partial到多个不同页面,提交报错时想返回该页面的情况(不过这时要重现已提交的对象的属性就比较麻烦了)

完整trace如下

redirect_to_article.html