AngularJSの$resource
でRailsアプリのAPIを呼び出した際、なぜかrequest.formats
が text/html
と認識されちゃう問題の解決策。
コントローラで respond_to json: xxx
としているのに、なぜかHTMLがレスポンスされるので解決方法を調べた。
TL;DR
AngularJS側で以下のconfigをするだけ。
$httpProvider.defaults.headers.common["Accept"] = 'application/json';
原因
AngularJSのAcceptヘッダのデフォルト値と、RailsのMIME Type解決のロジックが組み合わさって起こる。
AngularJS
ここに書かれている通り、$httpモジュールのAcceptヘッダのデフォルト値は application/json, text/plain, */*
。
さらに、X-Requested-With
ヘッダも送信しない仕様になっている。
Rails
Rails/actionpackのMIME Type解決をしている部分で、Acceptヘッダに*/*
が含まれるとブラウザからのアクセスとみなされてMIME Typeが強制的にtext/html
に設定される。(このロジックが導入された時のコミットログ。*/*
はブラウザしか使わないはずだから、とりあえずHTML送りつけとくぜ、的な。)
また上記の通りAngularJSからはX-Requested-With
ヘッダも送信されないため、MIME Typeが期待通りに解釈されない。
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
def valid_accept_header
(xhr? && (accept.present? || content_mime_type)) ||
(accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
end
解決
ということで、AngularJSのデフォルトAcceptヘッダから*/*
を除外すればMIME Type判別が期待通りに動くようになる。
$httpProvider.defaults.headers.common["Accept"] = 'application/json';
もしくは、XHRのヘッダーを付与してもOK。
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';