2015/03/26(木)PukiWiki If-Modified-Since(条件付きリクエスト)対応

PukiWiki If-Modified-Since(条件付きリクエスト)対応

別の日記に少し書いてますが、ECO-Wiki (acronia)をIf-Modified-Since(条件付きリクエスト)に対応させたので、その方法をメモしておきます。

If-Modified-Since(条件付きリクエスト)とは

If-Modified-Sinceを使うと、ページが更新されている時だけデータを送って欲しい、という要求ができます。

  • 初回アクセス
    1. [クライアント] ファイルください。
    2. [サーバー] はいどうぞ。ファイルの更新日時は「Wed, 25 Mar 2015 15:07:33 GMT」。(200 OK 応答)
  • 2回目以降:更新されてない場合
    1. [クライアント] 更新日時「Wed, 25 Mar 2015 15:07:33 GMT」のキャッシュ持ってるけど最新版ある?(If-Modified-Sinceフィールド)
    2. [サーバー] 更新されてないから、内容は送らない。そのキャッシュ使って。(304 Not Modified 応答)
  • 2回目以降:更新されている場合
    1. [クライアント] 更新日時「Wed, 25 Mar 2015 15:07:33 GMT」のキャッシュ持ってるけど最新版ある?(If-Modified-Sinceフィールド)
    2. [サーバー] 最新版あるので送る。(200 OK 応答)

PukiWikiでIf-Modified-Since対応する場合の問題

PukiWikiの場合、各ページの更新日時をどのような基準で決めるかが問題となります。

  • ページを編集した日時にした場合:pcommentやinclude先、メニューバーが更新されても、そのページは更新されたと見なされない
  • ページを構成しているファイル郡の中の最新更新日時にした場合:正しく動作するが、追加の処理が必要。負荷も増える。

対応方針

この問題は、bodycache適用時の問題と似ています。そこで、今回はbodycacheファイルの更新日時を対応するページの更新日時と見なすようにしました。

  • bodycacheファイルはpcommentやinclude先が更新されたときもアップデートされるようにしているので、更新見落としが起こらない
    • bodycache patch適用範囲拡大を実施していることが前提
    • メニューバーの問題については、メニューバー更新のたびにbodycacheを全削除*1することで対応

改造内容

主にread.inc.phpを書き換えます。

  • lib/bodycache.php
    • キャッシュファイルの更新日時を元にLast-Modifiedヘッダフィールドを返す関数
      • どこに書いても良いですが、bodycacheを前提にした記述なので、bodycache.phpに書きました。
        // Last-Modified header
        function header_lastmod_cache($page = NULL)
        {
            global $lastmod;
        
            if ( is_cache($page) && get_cachetime($page) != 0 ) { //get_cachetime==0の時は、そのキャッシュは破棄予定なので無視
                pkwk_headers_sent();
                header('Last-Modified: ' .
                    date('D, d M Y H:i:s', get_cachetime($page)) . ' GMT');
            } else if ( is_page($page) ) { //キャッシュが存在しない問いは現在時刻を使う
                header('Last-Modified: ' .
                    date('D, d M Y H:i:s', UTIME) . ' GMT');
            }
        }
        
  • plugin/read.inc.php
    • If-Modified-Sinceフィールドヘッダの内容をunixtimeに変換して返す関数
      function get_if_modified_since()
      {
          static $unixtime = null ;
          if ($unixtime !== null ) {
              return $unixtime ;
          }
      
          if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
              $unixtime = false ;
              return false ; // If-Modified-Since なし
          }
          
          $unixtime = strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
          return $unixtime ;
      }
      ?>
      
    • plugin_read_action関数内の改造
      • 変更前
        if (is_page($page)) {
            // ページを表示
            check_readable($page, true, true);
            header_lastmod($page);
            return array('msg'=>'', 'body'=>'');
        
      • 変更後: 更新の有無を確認して、更新されていなかったら304 Not Modifiedを送って処理を打ち切ります。また、header_lastmod_cacheでLast-Modifiedフィールドを送るようにします。
        if (is_page($page)) {
            // ページを表示
            check_readable($page, true, true);
            
            $ims = get_if_modified_since();
            if ( $ims ) {
                $ctm = get_cachetime($page);
                if ( $ctm && $ctm <= $ims) {
                    // 更新されていない
                    header('HTTP/1.1 304 Not Modified') ;
                    header_lastmod_cache($page);
                    exit;
                }
            }
        
            //header_lastmod($page);
            header_lastmod_cache($page);
            return array('msg'=>'', 'body'=>'');
        

*1 : メニューバー更新と同時に、bodycacheファイルの更新日時を現在時刻に書き換えてしまう手もありますが、間違えて古いbodycacheを最新扱いしないように注意が必要です。