rails 4:404,500的自定义错误页面以及来自哪里的默认500错误消息?

Rub*_*tic 54 error-handling ruby-on-rails-4

目前正在制作中获取此文字:

500 Internal Server Error
If you are the administrator of this website, then please read this web application's     
log file and/or the web server's log file to find out what went wrong.
Run Code Online (Sandbox Code Playgroud)

该页面中没有任何HTML.

这段代码在哪里?我没有公开/ 500.html或这方面的任何事情.

在我的路线中,我有:

  get "/404", :to => "errors#error_404"
  get "/422", :to => "errors#error_404"
  get "/500", :to => "errors#error_500"
  get "/505", :to => "errors#error_505"
Run Code Online (Sandbox Code Playgroud)

ErrorsController:

class ErrorsController < ApplicationController

  def sub_layout
    "left"
  end

  def error_404
    render :status => 404, :formats => [:html], :layout => "white", :sub_layout => "left"
  end

  def error_422
    render :status => 422, :formats => [:html], :layout => "white", :sub_layout => "left"
  end

  def error_500
    render :status => 500, :formats => [:html], :layout => "white", :sub_layout => "left"
  end

  def error_505
    render :status => 505, :formats => [:html], :layout => "white", :sub_layout => "left"
  end

end
Run Code Online (Sandbox Code Playgroud)

如何让它总是加载我的自定义错误?在一些错误上,它只是抛出来自rails core的2行文本,我希望它每次都能获取我自定义样式的错误页面!怎么样?谢谢!

Ric*_*eck 68

更新2018年

我们的exception_handler宝石现在被认为是最受欢迎的(Rails自定义错误页面)↴

在此输入图像描述 在此输入图像描述


这个怎么运作

处理所有Rails异常config.exceptions_app.这是在config/application.rbconfig/environments/*.rb文件中分配的- 它需要是一个回调:

在此输入图像描述

每当Rails遇到错误时,它就会调用ShowExceptions中间件.这会调用exception_app并将整个request(包括exception)发送到exceptions_app:

中间件驱动的异常

exceptions_app需要提供回复.如果没有,failsafe则加载:

  # show_exceptions.rb#L38
  def render_exception(env, exception)
    wrapper = ExceptionWrapper.new(env, exception)
    status  = wrapper.status_code
    env["action_dispatch.exception"] = wrapper.exception
    env["PATH_INFO"] = "/#{status}"
    response = @exceptions_app.call(request.env) # => exceptions_app callback
    response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
  rescue Exception => failsafe_error # => raised if exceptions_app false
    $stderr.puts "Error during failsafe response: #{failsafe_error}\n  #{failsafe_error.backtrace * "\n  "}"
    FAILSAFE_RESPONSE
  end
Run Code Online (Sandbox Code Playgroud)

failsafe存储为FAILSAFE_RESPONSE在顶部ShowExceptions.


自定义错误页面

如果要创建自定义错误页面,则需要将自己的回调注入config.exceptions_app.这可以在应用程序中完成,也可以使用gem完成:

在此输入图像描述

注意该call方法是如何使用的 - 这就是回调的工作原理.env从Internet接收请求时调用Rails(); 当引发异常时,env传递给exceptions_app.

异常处理的质量取决于您的管理方式env.这个很重要; 引用self.routes没有进行环境迈进.

最好的方法是使用单独的控制器处理异常.这允许您像处理另一个视图一样处理请求,授予对layout其他组件(model/ email)的访问权限.

-

两种方法可以处理异常:

  1. 覆盖404/ 500路线
  2. 调用控制器

我们的宝石是围绕我们设计的controller- 每次exception提升时都会被调用.这样可以完全控制异常过程,允许100%品牌布局:

在此输入图像描述

ExceptionHandler 现在是Rails的主要生产自定义错误页面gem.

维护超过3年,它是Rails最简单,最强大的异常宝石.它在Rails 5上100%工作,已经下载超过70,000次.


宝石

最新版本0.8.0.0有以下更新:

  • 自定义例外
  • 异常"映射"(选择要处理的异常)
  • 邮件通知
  • 模型后端
  • 链轮4+集成
  • RSpec测试套件
  • 基于区域设置的视图

你可以在这里阅读更多.


管理Rails的例外

如果您对gem不感兴趣,请让我解释一下这个过程:

使用config.exceptions_app回调处理所有Rails异常.这是在config/application.rbconfig/environments/*.rb文件中分配的- 它需要是一个回调:

在此输入图像描述

每当应用程序引发异常时,ShowExceptions都会调用中间件.此中间件将异常构建到request转发中并将其转发给config.exceptions_app回调.

默认情况下,config.exceptions_app指向路由.这就是Rails附带的原因404.html,500.html并且422.htmlpublic文件夹中.

如果要创建自定义异常页面,则需要覆盖config.exceptions_app回调 - 将错误请求传递给适当的处理程序,无论是a controller还是route:

[[中间件]]

有效管理它的两种方法是将错误的请求发送到路由,或者调用控制器.

最简单 - 也是最常见 - 的方法是将请求转发给路由; 遗憾的是,这会忽略该请求并阻止您正确地详细说明异常.

最好的方法是调用一个单独的控制器.这将允许您传递整个请求,允许您保存,通过电子邮件发送或执行许多其他操作.

-

400/500错误

Rails 只能响应HTTP有效错误.

虽然应用程序的异常可能不同,但返回的状态代码40x50x.这与HTTP规范一致,并在此处概述↴

在此输入图像描述

这意味着无论您使用/构建什么异常处理解决方案,Rails都需要向浏览器返回错误40x50x错误.

换句话说,自定义错误页面与异常类型几乎没有关系- 更多的是如何捕获和提供浏览器响应.

默认情况下,Rails会用这个404.html,422.html500.html文件的public文件夹.如果您想自己处理异常流程,则需要删除这些文件并将错误的请求传递给您自己的exceptions_app回调.

这可以通过routes或使用controller(我现在解释)来完成:


1.路线

最简单的方法是让路由处理它.

此方法臃肿,需要使用多个操作.管理答复也很困难.

本教程解释:

在此输入图像描述

这显示了如何直接替换exceptions_app路由:

# config/application.rb
config.exceptions_app = self.routes
Run Code Online (Sandbox Code Playgroud)

这是我的代码(Ruby 2.0.0,Rails 4.0):

应用程序配置

#config/application.rb
config.exceptions_app = self.routes
Run Code Online (Sandbox Code Playgroud)

路线

#config/routes.rb
if Rails.env.production?
   get '404', to: 'application#page_not_found'
   get '422', to: 'application#server_error'
   get '500', to: 'application#server_error'
end
Run Code Online (Sandbox Code Playgroud)

应用控制器

#controllers/application_controller.rb
def page_not_found
    respond_to do |format|
      format.html { render template: 'errors/not_found_error', layout: 'layouts/application', status: 404 }
      format.all  { render nothing: true, status: 404 }
    end
  end

  def server_error
    respond_to do |format|
      format.html { render template: 'errors/internal_server_error', layout: 'layouts/error', status: 500 }
      format.all  { render nothing: true, status: 500}
    end
  end
Run Code Online (Sandbox Code Playgroud)

错误布局(完全静态 - 仅用于服务器错误)

#views/layouts/error.html.erb
<!DOCTYPE html>
<html>
<head>
  <title><%= action_name.titleize %> :: <%= site_name %></title>
  <%= csrf_meta_tags %>
  <style>
    body {
        background: #fff;
        font-family: Helvetica, Arial, Sans-Serif;
        font-size: 14px;
    }
    .error_container {
        display: block;
        margin: auto;
        margin: 10% auto 0 auto;
        width: 40%;
    }
    .error_container .error {
        display: block; 
        text-align: center;
    }
    .error_container .error img {
        display: block;
        margin: 0 auto 25px auto;
    }
    .error_container .message strong {
        font-weight: bold;
        color: #f00;
    }
  </style>
</head>
<body>

    <div class="error_container">
        <%= yield %>
    </div>

</body>
</html>
Run Code Online (Sandbox Code Playgroud)

错误视图

#views/errors/not_found_error.html.erb    
<div class="error">
    <h2>Sorry, this page has moved, or doesn't exist!</h2>
</div>


#views/errors/internal_server_error.html.erb
<div class="error">
    <div class="message">
        <strong>Error!</strong>
        We're sorry, but our server is experiencing problems :(
    </div>
</div>
Run Code Online (Sandbox Code Playgroud)

虽然许多人更喜欢"路线"方法,但它既不高效也不模块化.实际上,如果您的应用程序具有任何面向对象的外观,您很快就会将其视为黑客攻击.

一个更响亮的方法是使用一个定制控制器赶上纯例外.这样,您可以根据应用程序的整体结构构建流程:


2.控制器

另一种选择是将所有请求路由到控制器.

这是非常强大的,因为它允许您接受请求(异常)并将其传递给视图,同时在后端管理它.这将允许将其保存到数据库中.

这个要点显示了如何:

在此输入图像描述

这意味着我们可以挂钩到中间件并将整个请求传递给控制器​​.

如果此控制器由模型和视图支持,我们可以将其提取到gem(这就是我们所做的).如果您想手动完成,请按以下步骤操作:

-

配置

这种方法的优点在于它可以直接挂钩config.exceptions_app.这意味着可以原生处理任何异常,从而提高效率.为了确保这一点,您需要将以下代码放入config/application.rb(exceptions_app仅适用于production- development显示错误):

#config/application.rb
config.exceptions_app = ->(env) { ExceptionController.action(:show).call(env) }
Run Code Online (Sandbox Code Playgroud)

要进行测试,您可以将"本地"请求设置为false:

#config/environments/development.rb
config.consider_all_requests_local  = false # true
Run Code Online (Sandbox Code Playgroud)

-

调节器

下一步是添加一个exception控制器.虽然可以处理这个问题application_controller,但提取它自己要好得多.注意来自application.rb- 的调用ExceptionController.action(:show):

#app/controllers/exception_controller.rb
class ExceptionController < ApplicationController

  #Response
  respond_to :html, :xml, :json

  #Dependencies
  before_action :status

  #Layout
  layout :layout_status

  ####################
  #      Action      #
  ####################

  #Show
  def show
    respond_with status: @status
  end

  ####################
  #   Dependencies   #
  ####################

  protected

  #Info
  def status
    @exception  = env['action_dispatch.exception']
    @status     = ActionDispatch::ExceptionWrapper.new(env, @exception).status_code
    @response   = ActionDispatch::ExceptionWrapper.rescue_responses[@exception.class.name]
  end

  #Format
  def details
    @details ||= {}.tap do |h|
      I18n.with_options scope: [:exception, :show, @response], exception_name: @exception.class.name, exception_message: @exception.message do |i18n|
        h[:name]    = i18n.t "#{@exception.class.name.underscore}.title", default: i18n.t(:title, default: @exception.class.name)
        h[:message] = i18n.t "#{@exception.class.name.underscore}.description", default: i18n.t(:description, default: @exception.message)
      end
    end
  end
  helper_method :details

  ####################
  #      Layout      #
  ####################

  private

  #Layout
  def layout_status
    @status.to_s == "404" ? "application" : "error"
  end

end
Run Code Online (Sandbox Code Playgroud)

-

查看

要添加这两个视图以使其正常工作.

第一个是exception/show视图,第二个是视图layouts/error.第一个是给出exception_contoller#show视图,第二个是500内部服务器错误.

#app/views/exception/show.html.erb
<h1><%= details[:name]    %></h1>
<p><%=  details[:message] %></p>


#app/views/layouts/error.html.erb (for 500 internal server errors)
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title>Error</title>
    <style>
      html {
        height: 100%;
        background: #fff;
      }
      body {
        font-family: Helvetica, Arial, Sans-Serif;
        font-size: 14px;
      }
      .error_container {
        display: block;
        margin: auto;
        margin: 10% auto 0 auto;
        width: 40%;
      }
      .error_container .error {
        display: block;
        text-align: center;
      }
      .error_container .error img {
        display: block;
        margin: 0 auto 15px auto;
      }
      .error_container .message > * {
        display: block;
      }
      .error_container .message strong {
        font-weight: bold;
        color: #f00;
      }
    </style>
  </head>
  <body>
    <div class="error_container"><%= yield %></div>
  </body>
</html>
Run Code Online (Sandbox Code Playgroud)

结论

例外不要紧不亚于错误代码.

当Rails引发异常时,它会分配上述HTTP响应代码之一.这些允许您的浏览器确定请求是否成功.

处理异常时,您需要确保能够处理40*错误(通常使用与应用程序其余部分相同的布局)和50*错误(需要自己的布局).

在这两种情况下,您最好使用单独的exception控制器,这将允许您将exception对象作为对象进行管理.

  • 仅供参考,您正在元上讨论 https://meta.stackoverflow.com/questions/406788 (48认同)
  • @RichardPeck很棒的工作,很奇怪你的广告.如何进行自定义<title>消息或至少找不到页面?谢谢 (3认同)

小智 43

您遇到的错误正在被抛出

https://github.com/rails/rails/blob/4-0-stable/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L18-L22

这意味着,您的异常获得的代码本身就会抛出异常.您可以检查日志中的文本:

Error during failsafe response:

确定异常的真正来源,从而解决您的问题.


Dar*_*ide 17

应用程序中的错误页面应尽可能简单.相同的建议涉及他们的渲染 如果您的应用程序返回500 HTTP响应代码,则意味着已经出现问题.并且您可能无法呈现错误页面并将其显示给用户.

理想情况下,错误页面应该是由Web服务器直接提供的纯HTML,而不会触及应用程序服务器.

说到Rails实现这个想法.它基于使用资产管道来预编译HTML静态页面.

首先添加新资产类型(Rails> 4.1):

# config/initializers/assets.rb

Rails.application.config.assets.precompile += %w(404.html 500.html)
Rails.application.config.assets.paths << Rails.root.join('app/assets/html')
Rails.application.config.assets.register_mime_type('text/html', '.html')
Run Code Online (Sandbox Code Playgroud)

如果使用模板引擎(例如slim,haml),请通过初始化程序注册:

# for Slim
Rails.application.assets.register_engine('.slim', Slim::Template)
# for Haml
Rails.application.assets.register_engine('.haml', Tilt::HamlTemplate)
Run Code Online (Sandbox Code Playgroud)

现在,您已准备好使用您喜欢的模板引擎和Rails内置视图助手在app/assets/html目录中创建漂亮的错误页面.

生产技巧

在生产资产管道上,将摘要添加到已编译资产并将文件存储在默认文件夹下(通常是生产服务器上的共享/公共/资产).您可以使用capistrano将错误页面复制到Web服务器根目录:

# config/deploy.rb
# Capistrano 3 only

namespace :deploy do
  desc 'Copy compiled error pages to public'
  task :copy_error_pages do
    on roles(:all) do
      %w(404 500).each do |page|
        page_glob = "#{current_path}/public/#{fetch(:assets_prefix)}/#{page}*.html"
        # copy newest asset
        asset_file = capture :ruby, %Q{-e "print Dir.glob('#{page_glob}').max_by { |file| File.mtime(file) }"}
        if asset_file
          execute :cp, "#{asset_file} #{current_path}/public/#{page}.html"
        else
          error "Error #{page} asset does not exist"
        end
      end
    end
  end
  after :finishing, :copy_error_pages
end
Run Code Online (Sandbox Code Playgroud)

最后一件事.告诉Web服务器将这些文件用于某些HTTP错误代码(示例nginx配置):

error_page 500 502 503 504 /500.html;    
error_page 404 /404.html;
Run Code Online (Sandbox Code Playgroud)

链轮3更新

对于Sprocket 3,您需要这样的东西(使用Rails 5测试):

# config/environments/production.rb
config.assets.configure do |env|
  env.register_transformer 'text/slim', 'text/html', Slim::Template
  env.register_mime_type 'text/slim', extensions: ['.html']
  env.register_engine '.slim', Slim::Template
end

# config/initializers/assets.rb
Rails.application.config.assets.precompile += %w(404.html 500.html)
Rails.application.config.assets.paths << Rails.root.join('app/assets/html')
Run Code Online (Sandbox Code Playgroud)