导轨4:404,500自定义错误页面和默认的500错误消息来自哪里?

目前在生产中我得到这个文本:

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. 

该页面中没有任何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" 

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 

如何使它总是加载我的自定义错误? 在一些错误,它只是把这两行文本来自铁轨核心的某处,我希望它每次拾起我的自定义样式的错误页面! 怎么样? 谢谢!

您遇到的错误正在抛出

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

这意味着,你的例外被解救的代码本身就是抛出exception。 你可以检查你的日志文本:

Error during failsafe response:

找出exception来自哪里,从而解决您的问题。

新2017年

我们为此创build了一个gem – exception_handler

在这里输入图像描述 在这里输入图像描述


怎么运行的

所有Railsexception都通过config.exceptions_appcallback进行处理。 这是在config/application.rbconfig/environments/*.rb文件中分配的 – 它需要是一个callback:

在这里输入图像描述

每当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 

failsafe保存为ShowExceptions顶部的ShowExceptions


自定义错误页面

如果你想创build自定义错误页面,你需要将自己的callback注入到config.exceptions_app 。 这可以在应用程序中完成,也可以使用gem来完成:

在这里输入图像描述

注意如何使用call方法 – 这是callback的工作原理。 Rails( env )在从Internet接收请求时被调用; 当引发exception时, env被传递给exceptions_app

你的exception处理的质量将取决于你如何pipe理env 。 这个很重要; 引用self.routes不会带来环境前进。

最好的方法是用一个单独的控制器来处理exception。 这使您可以像处理另一个视图一样处理请求,并授予对layout和其他组件( model / email )的访问权限。

处理exception有两种方法:

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

我们的gem是围绕我们的controllerdevise的 – 每当引发exception调用。 这完全控制了exception过程,允许100%的品牌布局

在这里输入图像描述

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

保持3年以上,这是Rails最简单,最强大的exceptiongem。 它在Rails 5上运行率达到100%,已经下载了7万次以上。


gem

最新版本0.7.0有以下更新:

  • 自定义例外
  • exception“映射”(select要处理的exception)
  • 电子邮件通知
  • 模型后端
  • 链轮4+集成

你可以在这里阅读更多。


pipe理Rails的例外

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

所有Railsexception都通过config.exceptions_appcallback进行处理。 这是在config/application.rbconfig/environments/*.rb文件中分配的 – 它需要是一个callback:

在这里输入图像描述

每当应用程序引发exception时, ShowExceptions调用ShowExceptions中间件。 该中间件将该exception构build到request ,并将其转发给config.exceptions_appcallback。

默认情况下, config.exceptions_app指向路由。 这就是Rails在公用文件夹中带有404.html500.html422.html的原因。

如果你想创build自定义的exception页面,你需要重写config.exceptions_appcallback – 将错误的请求传递给适当的处理程序,无论是controller还是route

[[中间件]]

有效pipe理这种方法的两种方法是将错误的请求发送给路由,或者调用控制器。

最简单也是最常用的方法是将请求转发给路由。 不幸的是,这忽略了请求,并阻止你正确地详细说明exception。

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

400/500错误

Rails 只能响应HTTP有效的错误

虽然应用程序的例外情况可能不同,但返回的状态代码应该40x50x 。 这符合HTTP规范,并在这里概述↴

在这里输入图像描述

这意味着无论您使用/构build什么exception处理解决scheme,Rails都需要40x50x错误返回给浏览器。

换句话说,自定义错误页面与exceptiontypes无关 – 更多的是如何捕捉和提供浏览器响应

默认情况下,Rails使用422.html 404.html422.html500.html文件执行此操作。 如果您想自己处理exceptionstream程,则需要删除这些文件,并将错误的请求传递给您自己的exceptions_appcallback。

这可以通过routescontroller来完成(现在我将解释):


1.路线

最简单的方法是让路线处理它。

此方法臃肿,需要使用多个操作。 pipe理回应也很困难。

本教程解释:

在这里输入图像描述

这显示了如何直接replace路由的exceptions_app

 # config/application.rb config.exceptions_app = self.routes 

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

应用程序configuration

 #config/application.rb config.exceptions_app = self.routes 

路线

 #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 

应用控制器

 #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 

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

 #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> 

错误视图

 #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> 

虽然许多人更喜欢简单的“路线”方法,但它既不是有效的,也不是模块化的。 事实上,如果你的应用程序有任何面向对象的外观,你很快就会将其视为黑客。

一个更响亮的方法是使用自定义控制器来捕捉纯粹的exception。 这样,您可以根据您的应用程序的整体结构构buildstream程:


2.控制器

另一种select是将所有请求路由到一个控制器。

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

这个要点显示了:

在这里输入图像描述

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

如果这个控制器有一个模型和视图的支持,我们可以将它提取成一个gem(这就是我们所做的)。 如果您想手动执行此操作,请执行以下操作:

configuration

这种方法的config.exceptions_app是它直接挂接到config.exceptions_app 。 这意味着任何exception都可以在本地处理,从而提高效率。 为了确保这个工作正常,你需要把下面的代码放到config/application.rbexceptions_app只在productiondevelopment显示错误):

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

要testing,您可以将“本地”请求设置为false:

 #config/environments/development.rb config.consider_all_requests_local = false # true 

调节器

下一步是添加一个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 

查看

有两个视图来添加到这个工作。

第一个是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> 

结论

exception错误代码无关。

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

在处理exception时,您需要确保能够处理40*错误(通常与您的其他应用程序使用相同的布局)以及50*错误(这将需要自己的布局)。

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

应用程序中的错误页面应尽可能简单。 同样的build议涉及他们的渲染 如果您的应用程序返回500个HTTP响应代码,则意味着事情已经出错了。 而且有可能无法呈现错误页面并将其显示给用户。

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

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

首先添加新的资产types(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') 

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

 # for Slim Rails.application.assets.register_engine('.slim', Slim::Template) # for Haml Rails.application.assets.register_engine('.haml', Tilt::HamlTemplate) 

现在,您可以使用自己喜欢的模板引擎和Rails内置的视图助手在app / assets / html目录中创build漂亮的错误页面。

生产提示

生产资产pipe道将编辑资产的摘要添加到文件夹中,并将文件存储在默认文件夹下(通常为生产服务器上的共享/公共/资产)。 您可以使用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 

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

 error_page 500 502 503 504 /500.html; error_page 404 /404.html; 

链轮3更新

对于Sprocket 3,你需要这样的东西(用Rails 5testing):

 # 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') 

这里是显示自定义404_error页面的最新和快速修复。

  • 根据你的环境在development.rbproduction.rb中添加以下行。

config.exceptions_app = self.routes

config.consider_all_requests_local = false

  • 删除所有rm public / {404,500,422} .html
  • 在您的rails项目的静态文件夹中创build404.html.erb文件。 你可以在这里添加自定义的html(这将使用你的应用程序布局,所以不要打扰页眉和页脚的内容)