Railsの静的なエラー画面表示時にhtml以外でもpublic以下のファイルを読むようにするRack middleware

Railsで例外が発生し、かつApplicationControllerとかで拾われない場合、通常は設定された exceptions_app ( デフォルトだと ActionDispatch::PublicExceptions ) が呼ばれてエラー画面表示の処理が行われる

application/html がcontent-typeに指定されてると public/500.html とか public/404.html とかを返してくれるけど、htmlじゃない場合はある規定された形のhashに対して to_xxx したものをbodyとして返すだけになってしまい、こちらが定めた定形のメッセージを返そうとした際に少し不便になる

実際に public/500.html を返すMiddlewareはどうなっているかというと以下の通りになっている

rails/public_exceptions.rb at 4dcb46182a4aaa57f44f3eb722c1db54fa0ff843 · rails/rails · GitHub

今回はこれの各種 content-type 対応版を書いた

まだgemにはしてないが以下の通り (というか既に存在しそうだし)

class MimeTypePublicExceptions < ActionDispatch::PublicExceptions
  private

  def render(status, content_type, _body)
    ext = content_type.symbol || 'html' # symbolは拡張子を表すメソッドではない
    path = [
      "#{public_path}/#{status}.#{I18n.locale}.#{ext}",
      "#{public_path}/#{status}.#{ext}"
    ].find { |fp| File.exist?(fp) }
    if path
      render_format(status, content_type, File.read(path))
    else
      [404, { 'X-Cascade' => 'pass' }, []]
    end
  end
end

こんなかんじでつかう

Rails.application.configure do
  config.exceptions_app = lambda do |env|
    MimeTypePublicExceptions.new(Rails.public_path).call(env)
  end
end

おわり