search
LoginSignup
63

More than 5 years have passed since last update.

posted at

updated at

Ruby on Rails のConditionalGet について

cuzic です。

Ruby on Rails Advent Calendar 24日目の記事です。

今日はクリスマスイブです。
実は生まれて初めての Advent Calendar への投稿です。
さらに Qiita で書いたのも初めてです。
ちょっとドキドキです。

今日は、Ruby on Rails における Conditional Get
ついて書きます。

ConditionalGet とは

ここで書こうとしている ConditionalGet とは、
HTTP1.1 で規定されている取得済みのコンテンツであれば
ブラウザ内のローカルキャッシュを使わせることで、
トラフィック量を削減する機能のことです。

一般に、ブラウザは動作を高速化するため、トラフィックを
軽減するため、コンテンツを一旦キャッシュとして保管します。

とはいえ、キャッシュを使う限り、そのキャッシュは今でも
有効なのか、全文を再取得すべきなのかを判断する必要があります。

このキャッシュの有効性の判断はコンピュータ科学の2つの難問の1つです。

There are only two hard things in Computer Science:
cache invalidation and naming things.
-- Phil Karlton

この難問に対する HTTP の回答が Last-Modified ヘッダと、
ETag ヘッダです。

サーバは応答するときにコンテンツの Last-Modified
ETag を付与します。ブラウザはその値を覚えておきます。

同じコンテンツにアクセスするとき、 ブラウザは ETag の値を
If-None-Match ヘッダとしてサーバに送信します。
サーバでは前回の ETag と同一かどうか確認し、
同一であれば 304 Not Modified という応答を返します。
このとき応答するのはヘッダのみでよいため、ネットワークトラフィックの
節約になります。

当然、この ETag は、本文のハッシュ値など異なるコンテンツであれば
異なる値になるようにする必要があります。

Rack での ConditionalGet

ConditionalGet が使われているかの確認方法

Ruby on Rails では ConditionalGet を下記の Rack ミドルウェアが
自動的に透過的に実行してくれます。

  • Rack::ETag
  • Rack::ConditionalGet

なお、自分の環境で、どのような Rack ミドルウェアが使われいるかは、
次のコマンドで確認できます。

 rake middleware

上記のコマンドを実行すると、 Rack::ConditionalGetRack::ETag
かなり下の方にあるはずです。

これらの Rack ミドルウェアが下の方に位置しているのには理由があります。
その理由は、後ほど述べます。

Rack::ETagRack::ConditionalGet の動作

Rack::ETag は、WEBアプリケーションサーバからの
レスポンスに対して、 Etag ヘッダを付与する Rack ミドルウェアです。

Rack::ETag は、下記の3つの条件がすべて満たされるとき、
response.bodyMD5 の値を ETag を付与します。

  • レスポンスのステータスコードが 200 か 201 であること
  • to_path メソッドに応答しないこと  (※)
  • Cache-Control ヘッダが no-cache でないこと
  • ETag ヘッダや Last-Modified ヘッダがすでに含まれていないこと

(※) to_path メソッドに応答するときは、 ApacheNginX がレスポンスを処理するので、
ここで ETag を付与する必要がない。くわしくは、 X-Sendfile などで Google すること。

そして、 ETag がすでに含まれていることを前提として、
Rack::ConditionalGet が下記の動作を行います。

 1. ブラウザのキャッシュ内のコンテンツが fresh (最新と一致している)かどうかを判断する。
 2. fresh であれば次の処理を行う。
   応答ステータスを 200 から 304 に変更する
   Body 、Content-Type、Content-Length を削除する。

fresh かどうかの判定は、下記のように行います。

  • リクエストに If-Modified-Since ヘッダがあれば、それがレスポンスヘッダのLast-Modified よりも新しいこと。
  • リクエストに If-None-Match ヘッダがあれば、それがレスポンスヘッダの ETag と一致していること。

まとめると、開発者が、特になにもしていなかったとしても、
ETagRack::ETag が付与してくれています。
そして、 Rack::ConditionalGet が適切に 304 の応答を返します。

Rack ミドルウェアの順序

Rack ミドルウェアは rake middleware で表示される順序に
従って処理が行われます。
リクエストを処理するときは上から順に処理され、
レスポンスを返すときは下から順に処理されます。

つまり、レスポンスを返すとき、 Rack::ETag がまず処理して、
ETag ヘッダを付与し、それに従って、 Rack::ConditionalGet
適切に 304 not modified の応答を返します。

Rack::ConditionalGet で 304 を返す場合、他の Rack ミドルウェア
での処理を行う必要もありません。
早い段階で ConditionalGet で応答することで処理を効率よく
することができます。

Ruby on Rails での ConditionalGet

プログラマがたとえ特に何も指定しなくても、
Rack::ConditionalGet によって、適切に 304 Not Modified
応答する処理をしてくれます。

これは Rails アプリケーションによるレスポンスの生成処理が
すべて終わってから、Rack ミドルウェアが行うものです。
ネットワークトラフィックの節約にはつながります。
しかし、WEBアプリケーションサーバの CPU 負荷の低減にはなりません。

304 Not Modified を返すかどうかの判断をレスポンスの生成処理を行う
より早い段階で実行できれば、WEBアプリケーションサーバの
CPU 負荷の低減できます。

この目的のために ActionController::ConditionalGet モジュールの
fresh_whenstale? があります。

紙幅が限られているため、 fresh_when を使う例を簡単にだけ紹介します。

下記の例を見てみましょう。

 def show
   @article = Article.find(params[:id])
   fresh_when(@article)
 end

この簡単な例では、 Last-Modified@article.updated_at メソッドの値を
ETag@article.cache_key メソッドの値が使われます。

この簡単なコードによって、クライアントのキャッシュが fresh かどうかの
判定が行われ、 fresh であれば View のレンダリングをスキップし、
304 Not Modified を応答させることができます。

cache_key メソッドは知らない方もいるかもしれません。
そのレコードを一意に特定するための文字列を生成するメソッドで、
他にもフラグメントキャッシュを使うときなどにもこのメソッドは有用です。

まとめ

今回紹介した ConditionalGet のテクニックは
Ruby on Rails の優れた特長を表しています。

第1の特長として、プログラマが特に何も意識することなく、
自動的に透過的にフレームワークが適切に処理してくれる点です。
ネットワーク負荷の低減のための 304 Not Modified を応答する
ためのコードを一切書く必要がないのです。

第2の特長は、しかもそれをモジュール化された適用範囲の高い
技術によって実現している点です。この場合は、Rack ミドルウェア
という Rails に限らず適用可能な優れたモジュール化技術で実現されています。
Rack ミドルウェアで実現されているため、より高度な部品に交換することも
容易に可能となっています。

第3の特長は、デフォルトの動作に加えてさらに高度な処理をすることも
非常に簡単な点です。たとえば、プログラマがほんの少しのコードを
書き足すだけで、 fresh であれば View のレンダリング処理を
スキップさせることができます。

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
What you can do with signing up
63