分享 Rails 中如何自定义 Error Pages

jesktop · 2014年09月07日 · 最后由 embbnux 回复于 2016年09月02日 · 7308 次阅读
本帖已被设为精华帖!

修改配置

首先需要修改项目的配置,让项目接收到错误提示时,从 route 中寻找,而不直接读 public 的文件:

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

加入配置之后,避免在读取到/404, /422, /500时,仍然从 public 中寻找,建议删除 public 下的这三个 html 文件。

加入 controler,view 和 route

因为 public 的对应错误信息页面删除了,所以需要重新定义错误信息的 Route:

# config/routes.rb
%w(404 422 500).each do |code|
  get code, to: "errors#show", code: code
end

在加好 Errors 后,自然需要对应的 Controller 文件:

class ErrorsController < ApplicationController

  def show
    render status_code.to_s, status: status_code
  end

protected

  def status_code
    params[:code] || 500
  end

end

这样就会根据错误的提示,去 render 对应的模板,如出现 404 错误,则去寻找errors/404.html.erb,所以加入相应的404.html.erb, 422.html.erb, 500.html.erb。参考:

# app/views/errors/404.html.haml
%h1 404 - Not Found

开发环境下,测试错误信息页面

因为开发环境下出现错误,都会呈现对应的错误信息,包括代码位置等。而不会弹出 404 或 500 页面,其实要在开发环境下,检测这些页面也是很方便的:

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

只需要把上面的设置从 true 改为 false 就可以了。

Rails 内自带的 500 错误提示

当把错误自定义以 route 的方式展现后,如果本来的错误信息页面,例如 404 页面出错了,就会出现这样的错误:

"500 Internal Server Error\n" \
"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."

这个问题就像刚刚上面说的,是因为你的错误提示信息页面出错了,无法展现 404 页面了,所以就调用了 Rails 下的一个 500 错误提示信息,源码位置在:show_exceptions.rb#L18-L22。 所以,如果出现了这样的错误,需要仔细看看自己的错误信息页面是否在哪里出了问题。

* 我在一个开源项目里,也加入了此功能,可以浏览对应的 commit,位置在:Merge branch 'dynamic_error_pages'

参考: DYNAMIC ERROR PAGES IN RAILS

原文: RAILS 中如何自定义 ERROR PAGES

开发环境要测试页面浏览器直接访问/404 /500 /403 就好啦

这个不错,如果使用静态页面,不能在不同环境使用不同代码,这个在用 GA 统计追踪的时候非常有用。👍

#2 楼 @hooopo 人家早年的笔记里早已记录了这招了哇... https://github.com/jasl/a_rails_start_up_omakase/blob/master/config/environments/production.rb#L111

我倾向生产环境才这么做,另外手动 raise RoutingError 在开发环境行为会比较奇怪...不会打断请求

#3 楼 @jasl 这个不会打断请求虽然我大概能想到,但是能不能稍微举几个例子说明一下他的利弊。另外能不能用手动打断方式代替。

#4 楼 @realwol 这个生产环境不会遇到的,也是无意发现,按道理既然是异常那打断程序执行应当是很正常的事情... 后来研究了下,加一句 config.action_dispatch.show_exceptions = true 就好了

个人倾向开发环境显示异常堆栈页面,调试 404 500 403 之类直接访问就好啦~

最近好贴,真多啊。看都看不完的样子。

中国好帖.

#5 楼 @jasl 哦,就说我没想到个什么情景下出现问题。

谢谢楼主~之前为这个错误页 google 了好久的

#3 楼 @jasl 确实,可以通过/404 /500 /422来查看页面样式,你不说我还真忘了。 😄 我一般做法就是,设置:

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

也只是查看 404 页面是否指向 route 的页面,就是是否已经生效和会不会产生 rails 自带的 500 错误,如果没有问题后,还是把设置改为true。毕竟开发环境有问题弹出 500 之类的也不太好。

然后你说到的raise RoutingErrorconfig.action_dispatch.show_exceptions = true,是什么时候需用用上就不太了解。

不错, 比之前自己手动处理每一个 error 方便多了

#10 楼 @JeskTop 你这样做有个缺点嘛...就是要频繁重启开发环境的 rails 进程

手动 raise RoutingError 的场景主要是保护管理员页面,403 的话等于暴露此处有小秘密了...有招致攻击的危险嘛! 但是我发现在开发环境下,手动 raise 之后并没有按期望打断请求,后边那个配置就是解决这个问题的

这里是另外一种作法,https://gist.github.com/wojtha/8433843 默认的 exceptions_app 是ActionDispatch::PublicExceptions的一个实例,它会在 ShowExceptions 这个 middleware 中被通过 call 来调用。 所以我们可以将其替换成一个 lambda 或者是直接一个 rack 的 middleware。

我在项目中参考使用了 LZ 的方案,一切正常😊

直到最近遇到了一个小问题,比如有爬虫访问了xxx.php,后台依旧会报 500 的错误,并且没有自定义 Error pages。

我是这么解决的:

def show
  respond_to do |format|
    format.html { render status_code.to_s, status: status_code }
    format.any { redirect_to root_path }
  end
end

#15 楼 @night_7th 哈哈,可以在 nginx 代理的时候就阻止了.php 的访问,简单粗暴

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册