我想在Rails中“伪造”一个404页面。在PHP中,我只会发送一个带有错误代码的报头:

header("HTTP/1.0 404 Not Found");

Rails如何做到这一点?


当前回答

如果希望以不同的方式处理不同的404,可以考虑在控制器中捕获它们。这将允许你做一些事情,如跟踪不同用户组生成的404数量,让支持人员与用户交互,找出哪里出了问题/用户体验的哪一部分可能需要调整,进行A/B测试等。

我在这里将基本逻辑放在ApplicationController中,但它也可以放在更特定的控制器中,以便只针对一个控制器拥有特殊的逻辑。

我使用ENV['RESCUE_404']的if的原因是,这样我就可以单独测试AR::RecordNotFound的提升。在测试中,我可以将这个ENV变量设置为false,并且我的rescue_from不会触发。这样我就可以从条件404逻辑中分离出来测试引发。

class ApplicationController < ActionController::Base

  rescue_from ActiveRecord::RecordNotFound, with: :conditional_404_redirect if ENV['RESCUE_404']

private

  def conditional_404_redirect
    track_404(@current_user)
    if @current_user.present?
      redirect_to_user_home          
    else
      redirect_to_front
    end
  end

end

其他回答

这些会帮助你……

应用程序控制器

class ApplicationController < ActionController::Base
  protect_from_forgery
  unless Rails.application.config.consider_all_requests_local             
    rescue_from ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception }
  end

  private
    def render_error(status, exception)
      Rails.logger.error status.to_s + " " + exception.message.to_s
      Rails.logger.error exception.backtrace.join("\n") 
      respond_to do |format|
        format.html { render template: "errors/error_#{status}",status: status }
        format.all { render nothing: true, status: status }
      end
    end
end

错误的控制器

class ErrorsController < ApplicationController
  def error_404
    @not_found_path = params[:not_found]
  end
end

视图/错误/error_404.html.haml

.site
  .services-page 
    .error-template
      %h1
        Oops!
      %h2
        404 Not Found
      .error-details
        Sorry, an error has occured, Requested page not found!
        You tried to access '#{@not_found_path}', which is not a valid page.
      .error-actions
        %a.button_simple_orange.btn.btn-primary.btn-lg{href: root_path}
          %span.glyphicon.glyphicon-home
          Take Me Home

不要自己渲染404页面,没有理由;Rails已经内置了这个功能。如果你想显示一个404页面,在ApplicationController中创建一个render_404方法(或者我称之为not_found),如下所示:

def not_found
  raise ActionController::RoutingError.new('Not Found')
end

Rails还以同样的方式处理AbstractController::ActionNotFound和ActiveRecord::RecordNotFound。

这样做有两个好处:

1)它使用Rails内置的rescue_from处理程序来呈现404页面 2)它会中断代码的执行,让你做一些不错的事情,比如:

  user = User.find_by_email(params[:email]) or not_found
  user.do_something!

而不用写丑陋的条件语句。

作为奖励,它在测试中也非常容易处理。例如,在rspec集成测试中:

# RSpec 1

lambda {
  visit '/something/you/want/to/404'
}.should raise_error(ActionController::RoutingError)

# RSpec 2+

expect {
  get '/something/you/want/to/404'
}.to raise_error(ActionController::RoutingError)

和小型试验:

assert_raises(ActionController::RoutingError) do 
  get '/something/you/want/to/404'
end

OR从控制器动作中找不到的Rails渲染404中引用更多信息

Raising ActionController::RoutingError('not found')对我来说总是感觉有点奇怪——在未经过身份验证的用户的情况下,这个错误并不反映现实——路由被找到了,用户只是没有经过身份验证。

我偶然发现config。action_dispatch。我认为在某些情况下,这是一个更优雅的解决方案的问题:

# application.rb
config.action_dispatch.rescue_responses = {
  'UnauthenticatedError' => :not_found
}

# my_controller.rb
before_action :verify_user_authentication

def verify_user_authentication
  raise UnauthenticatedError if !user_authenticated?
end

这种方法的好处在于:

它像普通的ActionController::RoutingError一样挂钩到现有的错误处理中间件中,但是在开发环境中你会得到一个更有意义的错误消息 它将正确地将状态设置为您在rescue_responses散列中指定的任何状态(在本例中为404 - not_found) 您不必编写一个需要在任何地方都可用的not_found方法。

你也可以使用渲染文件:

render file: "#{Rails.root}/public/404.html", layout: false, status: 404

你可以选择是否使用该布局。

另一种选择是使用Exceptions来控制它:

raise ActiveRecord::RecordNotFound, "Record not found."

如果希望以不同的方式处理不同的404,可以考虑在控制器中捕获它们。这将允许你做一些事情,如跟踪不同用户组生成的404数量,让支持人员与用户交互,找出哪里出了问题/用户体验的哪一部分可能需要调整,进行A/B测试等。

我在这里将基本逻辑放在ApplicationController中,但它也可以放在更特定的控制器中,以便只针对一个控制器拥有特殊的逻辑。

我使用ENV['RESCUE_404']的if的原因是,这样我就可以单独测试AR::RecordNotFound的提升。在测试中,我可以将这个ENV变量设置为false,并且我的rescue_from不会触发。这样我就可以从条件404逻辑中分离出来测试引发。

class ApplicationController < ActionController::Base

  rescue_from ActiveRecord::RecordNotFound, with: :conditional_404_redirect if ENV['RESCUE_404']

private

  def conditional_404_redirect
    track_404(@current_user)
    if @current_user.present?
      redirect_to_user_home          
    else
      redirect_to_front
    end
  end

end