Modified Since とは
通常Gemを利用することが多く、直接利用することは少なかったので調べてみました。
Modified Since とは HTTP のリクエストに含めることができるヘッダーの1つ
名前の通りコンテンツの最終更新日を示すことができます。
サーバはこのリクエストヘッダ情報を参照して、DBやコンテンツの最終更新日からページの内容が更新されて居ない場合
304 Not Modified レスポンスを返すことができます。
304レスポンスを返した場合、コンテンツが変更されて居ないことをブラウザは把握することができ
ブラウザの中にあるキャッシュを用いてページを表示することができます。
ModifiedSince(更新されたか?) は Last-Modified と Etag で判断
難しい文字が多いがシンプル
last_modified は最終更新日なので updated_at
を利用すればいい
etag はどのコンテンツのキャッシュなのかをキーとして保存するため @article
を利用
こうすることで、「どのコンテンツ」が「いつ更新された」のかを
ブラウザにキャッシュしているということがわかるようになる。
fresh_when(last_modified: @article.updated_at.utc, etag: @article)
Rails で Modified Since を使う方法
Rails では Controller に fresh_when や stale? というメソッドが実装されているため
これらを利用するとよさそうです。
どちらも引数が明確で使いやすいです
fresh_when(last_modified: @article.updated_at.utc, etag: @article)
if stale?(last_modified: @article.updated_at.utc, etag: @article)
# キャッシュが古い場合の処理
end
stale? と fresh_when の使い分けは文脈の差
概ねシンプルなウェブサイトであれば
挙動としてはどちらも同じように利用することができるため
大きな差は感じないと思います。
実際 stale? の実装は fresh_when の 反転なので大きな差はない
def stale?(object = nil, **freshness_kwargs)
fresh_when(object, **freshness_kwargs)
!request.fresh?(response)
end
fresh_when はチェック する文脈で
キャッシュに更新がなければ 304 レスポンスを即座に返し
キャッシュが古い場合は、通常通り controller の処理を続けるので
一応チェックとして一文入れておこうという感じで使えます。
stale? はもしキャッシュが古い場合は?...という文脈で
stale? は if ブロック内でキャッシュが古い場合に特定の処理を記述できるので
もしキャッシュが古い場合は?...という文脈で書きたい場合はこちらがよさそうです。
fresh_when で記事の更新があるかのサンプルコード
last_modified(updated_at
) && Etag(@article
) を参照し
更新の必要性がない場合は fresh_when は即座に 304 を返します。
更新の必要がある場合は その下の処理が引き続き実行されます。
def show
@article = Article.find(params[:id])
fresh_when(last_modified: @article.updated_at.utc, etag: @article)
# 以下にコードを書いても実行されず 304 が返り、ブラウザのキャッシュが利用される
end
stale? で記事の更新があるか
Last-Modified(@article.updated_at
) と Etag(@article
)を
リクエストヘッダーの If-Modified-Since と If-None-Match と比較します。
変更が無ければ false
で Railsは自動的に304レスポンスを返答してくれます。
変更がある場合 true
で Rails はブロック内のコードの実行をします。
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
if stale?(last_modified: @article.updated_at.utc, etag: @article)
# キャッシュが古いため新しいコンテンツをブラウザに返す
# レスポンスヘッダーにLast-ModifiedとETagが含まれる
end
# もし stale? がfalseの場合は即座に304が返されるため、elseは書くことができない。
end
end
304 Not Modified レスポンスが返されると?
ブラウザは 304 Not Modified レスポンスが返された場合
キャッシュにあるDL済みのファイルを用いてページを再度構築します。
再度ブラウザがコンテンツをダウンロードすることがないため
ユーザーにロード時間を待たせないメリットもあれば
サーバに対しての負荷も同時に下げることができます。
おまけ:fresh_whenの実装
def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
response.cache_control.delete(:no_store)
weak_etag ||= etag || object unless strong_etag
last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
if strong_etag
response.strong_etag = combine_etags strong_etag,
last_modified: last_modified, public: public, template: template
elsif weak_etag || template
response.weak_etag = combine_etags weak_etag,
last_modified: last_modified, public: public, template: template
end
response.last_modified = last_modified if last_modified
response.cache_control[:public] = true if public
response.cache_control.merge!(cache_control)
head :not_modified if request.fresh?(response)
end
おまけ: stale? の実装は fresh_when の 反転(再掲)
def stale?(object = nil, **freshness_kwargs)
fresh_when(object, **freshness_kwargs)
!request.fresh?(response)
end