mod_expiresとmod_rewriteを使ってサイトの帯域節約と体感速度を向上させる方法

普通の帯域節約術としては、mod_deflateでdeflate圧縮するとか、CSSやJSファイルのHTTPレスポンスヘッダにLast-ModifiedやEtagを追加しておいて、ブラウザがHTTPリクエストヘッダにIf-Modified-SinceやIf-None-Matchを付加するようにし、コンテンツが変更されていなかったら304 Not Modifiedを返すという方法を取るかと思います。

しかし、HTTPサーバーはコンテンツの数だけ304 Not Modifiedを返さないといけないため、その分帯域を消費しますし、またCSSや画像などのパーツの304 Not Modifiedが返ってくるまで、そのパーツのレンダリングが行えないという問題があります(つまり体感速度に影響します)。

今回紹介するのはExpiresヘッダやCache-Control: max-age=31536000を活用して、CSSやJSなどのファイルをリクエストする際、それらのファイルが変更されない限り、If-Modified-Sinceなどのリクエストをそもそも発生させなくする方法です。

まず、Expiresヘッダの説明。これは、このHTTPレスポンスで返されたコンテンツの有効期限を指定するものです。例えばExpires: Sun, 08 Jun 2008 07:07:37 GMTと返ってくると、その日が訪れるまでブラウザはキャッシュの中身のものを使い続けます。つまり、Expiresで指定した日が訪れるまでIf-Modified-Sinceなどのリクエストをしなくなります。

Cache-Control: max-age=31536000の方は、31536000秒(365日)間はそのコンテンツが有効であると判断されます。つまり31536000秒間はブラウザのキャッシュから読み続けることになり、Expiresと同様にIf-Modified-Sinceなどのリクエストをしなくなります。

ApacheでExpiresヘッダやCache-Control: max-age=31536000を付加させるディレクティブは以下の通り(この例では1年後が有効期限に来るように設定しています)。

ExpiresActive On
ExpiresDefault "access plus 1 year"

ちなみに、RFC2068の14.21節によれば、HTTP/1.1サーバーは有効期限として「無期限」を指定したい場合は、「ちょうど1年後をExipresに指定すべき」とあり、また「HTTP/1.1サーバーは有効期限として1年以上を返すべきでない」とされています。

さて、こうしてExpiresヘッダを指定してしまうと、CSSなどを変更したときに、(ファイル名が同じである限り)Expiresで設定した有効期限が訪れるまでブラウザが新しいものを読みに行ってくれなくなるという問題が発生します。これを解決するのがmod_rewrite。

ここでのミソはファイル名が同じである限りキャッシュから読み続けるということ。つまりファイル名さえ変えてしまえば、新しいファイルとして取得してきてくれるわけです。

しかしながら、CSSの変更を加える度に毎回linkタグで読み込むCSSのファイル名を変えるのはものすごく手間がかかります。そこで、ファイルにバージョン名を含ませる(/assets/css/base.v20070609.css)のだけれど、実体は/assets/css/base.cssを読むようにすることが出来るのがmod_rewrite。その設定方法は以下の通り。

<Directory /home/httpd/vhosts/example.jp/current/public/assets>
RewriteEngine on

RewriteRule ^(.*?).vd+.(.*)$ $1.$2 [QSA,L]
</Directory>

こうすることで、実体の/assets/css/base.cssを更新したときに、
<link href="/assets/css/base.v20070609.css" rel="stylesheet" type="text/css" />
から
<link href="/assets/css/base.v20070610.css" rel="stylesheet" type="text/css" />
に変更すれば、ブラウザは変更後のファイルを読みに行ってくれます。

さて、これは応用編になりますが、RailsアプリなどでCapistranoを使うと、サイトが以下のようなファイル構成になります。

---current ->(20070609071925へのシンボリックリンク)
|
|-releases/
|-20070609071925/
|   |-public/
|       |-index.cfm
|       |-assets/
|           |
|           |-css/
|           |  |
|           |  |-reset.css
|           |  |-base.css
|           |
|           |-js/
|              |
|              |-protopype.js
|
|-20070609065605/

つまり、releases以下にリリースされたサイトが蓄積され、currentから最新版のリリースディレクトリへシンボリックリンクが張られている状態になります。

ここで、releases以下のディレクトリ名がユニーク(ぶつからない)ことを利用して、上のmod_rewriteのソリューションを使うことが出来ます。

つまり、アプリケーションロジックでcurrentシンボリックリンクのリンク先をresolveして(20070609071925などというディレクトリ名を取得し)、CSS読み込みタグをアプリケーションで下記のように動的に書き出す
<link href="/assets/css/base.v(リリースディレクトリ名).css" rel="stylesheet" type="text/css" />
ことで、Capistranoでサイトをリリースするたびに、自動で新しいCSSが読み込まれるようになります。

4 thoughts on “mod_expiresとmod_rewriteを使ってサイトの帯域節約と体感速度を向上させる方法”

  1. Railsだと、stylesheet_link_tagとかjavascript_include_tagとかimage_tagを使うとファイル名の後ろにタイムスタンプ?がつきますね。

    <script src=”/javascripts/prototype.js?1181698238″ type=”text/javascript”></script>

    Expiresはどうなってるんだろう。

  2. 本文を読んだのですが

    expiresが設定されているcssをCSSAとすると
    CSSAを更新する際に
    ・CSSAを呼び出すhtmlやcss(及びそれらを生成するサーバサイドプログラム)内ではCSSAのファイル名を変える
    ・サーバ側のファイルであるCSSAの実体はファイル名を変更しない

    という事を行おうとしているのだと理解しています。

    こういう事がやりたいのでしたらいちいちmod_rewriteなど使わずに、CSSAを呼び出す際
    base.css?ver=20070610
    とすれば良いだけじゃないでしょうか?

  3. たしかに、base.css?ver=20070610で事足りるような気もしています。
    あえて、base.v20070610.cssとする意味があるとしたら、例えばbase.v20070609.cssは別ファイルとしてブラウザにキャッシュされるので、誤って更新してしまった場合にブラウザキャッシュから復元できると言うことぐらいでしょうか。
    ただ、昨今はsubversionなどでソースはバージョンコントロールもしていることでしょうし、また限られたブラウザのディスクキャッシュを圧迫してしまうと言う弊害もありますので、mod_rewriteを使う手法はあまりよろしくないのかも。

    クエリーストリングを付けた際のユーザエージェントが取るべき挙動について記したRFCは何に当たるんでしょうね(ご存じでしたら教えてください)。
    仮に「クエリーストリングを付けた場合Expiresヘッダなどを無視する」と言ったような記述があった場合、この記事の目的を達成するためにはmod_rewriteを使う手法を使わざるを得ないことになります。

  4. http://journal.mycom.co.jp/news/2008/09/01/016/index.html

    >Jacob Hoffman-Andrews氏との議論を通じて「mylogo.1.2.gif」のように
    >ファイル名を変更する方法の方が効果的であることに気がついたという。
    >「mylogo.gif?v=1.2」のようにクエリを付加する方法ではSquidなどの
    >プロクシにキャッシュ効果がみられない。
    >ファイル名を変更する方法であればSquidはキャッシュとして機能するが、
    >クエリが付加されているとキャッシュが機能しない。

Leave a Reply

Your email address will not be published. Required fields are marked *