提问



我想在Rails中伪造一个404页面。在PHP中,我只是发送带有错误代码的标题:


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


如何用Rails完成?

最佳参考


不要自己渲染404,没有理由; Rails已经内置了这个功能。如果你想显示404页面,在ApplicationController中创建一个render_404方法(或not_found,就像我调用它一样):


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


Rails也以同样的方式处理AbstractController::ActionNotFoundActiveRecord::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

其它参考1


HTTP 404状态



要返回404标头,只需使用:status选项进行渲染方法。


def action
  # here the code

  render :status => 404
end


如果要渲染标准404页面,可以在方法中提取要素。


def render_404
  respond_to do |format|
    format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found }
    format.xml  { head :not_found }
    format.any  { head :not_found }
  end
end


并在你的行动中称呼它


def action
  # here the code

  render_404
end


如果您希望操作呈现错误页面并停止,只需使用return语句。


def action
  render_404 and return if params[:something].blank?

  # here the code that will never be executed
end


ActiveRecord和HTTP 404



还记得Rails拯救了一些ActiveRecord错误,例如显示404错误页面的ActiveRecord::RecordNotFound


这意味着你不需要自己拯救这个动作


def show
  user = User.find(params[:id])
end


当用户不存在时,User.find会提出ActiveRecord::RecordNotFound。这是一个非常强大的功能。请看下面的代码


def show
  user = User.find_by_email(params[:email]) or raise("not found")
  # ...
end


您可以通过委托Rails检查来简化它。只需使用爆炸版。


def show
  user = User.find_by_email!(params[:email])
  # ...
end

其它参考2


Steven Soroka提交的新选择答案很接近,但并不完整。测试本身隐藏了这样一个事实,即它不会返回真正的404 - 它返回200的状态 - 成功。原始答案更接近,但试图渲染布局,好像没有发生故障。这修复了一切:


render :text => 'Not Found', :status => '404'


这是我的一个典型测试集,我希望返回404,使用RSpec和Shoulda匹配器:


describe "user view" do
  before do
    get :show, :id => 'nonsense'
  end

  it { should_not assign_to :user }

  it { should respond_with :not_found }
  it { should respond_with_content_type :html }

  it { should_not render_template :show }
  it { should_not render_with_layout }

  it { should_not set_the_flash }
end


这个健康的偏执让我发现内容类型不匹配,当其他一切看起来很好:)我检查所有这些元素:分配变量,响应代码,响应内容类型,模板渲染,布局呈现,Flash消息。


我将跳过内容类型检查严格html的应用程序...有时候。毕竟,怀疑论者检查所有抽屉:)


http://dilbert.com/strips/comic/1998-01-20/[38]


仅供参考:我不建议测试控制器中发生的事情,即should_raise。你关心的是输出。我上面的测试允许我尝试各种解决方案,无论解决方案是什么,测试都保持不变提出异常,特殊渲染等

其它参考3


您还可以使用渲染文件:


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


在哪里可以选择使用布局。


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


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

其它参考4


由于错误处理程序被转移到中间件(参见github问题),所选答案在Rails 3.1+中不起作用。[39]


这是我找到的解决方案,我非常满意。


ApplicationController中:


  unless Rails.application.config.consider_all_requests_local
    rescue_from Exception, with: :handle_exception
  end

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

  def handle_exception(exception=nil)
    if exception
      logger = Logger.new(STDOUT)
      logger.debug "Exception Message: #{exception.message} \n"
      logger.debug "Exception Class: #{exception.class} \n"
      logger.debug "Exception Backtrace: \n"
      logger.debug exception.backtrace.join("\n")
      if [ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction].include?(exception.class)
        return render_404
      else
        return render_500
      end
    end
  end

  def render_404
    respond_to do |format|
      format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 }
      format.all { render nothing: true, status: 404 }
    end
  end

  def render_500
    respond_to do |format|
      format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 }
      format.all { render nothing: true, status: 500}
    end
  end


application.rb中:


config.after_initialize do |app|
  app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end


在我的资源(显示,编辑,更新,删除)中:


@resource = Resource.find(params[:id]) or not_found


这当然可以改进,但至少,我对not_found和internal_error有不同的看法,而没有覆盖核心Rails函数。

其它参考5


这些会帮助你......


应用程序控制器


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

其它参考6


<%= render file: 'public/404', status: 404, formats: [:html] %>


只需将此添加到要呈现到404错误页面的页面即可完成。

其它参考7


要测试错误处理,您可以执行以下操作:


feature ErrorHandling do
  before do
    Rails.application.config.consider_all_requests_local = false
    Rails.application.config.action_dispatch.show_exceptions = true
  end

  scenario 'renders not_found template' do
    visit '/blah'
    expect(page).to have_content "The page you were looking for doesn't exist."
  end
end

其它参考8


如果要以不同方式处理不同的404,请考虑在控制器中捕获它们。这将允许您执行诸如跟踪不同用户组生成的404的数量,支持与用户交互以找出哪些内容出错/用户体验可能需要调整的部分,执行A/B测试等操作。


我在这里将基本逻辑放在ApplicationController中,但它也可以放在更具体的控制器中,只为一个控制器提供特殊逻辑。


我使用带有ENV [[RESCUE_404]]的if的原因是,我可以单独测试AR :: RecordNotFound的提升。在测试中,我可以将此ENV var设置为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