2016/09/11(日)RecentChangesページを考慮したブラウザキャッシュ制御

PukiWiki: RecentChangesページを考慮したブラウザキャッシュ制御

PukiWiki If-Modified-Since(条件付きリクエスト)対応のコメントでやりとりしていた、RecentChangesページを考慮したブラウザキャッシュ制御の機能をbodycache改良版に追加しました。

PukiWiki bodycache改良版のページで公開しています。なお、追加機能を使うには設定が必要です。*1

RecentChangesを考慮したページ更新日時

基本的な方針は、「Wikiページキャッシュ更新日時とRecentChanges更新日時のうち新しい方」をページの更新日時と見なすというものです。

以下の関数がこの新しい方の更新日時を返す関数です。$bodycache_lastmod_whatsnewはpukiwiki.ini.php内の設定で、$bodycache_lastmod_whatsnewがtrueの時のみこの機能は動作します。

  • bodycache.php
    // Get last-modified time for client side cache control
    function get_lastmodtime($page)
    {
    	global $bodycache_lastmod_whatsnew;
    	global $whatsnew;
    	static $lastmodtime = null ;
    	if ($lastmodtime !== null) {
    		return $lastmodtime ;
    	}
    
    	$cachetime = get_cachetime($page);
    	if ( $cachetime != 0 && $bodycache_lastmod_whatsnew ) {
    		$whatsnewtime = get_filetime($whatsnew);
    		$lastmodtime = ( $cachetime >= $whatsnewtime ) ? $cachetime : $whatsnewtime;
    	} else {
    		$lastmodtime =  $cachetime;
    	}
    
    	return $lastmodtime;
    }
    

この関数が返す日時を、ブラウザに返却するLast-Modifiedや、If-modified-sinceの比較に用いることで、RecentChanges更新時にはブラウザキャッシュも更新される動作になります。

追記

get_lastmodtimeは複数回呼ばれるので、処理量を減らすために1回目に呼ばれたときの結果を$lastmodtimeに残して2回目以降は$lastmodtimeを返すだけにしているのですが、今の書き方では引数の$pageを変更して呼ばれると正しく動作しなくなってしまいます。

今のコードでは、$pageを変更してget_lastmodtimeを呼ぶことはないので問題は起こりませんが、get_lastmodtimeの引数を変えてはいけないという制約ができてしまっているのであまり良くないですね。(うっかり忘れて将来事故を起こしそう^^;)そのうち修正するかもしれません。

*1 : 機能が不要な場合に負荷が増えないように。

その他のbodycache改良版の更新

更新のついでに、bodycache.php内の不要な記述の削除とコードインデントのタブへの統一*2も実施しました。

また、bodycache.php内にあった設定用の変数$bodycache_del_depthをpukiwiki.ini.phpに移動しました。

*2 : PukiWikiのコードが基本的にタブだったので。

2016/03/20(日)PukiWikiを1.5.1に更新

PukiWikiを1.5.1に更新

ECO-Wiki (acronia)で使っているPukiWikiは2015/03/25のスナップショットでしたが、PukiWiki 1.5.1 がリリースされたのでバージョンアップしました。

改造箇所があるのでdiff-patchで適用しましたが、大きな修正が必要な衝突もなく上手く動いているようです。

続きを読む

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を最新扱いしないように注意が必要です。

2015/02/03(火)bodycache patch 2重include対応

pukiwiki bodycache patch 2重include対応

bodycache 2重include

以前、#includeを含んだページで更新時にキャッシュが再生成されるような改良を行いましたが、この改良では上図のように#includeや#pcommentが2重に使われていると、キャッシュが正しく再生成されないことが分かりました。

bodycache 2重include 修正前

このような場合、一番深いページを更新したときに、最上位のページのキャッシュが再生成されずに残ってしまいます。これは、キャッシュを再生成するページを探すときに、#includeや#pcommentを更新されたページから1段しかたどっていないために発生する現象です。

そこで、更新時に2段までページをたどってキャッシュを再生成するように変更しました。これで、2重include/pcommentまでなら正しくキャッシュが再生成されるようになります。

bodycache 3重include 修正後

ただし、3重include/pcommentには未対応です。対応は難しくありませんが、たどる段数を増やすとその分負荷も増えるので、2重までの対応としています。

2015/02/03(火)見出しタイトル化のバグと続・PukiWiki bodycache patch副作用:id重複

見出しタイトル化のバグ

タイトルプラグイン(PukiWiki)導入に合わせて、最初に出現したレベル1見出しをタイトル扱いにする改造を行いましたが、バグがあったようです。(季節イベントページをみていて気づきました。)

  • 発生条件: includeプラグインが使われており、includeで参照しているページにレベル1見出しが含まれている
  • 現象: レベル1見出しが含まれる最後のincludeがタイトルとして使われてしまう。
  • 原因: includeを使うとconvert_htmlが複数回呼ばれることを考慮していなかった。その結果、後で呼び出されたconvert_htmlにより見出しから抽出したタイトルが上書きされていた。
  • 修正前: Heading->Heading() 内の改造箇所
    if( $this->level === 1 && !isset( $root->title ) ){
        $root->title = preg_replace('|\s*\[.*|', '', $text);
        $root->title = preg_replace('|^\*\s*|', '', $root->title);
    }
    
  • 修正後: Heading->Heading() 内の改造箇所
    //初回呼び出し時は $root->id は 1
    if( $this->level === 1 && !isset( $root->title ) && $root->id === 1 ){
        $root->title = preg_replace('|\s*\[.*|', '', $text);
        $root->title = preg_replace('|^\*\s*|', '', $root->title);
    }
    

また、includeプラグインの参照先ページにtitleプラグインが含まれている場合も、同様にincludeしたページのタイトルが優先されてしまうと思われます。ただ、このような使い方はたぶんしないと思うので、こちらの問題はそのままにしておこうかと思います。

続・PukiWiki bodycache patch副作用:id重複

PukiWiki bodycache patch副作用:id重複で適用した修正ですが、不十分だったようです^^;

原因は、やはりconvert_htmlが複数回呼ばれることを考慮していなかったことで、

function convert_html($lines)
{
	//省略
	static $contents_id = 0;
	global $bodycache_status;
	if ( $bodycache_status === 'cached' ) { //bodycacheから取得済み
		++$contents_id;
	}
	//省略
	$body = & new Body(++$contents_id);
	//省略
}

この修正では、includeによる2回目のconvert_html出力と、メニューバーの$contents_idが共に2になってid重複してしまいます。

ものすごく場当たり的ですが、以下のように再修正しました。

	if ( $bodycache_status === 'cached' ) { //bodycacheから取得済み
		$contents_id += 90 ; //これだけ足せばきっと重複しない・・・(^_^;)
	}

いっそ、メニューバーでは見出しの自動id生成をやめた方が良い気もしてきましたが、メニューバーだけ自動id生成をやめる方が修正は面倒そうです。(もしかしたら、本文も自動id要らないかも?)

OK キャンセル 確認 その他