Railsはどうやって500やdebug用のエラー画面を出しているのか

関心

素の Rack middleware はただの玉ねぎ構造で、Exception は Exception で rescue しなければそのまま一番外側の middleware まで伝播して、ユーザーに適切に status code を伝えることなく果ててしまうはずである。

しかし実際には Rails などのフレームワークを使った場合、それっぽいエラー画面をユーザーに伝えることができるようになっている。

それでいて ExceptionNotification gem など、自分で use した middleware の中ではやはり例外を例外のまま拾うことができる。

ということは、通常の

Rack::Builder.new do
  use ..
  use ..
  run ..
end

とは異なる何らかの middleware 群がフレームワークによって外側に配置されているはずである。

結論

予想通り、いい具合に例外を適切な response に変換する middleware が外側の stack にいる。したがって通常のアプリや独自の rack middleware を書く際に例外は例外のまま投げっぱなしでよい。

いざコードリーディング

※ ちょっと事情があって古い Rails を読んでいるので最新のものでは細かい部分で食い違いがあるかもしれないが、基本的な構造は変わっていないはず

railtie の中で middleware を探していくと Rails::Engine にそれっぽい記述が見つかる。

  • Rails::Engine の中で default_middleware_stack を呼んでいて
  • これが ActionDispatch::MiddlewareStack.new して…途中すっ飛ばして
  • Rails::Application::DefaultMiddleware#build_stack の中でデフォルトで定義済みの middleware を use していく。これが

https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack

に挙げられているもの。

この中で中盤くらいに ActionDispatch::ShowExceptions という middleware があって、こいつに ActionDispatch::PublicExceptions という別な middleware を与えてある。

default_middleware_stack.rb の以下のコードがそれである。

         middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app

通常の Rack::Builder で use している場合の Middleware.new(app, *args, &block ) とは別な形でもう一つの middleware が与えられていて、 rescue Exception した際にはこの show_exceptions_app として PublicExceptions が呼ばれるようになっている。該当部分は以下の通り。

show_exceptions.rb の中身はこう。

   def initialize(app, exceptions_app)
     @app = app
     @exceptions_app = exceptions_app
   end

   def call(env)
     @app.call(env)
   rescue Exception => exception
     if env['action_dispatch.show_exceptions'] == false
       raise exception
     else
       render_exception(env, exception)
     end
   end

そして PublicExceptions の中では

   def render_html(status)
     path = "#{public_path}/#{status}.#{I18n.locale}.html"
     path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))

     if found || File.exist?(path)
       render_format(status, 'text/html', File.read(path))
     else
       [404, { "X-Cascade" => "pass" }, []]
     end
   end

こんな風に決め打ちで public/ 以下からerror 用の HTML を render している。なるほど。

More

Categories

Tool 日々 Web Biz Net Apple MS ことば News Unix howto Food PHP Movie Edu Community Book Security Text TV Perl Ruby Music Pdoc 生き方 RDoc ViewCVS CVS Rsync Disk Mail FreeBSD Cygwin PDF Photo Zebedee Debian OSX Comic Cron Sysadmin Font Analog iCal Sunbird DNS Linux Wiki Emacs Thunderbird Sitecopy Terminal Drawing tDiary AppleScript Life Money Omni PukiWiki Xen XREA Zsh Screen CASL Firefox Fink zsh haXe Ecmascript PATH_INFO SQLite PEAR Lighttpd FastCGI Subversion au prototype.js jsUnit Apache Trac Template Java Rhino Mochikit Feed Bloglines CSS del.icio.us SBS qwikWeb gettext Ajax JSDoc Rails HTML CHM EPWING NDTP EB IE CLI ck ThinkPad Toy WSH RFC readline rlwrap ImageMagick epeg Frenzy sysprep Ubuntu MeCab DTP ERD DBMS eclipse Eclipse Awk RD Diigo XAMPP RubyGems PHPDoc iCab DOM YAML Camino Geekmonkey w3m Scheme Gauche Lisp JSAN Google VMware DSL SLAX Safari Markdown Textile IRC Jabber Fastladder MacPorts LLSpirit CPAN Mozilla Twitter OpenFL Rswatch ITS NTP GUI Pragger Yapra XML Mobile Git Study JSON VirtualBox Samba Pear Growl Mercurial Rack Capistrano Rake Win RSS Mechanize Sitemaps Android JavaScript Python RTM OOo iPod Yahoo Unicode Github iTunes God SBM friendfeed Friendfeed HokuUn Sinatra TDD Test Project Evernote iPad Geohash Location Map Search Simplenote Image WebKit RSpec Phone CSV WiMAX USB Chrome RubyKaigi RubyKaigi2011 Space CoffeeScript Nokogiri Hpricot Rubygems jQuery Node GTD CI UX Design VCS Kanazawa.rb Kindle Amazon Agile Vagrant Chef Windows Composer Dotenv PaaS Itamae SaaS Docker Swagger Grape WebAPI Microservices OmniAuth HTTP 分析基盤 CDN Terraform IaaS HCL Webpack Vue.js BigQuery Middleman CMS AWS PNG Laravel Selenium OAuth OpenAPI GitHub UML GCP TypeScript SQL Hanami Develop Document Jekyll