なぜ書くのか?
flashで表示すべきか?htmlで表示するべきか?を判断するときに、それぞれの技術の性質や役割を理解していないために、どちらを使えば良いのかを判断ができなかった。またそれらの技術の特徴を理解する以前に、基本的なこと(ブラウザのリクエスト、レスポンス、セッション(Cookie))についての理解が曖昧なために、どれだけ説明をよんでもちゃんと理解できなかった。そのため、本記事では研修で学んだ基礎的なことの復習をし、flashの技術の性質を理解するためにかく。
基本的なことのおさらい
HTTPとは
HTTPとはWebサーバとWebブラウザの間で情報のやり取りをするための通信規則です。(プロトコル) 例えばgoogleでサイトを閲覧するときも、このプロトコルに則ってブラウザがサーバーにリクエストし、その結果サイトを閲覧することができている。
HTTPの特徴として、常にブラウザ側からサーバーをよびだし、1つの要求(リクエスト)には1つの応答(レスポンス)を返すこと。そして、以前に何を要求したかで応答が変わることなく、同じ条件ならば、ある要求に対する応答は常に同じものになることです。(ステートレス)
似た名前でHTTPSがあります。HTTPとHTTPSの違いは通信が暗号化されていないか暗号化されているかの違いで、HTTPSが暗号化されています。( 僕の肌感ですが、だいたいのWebサイトがhttps
になっている気がします。
HTTPリクエスト
HTTPメソッドを使ってサーバーに対して、必要なレスポンスの要求をすることです。HTTPリクエストには「HTTPリクエスト行」「HTTPヘッダー」「データ本体」の3つのパートがあります。
HTTPリクエスト行
HTTPリクエストの1行目が、「メソッド」「URL」「HTTPのバージョン」の3つの情報が含まれています。
HTTPヘッダー
主に5つが含まれている
- ユーザーエージェント名(User-Agent)
-
ブラウザの種類やOSの情報
2.リファラ(Referer)
-
リクエストの発生元のページ名
-
クッキー(Cookie)
Cookieとは、ブラウザに対し特定の情報を保持させておく仕組みです。例えば、ログインしたことあるサイトであればIDとパスワードの情報を打たなくても、入力フォームに情報が入っているなど。 -
更新されていたら(If-Modified-Since)/同じでなければ(If-None-Match)
- ブラウザに保存されているローカルキャッシュが変更されているかどうかを問い合わせています
- 受け取り希望情報(Accept、Accept-Language、Accept-Encoding、Accept-Charset)
- どのようなデータを受け取りたいのか、言語、文字コード、画像の種類などの情報
データ本体
メソッドに「POST」が指定されている場合、送るデータが記載される
「GET」の場合は空になる
HTTPレスポンス
HTTPリクエストに従って、必要なレスポンスを返すこと。HTTPレスポンスには「レスポンス状態行」「HTTPヘッダー」「データ本体」の3つのパートがあります。
レスポンス状態行
- HTTPステータスコードが含まれている
HTTPヘッダー
データ本体の各種の状態を示す情報が入れられている部分です。主に6つが含まれています。
-
コンテンツタイプ(Content-Type)
どのようなデータを受け取ったのか?HTMLなのか画像、文字コードなどの情報。 -
再利用期限(Expires)
取得したデータを再度サーバーに問い合わせなくてもブラウザが再利用していい期限(キャッシュの制御に使われる) -
データの最終更新日時(Last-Modified)やエンティティ情報(ETag)
HTMLや画像などがいつ更新されたものかの情報や、サーバー上のファイルの場所ID、ファイルのサイズ、更新日時などから算出した、更新チェック情報。次回同じデータをリクエストする際に、これらの情報を使って更新されているかどうかを確認します。 -
キャッシュ制御(Cache-ControlやPragma)
ブラウザや通信を橋渡しするプロキシが、データのキャッシュをどう扱うかの情報。 -
接続状況(Connection)
接続を持続するのか(keep-alive)、毎回接続を切断するのか(close)。ブラウザもサーバーもHTTPバージョン1.1の持続接続(keep-alive)を使える場合、通信のやりとりが効率良くなります。 -
移動先(Location)
リクエストと違う場所からデータを取得するように示す指示。新しい場所のURLが含まれます。いわゆるリダイレクト先を示す情報です。
本題(flashの技術の性質を理解していく)
flashって?
flashの性質を理解しようと、いくつかのサイトを参考にまとめた結果、下記のように動いていることが分かった。
リダイレクト前にセッションに値を保存して、リダイレクト後のページでセッションから値を取り出して、次のリクエストでセッションに保存した値を削除する
ここから2つの疑問がうまれる
-
sessionにいつ保存したん?
-
flash[:key]= "hoge"
としか書いてないのに、なぜcookieにflashの値が保存されているの? - ブラウザでcookieの中を探しても、
hoge
らしき文言が見つからない!
-
-
sessionから、値を取得するための処理をコントローラ内に書かないと!?
-
cookie
に保存された値を取得するためのメソッドはどこに書かれているのか? - 外部情報の取得 ==
GETリクエスト
(間違った解釈) のはずだから、検証ツールのNetwork
を確認すれば、GETのレスポンスの中にhoge
がいるのではないか!
-
疑問(勘違い)を抱いた背景
1の原因
-
railsチュートリアルでは、ログイン済みユーザーを返すためにに下記のコードを書いていた。
なので、sessionと連携するために、
session[:flash] = flash
みたいなものを書くべき!と考えた。
# sessionに保存
def log_in(user)
session[:user_id] = user.id
end
-
flashの中の実装がどうなっているかを知らなかった
-
cookieに保存するときに、値が暗号化されることを忘れてしまっていた
2の原因
外部情報の取得 ==
GETリクエスト
(間違った解釈)
- HTTPリクエストをちゃんと理解ができていなかった
- railsとsessionとの関係が理解できていない
下記では、flashの確認の前に、sessionの実体を探っていく
Sessionの仕組みについて知る
そもそもsession[:key] = "hoge"
も、どうやって実装しているんだっけ?を紐解いていく
ApplicationControlle
rから、順に継承元を見ていくと ActionController::Metal
というクラスに、初めてsessio
の文字を発見(githubリンク)
module ActionController
...
class Metal < AbstractController::Base
...
delegate :session, to: "@_request"
...
end
...
end
ここから、ActionController::Metal
がsession
のメソッドを持っていることがわかるが、中身が@_request
で何を示していいるのかがわからない。。
inspectメソッドでクラス名を調べる
アクションの中に、raise session.inspect
を挿入にしてオブジェクトを調べてみる
def show
raise session.inspect
@task = Task.find(params[:id])
end
結果、session
がActionDispatch::Request::Session
であるとことが判明!
どのクラスかがわかったので、おってみる(gituhbリンク)
module ActionDispatch
class Request
...
class Session
...
def [](key)
@delegate[key]
end
...
def []=(k, v)
@delegate[k] = v
end
...
end
end
end
これでようやくアプリケーションの controllerで、sesion[:hoge]= "hogehoeg"
のメソッドが使える理由が判明!
(継承クラスを追ってみて思った。結構複雑。日々何気なく、使ってるメソッドも先人の努力の賜物であり、先人に感謝しないとなと思った。)
セッションの管理
-
Railsでは、セッションの管理にCookieStoreがデフォルトで用意されている
- cookieの他、Redis(インメモリ方式)などがある
-
Session情報を全てsecret_key_baseで暗号化し、クライアントのCookieに保存する
-
リクエストの際に、cookieに保存した暗号かされた情報から、アプリケーション内でsecret_key_baseで復号し、Session情報を取得する
flashの実装の中身を見ていく
sessionの仕組みがある程度わかったので、flashの実体を追っていく
def show
raise flash.inspect
@task = Task.find(params[:id])
end
flashは、ActionDispatch::Flash::FlashHash
であることがわかった。この中身を追っていく(github)
module ActionDispatch
class Flash
...
class FlashHash
...
def [](key)
@delegate[key]
end
...
def []=(k, v)
@delegate[k] = v
end
...
end
end
end
なるほど!これで、アプリケーションの controller、flash[:hoge]= "hogehoeg"
のメソッドが使える理由がわかりました。
ここにflash.now
の実装も書かれていました。
flashをsessionに保存 or 削除
def commit_flash # :nodoc:
session = self.session || {}
flash_hash = self.flash_hash
# flash_hashをsessionに保存
if flash_hash && (flash_hash.present? || session.key?("flash"))
session["flash"] = flash_hash.to_session_value
self.flash = flash_hash.dup
end
# sessionに保存していたflashをkeyとするhashを削除
if session.loaded? && session.key?("flash") && session["flash"].nil?
session.delete("flash")
end
end
このcommit_flash
はActionDispatch::Routing::Redirection
の中で、読み込まれており、リダイレクトのたびに実行される。
4つのポイントに分けて確認する
1. flashメッセージをセッションに登録
- コントローラ内のupdateアクションで、
flash
を作成 - ここでは、
session
には保存されていない
def update
...
flash[:success] = "タスク「#{@task.name}」を更新しました"
binding.pry
...
end
pry(#<TasksController>)> flash
=> #<ActionDispatch::Flash::FlashHash:0x00007f980022c028
@discard=#<Set: {}>,
@flashes={"success"=>"タスク「変更」を更新しました"},
@now=nil>
pry(#<TasksController>)> session[:flash]
=> nil
2. ルーティング後のコントローラ
- リダイレクトのタイミングで、
commit_flash
メソッドが実行され、session
にflash
を保存
def index
binding.pry
@tasks = Task.all
...
end
pry(#<#<Class:0x00007f95019d9228>>)> session[:flash]
=> {"discard"=>[], "flashes"=>{"success"=>"タスク「変更」を更新しました"}}
3. Viewでflashを表示
-
session
に保存していたflash_hash
の情報を取得し、画面に表示
<% binding.pry %>
<% flash.each do |key, value| %>
<%= content_tag(:p, value, class: "mb-4 alert alert-#{key}") %>
<% end %>
pry(#<#<Class:0x00007fcb0feecd20>>)> flash
=> #<ActionDispatch::Flash::FlashHash:0x00007fcb1232e3f0
@discard=#<Set: {"success"}>,
@flashes={"success"=>"タスク「変更」を更新しました"},
@now=nil>
4. 2回目のルーティング後のコントローラ
- リダイレクトのタイミングで、
commit_flash
が実行され、session
のflashが削除 - flashの中身もすべて削除されている
def show
binding.pry
@task = Task.find(params[:id])
end
pry(#<TasksController>)> session[:flash]
=> nil
pry(#<TasksController>)> flash
=> #<ActionDispatch::Flash::FlashHash:0x00007fcb194be380
@discard=#<Set: {}>,
@flashes={},
@now=nil>
この結果から、もちろんだが次のviewではflashメッセージは表示されていません。
flashとhtmlの使い分け
-
flash
- アクションの結果をリダイレクト先で一時的に表示するときに使う
-
html
- アクションの結果を現在のページで表示したい時(永続的)に使う
結果
研修でなんとなくで読んでいた箇所を今回改めて読むと以前よりも、理解できる箇所やイメージがつく箇所が増えた。正直なんとなくの理解でも、コードは動くことは動く。ただ、なんとなくの理解では、模倣でしかコードを書くことができず、応用がきかなくなりすぐに頭打ちがきそう。数年後の自分のためにも、納得して進めていきたいと思う。