PukiWiki bodycache改良版
ECO-Wiki (acronia)で使っている改良したPukiWiki bodycacheの改良内容とソースコード(パッチ)。
PukiWiki bodycacheについて
PukiWikiのWiki記法→HTMLの変換結果をキャッシュすることで、負荷を削減するパッチ/プラグインです。
詳しいことは下記ページを参照してください。
- bodycache
- PukiWiki bodycacheについて - WebArchive
- 一次配布元。2016/05/29現在ページ消失。
- PukiWikiでbodycasheを利用してHTMLコンバートタイムを70倍に高速化 | OXY NOTES
- PukiWiki bodycache導入の解説ページ。必要なファイルも配布されている。
PukiWiki bodycache改良版
※本記事内で特に断り無く「キャッシュ」と記載している場合、bodycacheが生成したキャッシュのことを指します。ブラウザが持っているキャッシュはブラウザキャッシュと記載。
bodycacheに対して以下の改良を実施した物です。
- キャッシュの自動再生成機能
- ページが更新されたとき、そのページを参照しているページのキャッシュも再生成。
- pcommentとincludeプラグインによる読み込みも別ページ参照と見なします。
- 参照ページをたどるのは、デフォルトでは2階層までです。関連→bodycache patch 2重include対応、PukiWiki 多重includeの深さ制限
- メニューバーの編集は特別扱いで全キャッシュを削除。*1
- ページが更新されたとき、そのページを参照しているページのキャッシュも再生成。
- 条件付きリクエスト If-Modified-Since 対応
- キャッシュの生成日時を、httpレスポンスのLast-Modifiedヘッダで返却。*2
- httpリクエスト時に、If-Modified-Sinceとキャッシュの生成日時を比較し、キャッシュが更新されていなければ 304 Not Modified を返却。
- これによりブラウザキャッシュが参照され、サーバー側の負荷が下がります。
- id重複対策
- メニューバーに見出しを使ったときにidが重複する問題に対応しています。
- ただし、かなり場当たり的な対応です。
- ページのタイトル(<title>要素、<h1>要素)もキャッシュ
副作用と既知の問題
以下の副作用・既知の問題があります。
- pcommentとincludeプラグインによるページ参照が、「関連ページ」として扱われるようになる
- ?plugin=related の出力などに影響
- メニューバーのidが、キャッシュが存在するときとしないときで異なる
- newプラグインの表示はキャッシュが再生成されるまで更新されない
- 304 Not Modified返却時はカウンタ(counterプラグイン)は動作しない
- ページ更新時に「タイムスタンプを変更しない」を有効にするとキャッシュが更新されない
また、AutoLinkは使っていないので、一緒に使ったときうまく動くかは不明です。
PukiWiki bodycache改良版 注意事項
bodycache改良版はECO-Wiki (acronia)にも適用している機能ですが、ECO-Wiki (acronia)では他の改造も加えているため、本記事で公開するにあたってbodycache改良版に関連する部分だけ抜き出しています。そのため、その際の作業ミス等でうまく動作しないコードになっている可能性もあります。本番環境で運用する前に、十分テストしてください。
問題を見つけた場合は本記事のコメント等で連絡ください。
PukiWiki bodycache改良版の適用
既に運用中のWikiに適用する場合、cacheディレクトリ内の、*.rel、*.refファイルを全て削除するか、?plugin=linksにアクセスしてページ間キャッシュを再生成してください。
patchコマンド
本記事の添付ファイルに含まれるbodycache.patchを用いて以下の通りpatchを当ててください。UTF-8版のPukiWiki 1.5.1用です。
$ ls bodycache.patch pukiwiki-1.5.1_utf8.zip $ unzip pukiwiki-1.5.1_utf8.zip $ cd pukiwiki-1.5.1_utf8 $ patch -p1 < ../bodycache.patch
手修正
patchコマンドがうまく使えない場合などのために、手修正による適用方法を記載していきます。
本記事の添付ファイルに含まれるbodycache.phpをlibディレクトリに格納し、以下のファイルを修正してください。
- pukiwiki.ini.php
- どこでも良いので以下の設定を追加します。
// Bodycache feature // enable bodycache or not // default : true $enable_bodycache = true; // use bodycache as default. If it's false, // #bodycache(enable) is required per page. // default : true $enable_bodycache_default = true; // If these (block) plugins are contained in page, bodycache will be disabled. // Users can increase this plugin list to control bodycache. // default : array( 'ls2', 'recent', 'popular', 'menu' ); $bodycache_disable_plugins = array( 'ls2', 'recent', 'popular', 'menu' ); // Specify search depth to delete old caches when page is updated. // 0 means no deleting. // default : 2 $bodycache_del_depth = 2; // Use last-modiefied time of $whatsnew (RecentChanges) for each wiki page. // If it's false, use only page cached time as wiki page last-modiefied. // default : false $bodycache_lastmod_whatsnew = false; //false or true
- どこでも良いので以下の設定を追加します。
- skin/pukiwiki.skin.php
- 最後の方に以下の記述を追加
- 変更前
<div id="footer"> Site admin: <a href="<?php echo $modifierlink ?>"><?php echo $modifier ?></a><p /> <?php echo S_COPYRIGHT ?>. Powered by PHP <?php echo PHP_VERSION ?>. HTML convert time: <?php echo elapsedtime() ?> sec. </div>
- 変更後
<div id="footer"> Site admin: <a href="<?php echo $modifierlink ?>"><?php echo $modifier ?></a><p /> <?php echo S_COPYRIGHT ?>. Powered by PHP <?php echo PHP_VERSION ?>. HTML convert time: <?php echo elapsedtime() ?> sec. <br /><?php echo bodycache_signature_gen() ?> </div>
- 変更前
- 最後の方に以下の記述を追加
- lib/convert_html.php
- id重複対策の記述を追加します。(不要なら省略可能)
- 最初の方にある以下の記述を修正。
- 変更前
function convert_html($lines) { global $vars, $digest; static $contents_id = 0;
- 変更後
function convert_html($lines) { global $vars, $digest; global $bodycache_status; static $contents_id = 0; if ( $bodycache_status === 'cached' ) { $contents_id += 90 ; }
- 変更前
- lib/link.php
- キャッシュ自動再生成のための記述を追加します。
- この部分で実際にやっている処理は、キャッシュファイルの更新日時の過去(1970年1月1日)への書き換えです。こうすることで、次回アクセス時にキャッシュが古いと判断されキャッシュが再生成されます。
- function links_updateの最初に以下の記述を追加。
- 変更前
function links_update($page) {
- 変更後
function links_update($page) { global $whatsnew; global $menubar; global $bodycache_del_depth;
- 変更前
- function links_updateの末尾~function links_initの直前に以下の記述を追加。
- 変更前
} // Init link cache (Called from link plugin) function links_init() {
- 変更後
// bodycache対応 if ( $page === $menubar ) { foreach (get_existfiles(CACHE_DIR, '.body') as $body_cache) { unlink($body_cache); } } else { links_bodycache_del($ref_file, $bodycache_del_depth); pkwk_touch_file( get_cachename($whatsnew) , 0); } } function links_bodycache_del($ref_file, $depth) { $depth--; if ( file_exists($ref_file) ) { foreach (file($ref_file) as $line) { list($ref_page) = explode("\t", rtrim($line)); $cachename = get_cachename($ref_page); if ( file_exists($cachename) ) { // cache exists. pkwk_touch_file($cachename, 0); } if( $depth > 0 ){ $ref_file_2nd = CACHE_DIR . encode($ref_page) . '.ref'; links_bodycache_del($ref_file_2nd, $depth); } } } } // Init link cache (Called from link plugin) function links_init() {
- 変更前
- キャッシュ自動再生成のための記述を追加します。
- lib/make_link.php
- pcommentとincludeをリンクと同様にたどるための修正をします。
- function InlineConverterを以下の通り修正。
- 変更前
function InlineConverter($converters = NULL, $excludes = NULL) { if ($converters === NULL) { $converters = array( 'plugin', // Inline plugins 'note', // Footnotes 'url', // URLs 'url_interwiki', // URLs (interwiki definition) 'mailto', // mailto: URL schemes 'interwikiname', // InterWikiNames 'autolink', // AutoLinks 'bracketname', // BracketNames 'wikiname', // WikiNames 'autolink_a', // AutoLinks(alphabet) ); }
- 変更後
function InlineConverter($converters = NULL, $excludes = array('pcomment','include')) { if ($converters === NULL) { $converters = array( 'plugin', // Inline plugins 'note', // Footnotes 'url', // URLs 'url_interwiki', // URLs (interwiki definition) 'mailto', // mailto: URL schemes 'interwikiname', // InterWikiNames 'autolink', // AutoLinks 'bracketname', // BracketNames 'wikiname', // WikiNames 'autolink_a', // AutoLinks(alphabet) 'pcomment', // pcomment 'include', // include ); }
- 変更前
- function convertを以下の通り修正。
- 変更前
function convert($string, $page) { $this->page = $page; $this->result = array(); $string = preg_replace_callback('/' . $this->pattern . '/x', array(& $this, 'replace'), $string);
- 変更後: '/x' を '/xm' にします
function convert($string, $page) { $this->page = $page; $this->result = array(); $string = preg_replace_callback('/' . $this->pattern . '/xm', array(& $this, 'replace'), $string);
- 変更前
- function get_objectsを以下の通り修正。
- 変更前
function get_objects($string, $page) { $matches = $arr = array(); preg_match_all('/' . $this->pattern . '/x', $string, $matches, PREG_SET_ORDER);
- 変更後: '/x' を '/xm' にします
function get_objects($string, $page) { $matches = $arr = array(); preg_match_all('/' . $this->pattern . '/xm', $string, $matches, PREG_SET_ORDER);
- 変更前
- function make_pagelinkの直前に以下の記述を追加。
- 変更前
// Make hyperlink for the page function make_pagelink($page, $alias = '', $anchor = '', $refer = '', $isautolink = FALSE) {
- 変更後
// #pcomment class Link_pcomment extends Link { var $anchor, $refer; function Link_pcomment($start) { parent::Link($start); } function get_pattern() { global $WikiName, $BracketName; return <<<EOD ^\#pcomment # pcomment plugin (?:\( ( # (1) PageName [^,\)]+ ) [,\)])? EOD; } function get_count() { return 1; } function set($arr, $page) { global $WikiName; list(, $name) = $this->splice($arr); if ($name == '') { $name = 'コメント/' . $page; if ( !is_page($name) ) { $name = 'Comments/' . $page; } } $name = get_fullname($name, $page); if (! is_pagename($name)) return FALSE; return parent::setParam($page, $name, '', 'pagename' , ''); } function toString() { return ''; } } // #include class Link_include extends Link { var $anchor, $refer; function Link_include($start) { parent::Link($start); } function get_pattern() { global $WikiName, $BracketName; return <<<EOD ^\#include # include plugin (?:\( ( # (1) PageName [^,\)]+ ) [,\)]) EOD; } function get_count() { return 1; } function set($arr, $page) { global $WikiName; list(, $name) = $this->splice($arr); $name = get_fullname($name, $page); if (! is_pagename($name)) return FALSE; return parent::setParam($page, $name, '', 'pagename' , ''); } function toString() { return ''; } } // Make hyperlink for the page function make_pagelink($page, $alias = '', $anchor = '', $refer = '', $isautolink = FALSE) {
- 変更前
- lib/pukiwiki.php
- 最初の方でbodycache.phpを読み込みます。
- 変更前
require(LIB_DIR . 'func.php'); require(LIB_DIR . 'file.php'); require(LIB_DIR . 'plugin.php'); require(LIB_DIR . 'html.php'); require(LIB_DIR . 'backup.php');
- 変更後
require(LIB_DIR . 'func.php'); require(LIB_DIR . 'file.php'); require(LIB_DIR . 'plugin.php'); require(LIB_DIR . 'html.php'); require(LIB_DIR . 'backup.php'); require(LIB_DIR . 'bodycache.php');
- 変更前
- 最後の方にbodycacheを呼び出す記述を追加します。
- 変更前
$vars['cmd'] = 'read'; $vars['page'] = & $base; $body = convert_html(get_source($base)); }
- 変更後
$vars['cmd'] = 'read'; $vars['page'] = & $base; global $enable_bodycache; if ( $enable_bodycache ) { $body = render_body( $base ); } else { $body = convert_html(get_source($base)); } }
- 変更前
- 最初の方でbodycache.phpを読み込みます。
- plugin/read.inc.php
- If-Modified-Since対応を追加します。(不要なら省略可能)
- function plugin_read_action内を、以下のように修正。
- 変更前
if (is_page($page)) { // ページを表示 check_readable($page, true, true); header_lastmod($page); return array('msg'=>'', 'body'=>''); } else if (! PKWK_SAFE_MODE && is_interwiki($page)) {
- 変更後
if (is_page($page)) { // ページを表示 check_readable($page, true, true); $ims = get_if_modified_since(); if ( $ims ) { $ctm = get_lastmodtime($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'=>''); } else if (! PKWK_SAFE_MODE && is_interwiki($page)) {
- 変更前
- ファイル末尾に、以下の内容を追記。
- 変更前
} ?>
- 変更後
} 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 ; } ?>
- 変更前
bodycache関連の設定
キャッシュ自動再生成時の参照階層数の変更
$bodycache_del_depth の値を変更してください。
- pukiwiki.ini.php
// Specify search depth to delete old caches when page is updated. // 0 means no deleting. // default : 2 $bodycache_del_depth = 2;
多重にinclude/pcommentを使っている場合は、$bodycache_del_depth を増やさないとキャッシュの再生成漏れが起こります。しかし、あまり大きな値を設定すると、編集時の負荷が増える恐れがあります。
そもそも、includeを多重に使うことを禁止したい場合は、PukiWiki 多重includeの深さ制限を試してみてください。
RecentChangesを考慮したキャッシュ制御
メニューバーで #recent プラグインを使っている場合など、RecentChangesページの更新時にブラウザキャッシュを更新されると都合が悪い場合は、$bodycache_lastmod_whatsnewをtrueに設定してください。
- pukiwiki.ini.php
// Use last-modiefied time of $whatsnew (RecentChanges) for each wiki page. // If it's false, use only page cached time as wiki page last-modiefied. // default : false $bodycache_lastmod_whatsnew = true; //false or true
この設定により、キャッシュ更新日時とRecentChanges更新日時の内、新しい方を更新日時として扱うようになります。
タイムスタンプ変更の強制
Wikiページ更新時の「タイムスタンプを変更しない」は、更新日時を判定条件にするbodycacheとの相性が良くないので、$notimeupdateの設定は0にすることを推奨します。
- pukiwiki.ini.php
///////////////////////////////////////////////// // Allow to use 'Do not change timestamp' checkbox // (0:Disable, 1:For everyone, 2:Only for the administrator) $notimeupdate = 0;
WikiNameを使用しない
WikiNameによる自動リンクが生成されるとその分負荷も増えるので、WikiNameが不要であればオフにすることを推奨します。
- pukiwiki.ini.php
///////////////////////////////////////////////// // _Disable_ WikiName auto-linking $nowikiname = 1;
おまけ: reload.inc.php
PukiWikiのpluginディレクトリにreload.inc.phpを格納し、「?cmd=reload&page=ページ名」にアクセスすると、指定したページのキャッシュが再生成されます。
ECO-Wiki (acronia)では、「リロード」リンクでこのプラグインを使っています。
このプラグインへのリンクを張る場合、検索エンジン等にクロールされて負荷が高くなる恐れがあることに注意してください。
更新履歴
- 2016/09/11
- ソースコード更新
- RecentChangesページの更新日時を考慮する機能を追加(デフォルトオフ)。
- ユーザーの変更を意図した変数をbodycache.phpからpukiwiki.ini.phpに移動。
- 更新されたファイル
lib/bodycache.php plugin/read.inc.php pukiwiki.ini.php
- ページ更新時の「タイムスタンプを変更しない」について注意を追加。
- pukiwiki.ini.php設定に関する説明を追加。
- ソースコード更新
- 2016/06/25
- PukiWiki 多重includeの深さ制限に関する記述を追加。
- 2016/06/14
- ソースコード更新
- typoを修正
- bodycacheのシグネチャを更新(この記事へのリンクにした)
- 副作用に「304 Not Modified返却時はカウンタ(counterプラグイン)は動作しない」を追加
- ソースコード更新
添付ファイル
- 2016/09/11版: bodycache_20160911.zip
- bodycache.patch: patchコマンド用
- bodycache.php: bodycache本体
- bodycache.patchにはbodycache.phpも含まれているので、patchコマンドでパッチを当てるなら不要
- reload.inc.php: おまけ
- 旧版
- 2016/05/29版: bodycache_20160529.zip
- 2016/06/14版: bodycache_20160614.zip
ライセンスはGPLv2 or laterです。(PukiWikiや元のbodycacheと同じ。)