ログやアクセス制御でAPIかHTMLリクエストかを簡単に区別できるようにする為。
また、ブラウザで.jsonのURLにアクセスしたらエラーにしたい。簡単に見れないようにしたい。

デフォルトの挙動と課題

request.format.json?がtrueになるのは、URLの拡張子が.jsonか、acceptヘッダにapplication/jsonが含まれる(htmlや*がない)場合なので、拡張子なしでもacceptヘッダにあればJSONが返却される。
また、renderで、request.format.json?がtrueの場合にjbuilderが使われ、request.format.html?がtrueの場合はerbのviewが使われるので、一致するviewがないとエラーになる。

URLの拡張子 Acceptヘッダ request.format.json? request.format.html? レスポンス
.json JSONが含まれる(htmlや*がない) true false JSON返却
上記以外 true false JSON返却 → 406にしたい
なし JSONが含まれる(htmlや*がない) true false JSON返却 → 406にしたい
上記以外 false true HTML返却

参考: request.format.json?がtrueになる条件は?
既存のControllerでDevise Token Auth認証してJSONを返す

defaults formatの挙動

routesでdefaults formatを指定している場合は、URLの拡張子がなくてもJSON扱いになる。
また、下記のようにRSpecとかでformat指定しても、リクエストURLに.jsonは入らない。

    subject { get xxx_path(format: :json) }

下記でも同じ.jsonが入らないURLになる。

    subject { get xxx_path(format: nil) }

方針

defaults formatを使わずに、明示的に406(not_acceptable)を返すようにする。
renderで対象のviewがない場合に返すHTTPステータスと同じ。

config/routes.rb

  draw :infomations

config/routes/infomations.rb

-  defaults format: :json do
-    get 'infomations/important', to: 'infomations#important', as: 'important_infomations'
-  end
+  get 'infomations/important', to: 'infomations#important', as: 'important_infomations'

JSONとHTMLの両方を返却する場合

ApplicationAuthControllerを継承するようにしているので、今回はここに追加。

app/controllers/application_auth_controller.rb

class ApplicationAuthController < ApplicationController
  include DeviseTokenAuth::Concerns::SetUserByToken
  skip_before_action :verify_authenticity_token, unless: :format_html?
+  prepend_before_action :not_acceptable_response

  private

+  # リクエストに不整合がある場合、HTTPステータス406を返却
+  def not_acceptable_response
+    if format_html?
+      head :not_acceptable unless accept_header_html?
+    else
+      head :not_acceptable unless accept_header_api?
+    end
+  end

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  private

  # URLの拡張子がないかを返却
  def format_html?
    request.format.html?
  end

  # acceptヘッダにJSONが含まれる(ワイルドカード不可)かを返却
  def accept_header_api?
    !(%r{,application/json[,;]} =~ ",#{request.headers[:ACCEPT]},").nil?
  end

  # acceptヘッダが空か、HTMLが含まれる(ワイルドカード可)かを返却
  def accept_header_html?
    request.headers[:ACCEPT].blank? ||
      !(%r{,text/html[,;]} =~ ",#{request.headers[:ACCEPT]},").nil? ||
      !(%r{,\*/\*[,;]} =~ ",#{request.headers[:ACCEPT]},").nil?
  end

JSONまたはHTMLのみ返却する場合

app/controllers/application_controller.rb

  # APIリクエストに不整合がある場合、HTTPステータス406を返却(明示的にAPIのみ対応にする場合に使用)
  def not_acceptable_response_not_api_accept
    head :not_acceptable if format_html? || !accept_header_api?
  end

  # HTMLリクエストに不整合がある場合、HTTPステータス406を返却(明示的にHTMLのみ対応にする場合に使用)
  def not_acceptable_response_not_html_accept
    head :not_acceptable if !format_html? || !accept_header_html?
  end

app/controllers/infomations_controller.rb

class InfomationsController < ApplicationAuthController
+  prepend_before_action :not_acceptable_response_not_api_accept, only: %i[important]

HTMLのみ返却する場合は、not_acceptable_response_not_html_acceptにすればOK


今回のコミット内容
※RSpecやDevise Token Auth使っている部分も同様の修正を入れています。
https://dev.azure.com/nightonly/rails-app-origin/_git/rails-app-origin/commit/6a0fb22e942bc7e24a4421a9f605929a3612b311

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です