2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【HTTP Message Header】Rails の ModifiedSince を用いた キャッシュコントロール

Last updated at Posted at 2023-11-09

Modified Since とは

通常Gemを利用することが多く、直接利用することは少なかったので調べてみました。

Modified Since とは HTTP のリクエストに含めることができるヘッダーの1つ
名前の通りコンテンツの最終更新日を示すことができます。

サーバはこのリクエストヘッダ情報を参照して、DBやコンテンツの最終更新日からページの内容が更新されて居ない場合
304 Not Modified レスポンスを返すことができます。

304レスポンスを返した場合、コンテンツが変更されて居ないことをブラウザは把握することができ
ブラウザの中にあるキャッシュを用いてページを表示することができます。

ModifiedSince(更新されたか?) は Last-Modified と Etag で判断

難しい文字が多いがシンプル

last_modified は最終更新日なので updated_at を利用すればいい
etag はどのコンテンツのキャッシュなのかをキーとして保存するため @article を利用

こうすることで、「どのコンテンツ」が「いつ更新された」のかを
ブラウザにキャッシュしているということがわかるようになる。

Last-Modified と Etag は簡単
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 の 反転なので大きな差はない

railsの実装
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 を返します。
更新の必要がある場合は その下の処理が引き続き実行されます。

fresh_when
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 はブロック内のコードの実行をします。

Modifiled Since on 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の実装

railsの実装
    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 の 反転(再掲)

railsの実装
def stale?(object = nil, **freshness_kwargs)
   fresh_when(object, **freshness_kwargs)
   !request.fresh?(response)
end

2
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?