tohokuaikiのチラシの裏

技術的ネタとか。

Apacheのmod_cacheが効かなかったので色々と調べた件

WordPressの負荷軽減にmod_cacheを使おうと思った。

で、こんな感じ

# a2enmod cache_disk
# systemctl start apache-htcacheclean
# systemctl restart apache2

で、/etc/apache2/site-availables/vhosts.confに

CacheRoot /tmp/apache/cache
CacheIgnoreCacheControl On
CacheIgnoreHeaders Set-Cookie
CacheEnable disk /
CacheDefaultExpire 300

を加える。/tmp/apache/cacheは、Apacheユーザーにするか777に。

ところが、効かない…

キャッシュされる感じが全くない。/tmp/apache/cacheにもファイルができない。

ん~~~~~~と、ログ見たりapacheの mod_cache の設定を復習した - うまいぼうぶろぐ見たりして気付いた。

Cacheが有効になるのは以下の条件がある。 http://httpd.apache.org/docs/2.4/caching.html

What Can be Cached?
The full definition of which responses can be cached by an HTTP cache is defined in RFC2616 Section 13.4 Response Cacheability, and can be summed up as follows:

  • Caching must be enabled for this URL. See the CacheEnable and CacheDisable directives.
  • If the response has an HTTP status code other than 200, 203, 300, 301 or 410 it must also specify an "Expires" or "Cache-Control" header.
  • The request must be a HTTP GET request.
  • If the response contains an "Authorization:" header, it must also contain an "s-maxage", "must-revalidate" or "public" option in the "Cache-Control:" header, or it won't be cached.
  • If the URL included a query string (e.g. from a HTML form GET method) it will not be cached unless the response specifies an explicit expiration by including an "Expires:" header or the max-age or s-maxage directive of the "Cache-Control:" header, as per RFC2616 sections 13.9 and 13.2.1.
  • If the response has a status of 200 (OK), the response must also include at least one of the "Etag", "Last-Modified" or the "Expires" headers, or the max-age or s-maxage directive of the "Cache-Control:" header, unless the CacheIgnoreNoLastMod directive has been used to require otherwise.
  • If the response includes the "private" option in a "Cache-Control:" header, it will not be stored unless the CacheStorePrivate has been used to require otherwise.
  • Likewise, if the response includes the "no-store" option in a "Cache-Control:" header, it will not be stored unless the CacheStoreNoStore has been used.
  • A response will not be stored if it includes a "Vary:" header containing the match-all "*".
  • CacheEnableとCacheDisable の指定
  • HTTPステータスが200/203/300/410以外なら"Expires" or "Cache-Control"で指定する
  • GETメソッドであること
  • Authorizationヘッダーが含まれる場合(Basic認証など)、Cache-Controlヘッダーにs-maxage/must-revalidate/publicが入っていること
  • Queryパラメータがある場合は、Expiresヘッダがで指定するかCache-Controlヘッダでs-maxageかmax-ageが入っていること
  • "Etag"か"Last-Modified"か"Expires"が入っていること。または、 max-age or s-maxageが入っているCache-Controlヘッダがあること。ただし、CacheIgnoreNoLastMod を使っている場合は除く
  • Cache-Controlでprivateを指定している場合、CacheStorePrivate オプションが有効になっていること。
  • Cache-Controlでno-storeを指定している場合、CacheStoreNoStore オプションが有効になっていること。
  • Vary: *があるとキャッシュされない

キャッシュされなかった理由は

Basic認証のページを見ていたから。

正確には、

  1. Basic認証のページを開く
  2. httpd.confでBasic認証を解除
  3. Apache再起動
  4. ブラウザでアクセス

としてたんだけど、ブラウザはBasic認証の情報を自動送信するのでブラウザを再起動させなければRequestHeaderにAuthorizationがそのまま記憶されて送られてしまうんだな。ハマった。

上記には、「 If the response contains an "Authorization:" header,」ってあるけど、これ「If the request」の間違いじゃないかな?文中には

Requests with an "Authorization" header (for example, HTTP Basic Authentication) are neither cacheable nor served from the cache when mod_cache is running in this phase.

ともあって、Basic認証を掛けている場合はキャッシュされない模様。

なんか、全部同じページになるんだけど…

なんだこれは…違うURLにアクセスしても全部同じコンテンツになっとる…

これか。

RewriteRule . /index.php [L]

RewriteRule . /index.php?cache_key=%{REQUEST_URI} [QSA,L]

にする。

しかし、なんかまだchromeでアクセスしたときにDeveloper Toolsを開けてるとキャッシュを取りに行き、そうでないとキャッシュ使わないとか挙動が不穏…

ということで、とりあえず現状で

.htaccess

RewriteRule . /index.php?cache_key=%{REQUEST_URI} [QSA,L]
#RewriteRule . /index.php [L]

テーマのfunctions.php

<?php
/* mod_cacheのための */
unset($_GET['cache_key']);
unset($_REQUEST['cache_key']);

// キャッシュ制御のためのLast-Modifiedヘッダ
add_action("template_redirect", function (){
    if( (is_home() || is_singular()) && !$_GET) {
        $span = 300;
        header('Expires: '. date('r', time() + $span));
        header('Cache-Control: public, max-age='.$span.', s-maxage='.$span);
        header(sprintf("Last-Modified: %s", get_the_modified_time("r")) );
    }
});

httpd.conf

    <IfModule mod_cache.c>
    <IfModule mod_cache_disk.c>
          CacheEnable disk /
          CacheRoot /tmp/var/apache2/cache
          CacheIgnoreHeaders Set-Cookie
          CacheIgnoreCacheControl On
          CacheIgnoreNoLastMod On
          CacheMaxExpire 600
          CacheDetailHeader On
    </IfModule>
    </IfModule>

うーん、CacheIgnoreCacheControl とかCacheIgnoreNoLastMod あたりはOffにしたいなぁ…とは思うけど、ブラウザ側の挙動に依存させたくないのでこれでいいか。

あと、CacheDetailHeader On これは良い。HTTPヘッダにキャッシュを使ったかどうかが分かる。そもそもサーバー側のキャッシュポリシーはCache-Controlヘッダで相手にはわかっているのでこれがユーザーに漏れたところで実害は無いと思う。