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

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

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

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

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

## ModifiedSince(更新されたか?) は Last-Modified と Etag で判断
難しい文字が多いがシンプル

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

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

```rb:Last-Modified と Etag は簡単
fresh_when(last_modified: @article.updated_at.utc, etag: @article)
```

## Rails で Modified Since を使う方法
Rails では Controller に fresh_when や stale? というメソッドが実装されているため
これらを利用するとよさそうです。

どちらも引数が明確で使いやすいです

```rb:引数も単純明快ですね
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 の 反転なので大きな差はない

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

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

```rb: 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の実装

```rb: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 の 反転(再掲)

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

```