Railsのコントローラにてリダイレクトをしたい時は redirect_to
が使えます。
2.3 redirect_toを使う | レイアウトとレンダリング - Railsガイド
以下の例では、コントローラの show()
へルーティングされた時に after_redirect_erb_fruits_path
へとリダイレクトしています。
class AfterRedirect::Erb::FruitsController < ApplicationController def index end def show redirect_to after_redirect_erb_fruits_path # リダイレクト end end
試しに curl で動作確認すると、HTTP302なレスポンスになります。
% curl http://localhost:7100/after_redirect/erb/fruits/1 -v ... < HTTP/1.1 302 Found ... < Location: http://localhost:7100/after_redirect/erb/fruits ... <html><body>You are being <a href="http://localhost:7100/after_redirect/erb/fruits">redirected</a>.</body></html>
そんな中
- バックエンドはRailsでAPIを作る
- フロントエンドのJavaScriptの
fetch()
RailsアプリのAPIを呼ぶ- APIのレスポンスを元に、JavaScript側で処理を加えたりリダイレクトする
というアプリを作ってみたところ、 fetch()
で受け取ったレスポンスがcurlと異なっていました。
そこで、レスポンスの違いについて調べたことをメモしておきます。
目次
- 環境
- JavaScriptは fetch() + Railsは redirect_to() の場合
- JavaScriptはオプション redirect: manual な fetch() + Railsは redirect_to() の場合
- JavaScriptはfetch() + Railsは render() の場合
- JavaScriptはfetch() + Railsは render() + location の場合
- まとめ
- ソースコード
環境
- Rails 7.0.3
- React 18.1.0
- JavaScriptで
fetch()
を使うためのアプリをReactで作成
- JavaScriptで
なお、コントローラは
class Api::TryRedirect::FruitsController < ApplicationController def show # ここに実装 end end
な形とし、 ここに実装
へ実装していくものとします。
JavaScriptは fetch() + Railsは redirect_to() の場合
redirect_to のオプションなし
まず、Rails側は
redirect_to after_redirect_erb_fruits_path
とします。
次に、JavaScript側は
const res = await fetch(`/api/try_redirect/fruits/${id}`) console.log(res) console.log(res.status) const t = await res.text() console.log(t)
とします。
この実装で動作確認したところ、ブラウザの Console にレスポンスのHTTPステータスが 200
と出力されました。
また、レスポンスボディの出力も見たところ、リダイレクト先の情報を取得しているように見えました。
redirect_to のオプションにステータスコードを指定
次に redirect_to
へステータスコード 303 (see other)
を与えてみます。
ActionController::Redirecting
redirect_to after_redirect_erb_fruits_path, status: :see_other
しかし、結果は同じく
- HTTPステータスコードは
200
- リダイレクト先も読みに行っている
でした。
リダイレクト先も読みに行っている原因を調査
curlのときと何が違うのかを調べてみたところ、以下の情報がありました。
https://scrapbox.io/nwtgck/fetch()でもXMLHttpRequestでもデフォルトでリダイレクトを追跡してXHRだとリダイレクトさせない方法はない
- (URLの途中に
()
があるせいか、はてなブログに上手くリンクが貼れず、この形式でリンク) - https://fetch.spec.whatwg.org/#concept-request-redirect-mode
- (URLの途中に
- XMLHttpRequestオブジェクトでRedirectをハンドリングするには?(リダイレクトを拾う方法) - on the center line.
- How redirect_to works in Rails? - DEV Community
- HTTP のリダイレクト - HTTP | MDN
fetch()
の場合、リダイレクトモードのデフォルトが follow
なため、自動でリダイレクト先のデータも取得してしまったようでした。
WindowOrWorkerGlobalScope.fetch() - Web API | MDN
JavaScriptはオプション redirect: manual な fetch() + Railsは redirect_to() の場合
fetch()
のリダイレクトモードによる違いがあるか気になったため、リダイレクトモードを変更して試してみます。
fetch()
にオプション redirect: manual
を渡すよう、JavaScript側を修正します。
const res = await fetch(`/api/try_redirect/fruits/1`, {redirect: 'manual'}) // 追加
Railsはそのままの実装として動作確認すると、
- responseの
type
がopaqueredirect
- status が
0
- レスポンスボディが空
- リダイレクト先の読み込みしてなさそう
という結果に変わりました。
この結果は Response オブジェクトの type = opaqueredirect
に書かれている内容の通りでした。
Response.type - Web APIs | MDN
JavaScriptはfetch() + Railsは render() の場合
fetch()
の redirect
オプションを使うことで自動的なリダイレクト先の読み込みがなくなりそうでした。
ただ、
- RailsからHTTP 3xx を受け取って、JavaScript側で処理したい
- リダイレクトする条件を満たしていたら、Railsからレスポンスボディを受け取って、JavaScript側で処理したい
- 302の場合、レスポンスボディを返してもよさそう
などを行おうとすると、 fetch()
の redirect
オプションでは厳しそうでした。
そこで、Railsで render()
で 302 を返してみるとどうなるかを見てみます。
const res = await fetch(`/api/try_redirect/fruits/${id}`)
とします。
また、Rails側は
- HTTPステータスコードは
302
- JSONのレスポンスを返す
となるよう
render status: :found, json: {path: params[:id]}
な実装にします。
動作確認をしたところ、
- HTTPステータスコードは
302
- レスポンスボディにJSONあり
- リダイレクト先の読み込みなし
な結果がブラウザの Console に出力されました。
JavaScriptはfetch() + Railsは render() + location の場合
先ほどの実装でやりたいことは満たせそうですが、もう少し render()
について調べてみます。
render()
のリファレンスより location
オプションを使うことでHTTPヘッダの Location
に値を渡せそうでした。
2.2.13.3 :locationオプション - 2.2.13 renderのオプション | レイアウトとレンダリング - Railsガイド
そこで、Rails側を
render status: :found, location: after_redirect_erb_fruits_path, json: {path: params[:id]}
として動作確認したところ、 redirect_to()
と同様、リダイレクト先も自動で読み込まれました。
ここで redirect_to()
の実装を見てみると、 location
を設定していました。
https://github.com/rails/rails/blob/24ebaa4e83b8809be5145bc31d68f267daadfe20/actionpack/lib/action_controller/metal/redirecting.rb#L89
そのため、 render
で location
を設定した場合は、 redirect_to
と同じ動作になりそうでした。
まとめ
fetch()
を使う時に、自動でリダイレクト先も読み込まないようにするには
- JavaScriptはオプション
Predirect: manual
なfetch()
+ Railsはredirect_to()
- JavaScriptは
fetch()
+ Railsはrender()
あたりを使うのが良さそうでした。
ソースコード
Githubに上げました。
https://github.com/thinkAmi-sandbox/react_with_vite_rails-sample
今回のプルリクはこちらです。
https://github.com/thinkAmi-sandbox/react_with_vite_rails-sample/pull/4