Rails でブラウザのキャッシュを利用する


# 静的ファイル
## Cache-controlヘッダを指定する

Expiresと同じく、キャッシュの有効期限を指定できるレスポンスヘッダ。HTTP1.1で定義されたもので、有効期限だけでなくキャッシュに関する詳細な設定を行える。大多数のブラウザが HTTP1.1に対応しているので、Expiresではなく、Cache-Controlヘッダを利用するのがよい。

### 説明

- max-age : キャッシュの有効期限を設定する。(相対時間で指定し、単位は秒。）

    例えばブラウザのキャッシュを1年間有効にしたい場合は、次のように指定する。
有効期限の間はキャッシュが利用され、サーバに問い合わせが発生しないようにできる。

```
Cache-Control: max-age=31557600
```

- private : そのコンテンツがそのユーザのみに対して提供されたものであり、キャッシュを他のユーザと共有しない。

- must-revalidate : キャッシュに記録されているコンテンツが現在も有効かどうかを、必ずサーバに問い合わせる設定。

### Rails では
以下がデフォルトでレスポンスに付与している。

```
Cache-Control: max-age=0, private, must-revalidate
```

Cache-Control ヘッダを Rails から指定したい場合は、コントローラで次のように記述する。

```ruby:
def index
  expires_in 1.hour
  # => Cache-Control: max-age=3600, private
end
```
expires_in はオプションとして、public と must_revalidate を T/F で指定する。

### ブラウザにキャッシュさせたくない場合
```rb:
def index
  expires_now
end
# => Cache-Control: no-cache
```

### Nginx で同様の設定をするには
```nginx.conf
location ~ ^/assets/ {
  add_header Cache-Control "max-age=31557600, public";
}
```

これで、有効期限の間はキャッシュが利用され、サーバに問い合わせが発生しないが、活用できるのは静的ファイルに限られる。


# 動的ファイル

## ETag、Last-Modified ヘッダを指定する

### 説明
- __ETag（If-None-Match）__
    そのコンテンツを示すユニークな値を返すレスポンスヘッダで、コンテンツの更新の有無を確認するために用いられる。同じURLへリクエストを行った場合、コンテンツが更新されていなければ ETag ヘッダの値は毎回同じ、コンテンツが更新されていれば ETag ヘッダを変化させるという決まり。Etag ヘッダを設定すると次回のリクエストのヘッダに、If-None-Match というリクエストヘッダを付し、この値を比較し、ページが更新されていなければ 304 - Not Modified のステータスコードのみを返す。

- __Last-Modified（If-Modified-Since）__
    Etag と同様に、条件付き GET リクエストのためのレスポンスヘッダ。名前の通りそのURLが最後に更新された時刻を出力する。Last-Modified ヘッダを設定すると If-Modified-Since というリクエストヘッダを用いる。

### Rails では

```rb:
def index
  @article = Article.last
end
# => 200 OK

def index
  @article = Article.last
  fresh_when @article
end

# => 304 Not Modified
# => Etag : "<MD5>"
# => Last-Modified: <日付>
```

#### fresh_when
Etag と　Last-Modified をレスポンスヘッダに付与する。

fresh_when は Rails4.2 から template の更新が考慮されるように修正されたみたいです。
> rails commit log流し読み(2014/08/17) - なるようになるブログ : http://y-yagi.hatenablog.com/entry/2014/08/18/063227

#### コントローラレベルでの ETag
```rb:
class ArticleController < ApplicationController
  etag { current_user.try(:id) }
 
  def index
    @article = Article.find(params[:id])
    fresh_when(@article)
  end
end
```
> etag はブロックの評価結果を ETag の値を算出する元データに追加します。ログインユーザごとに ETag の値を変更したい場合に有効。

## コード
今回使ったメソッドは、
```ActionController::ConditionalGet``` にある。
https://github.com/rails/rails/blob/b40bd16b7b0810b9d5621339f243c4a9569ceb96/actionpack/lib/action_controller/metal/conditional_get.rb

- expires_in, expires_now
- fresh_when
- stale? (上で紹介していない）

> 適切なEtagとLast-Modified-Sinceヘッダを設定し、現在のリクエストがstale (腐りかけた; 完全な処理をする必要がある) か、それともfresh (新鮮な; ウェブクライアントがキャッシュされたコンテンツをつかえる) かを真偽値で返す。

# 参考・引用
以下の書籍、記事から多く引用・参考にさせていただきました。
スライドや図、サンプルコードなどもあってとてもわかりやすいです。
> 
- WEB+DB PRESS Vol.70｜技術評論社 : http://gihyo.jp/magazine/wdpress/archive/2012/vol70
- High Performance Rails (long edition) // Speaker Deck : https://speakerdeck.com/mirakui/high-performance-rails-long-edition
- Ruby on Rails のConditionalGet について - Qiita : http://qiita.com/cuzic/items/326e8600dc596de6636a
- Controller に関する変更点 | TECHSCORE(テックスコア) : http://www.techscore.com/tech/Ruby/rails-4.0/controller/
- Http caching ruby rails · herokaijp/devcenter Wiki : https://github.com/herokaijp/devcenter/wiki/Http-caching-ruby-rails
-  ActionController::ConditionalGet : http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F