Jan 30, 2005

Red Lobster@深沢

例によって唐突にSteamed Lobsterを食べたくなったので、近所のレッドロブスターに出かけることにした。ちなみに私は熱心な「ロブラー」(=ロブスター好き)でもある。最寄の「ロブポイント」は、駒沢というか深沢にある。自宅からtechi-techi歩いていけなくもない距離だが、歩いていくには寒すぎる夕方だった。

Red Lobster | シーフードディナーレストラン レッドロブスター
Google マップ - 東京都世田谷区深沢6-2-15

念願のSteamed Lobsterにもありつけ、着いたときにはがら空きだった店内が時間が経つにつれてご家族連れで満杯になっていく感動的な風景も観られた。よい食事だった。もし私に子供ができたならごく早い段階でこの店で「ロブデビュー」させてあげたい(そのときまで店があれば…)。

それにしてもSteamed Lobsterを食べる人はあまり多くはないようだ。深沢という土地柄ゆえ、もっと熱心な「ロブラー」がいるかと思ったのだがそうでもなく、Steamedのときだけテーブルに出動するスタッフ「殻割り君」(彼はその特殊な技能を他の職場で活かせるのだろうかと気になって仕方がない…)が結構暇していた。あとは焼き牡蠣はメニューにあるのに生牡蠣がないのは不満な点。

レッドロブスターもいつも間にやらイオン(ジャスコ)グループからレインズ・インターナショナル(=牛角)グループに変わっていた模様だが、それにメゲることなく従来のマイルドで怠慢な出店ポリシーを維持していってもらいたい。また、グルメ情報サイトなどでRLの評価が低いのはLive Lobsterを食べていないせいだと推察されるのであまり真に受けるべきではない。この際なので、

「冷凍ロブスターがパサパサするのは当たり前だろうがっ!!」

と強く強く念押ししておきたい。肉付きプレートのおまけのロブスターなどではなく、Steamed LobsterをメインにShrimp、Crabのサイドディッシュを注文するのが正しい「ロブラー」。値段を見れば分かりそうなものだが。

Jan 29, 2005

recently_pinged_on Plugin

この際なので随分前に作ったMTEntriesコンテナにrecently_pinged_onというオプションを追加するプラグインも公開しておきます。

recently_pinged_on_Plugin - ogawa - Google Code

recently_commented_onと同様に下のような感じで使えます。

<dl>
  <MTEntries recently_pinged_on="5">
    <dt><a href="<$MTEntryLink$>"><$MTEntryTitle$></a></dt>
    <MTPings lastn="5">
      <dd><a href="<$MTPingURL$>" rel="nofollow">
          <$MTPingBlogName$>: <$MTPingTitle$></a></dd>
    </MTPings>
  </MTEntries>
</dl>

既存のMTタグにオプションを追加する技法の習作です。

Jan 28, 2005

Quasi-Spam Filter Plugin

コメントスパム受信時、トラックバックスパム受信時のリアクションを複数サポートする、スパムフィルタプラグインを公開します。

Movable Type 3.2以降では、標準で付属しているSpamLookupプラグインの利用をお勧めします。Quasi-Spam Filter Pluginは3.2以降でも動作しますが、SpamLookupなどと同時に使用した場合の振る舞いは保証できません。また、今後のバージョンアップなどは予定していません。

QuasiSpamFilter_Plugin - ogawa - Google Code

コメントスパム受信時、トラックバックスパム受信時のリアクションを複数サポートする、スパムフィルタプラグインです。実用にも堪えると思います(私自身も使っています)が、スパムフィルタのリファレンス実装を目的としています。このプラグインでは、自作のスパムフィルタを作りたい、もしくは作っているという方にも利用いただけるようなBuilding Blocksを提供できればと考えています。

Jan 25, 2005

私のコメントスパム対策

「コメントスパム対策」と言うとき、それは複合的な意味を持ちます。コメントスパムがブログ内に表示されるのを避けたいのか、管理画面の「コメント一覧」に表示されるのすら避けたいのか、あるいはコメント投稿によって発生する再構築の負荷を低減したいのか?

このエントリーでは私がやっているコメントスパム対策について述べます。

番組の途中ですが緊急速報です。mt-comments.cgiに極めて重大な欠陥が見つかったようです。スパムメールの踏み台にされるという、とんでもない欠陥です。対策が発表されたら全員がすぐに適用する必要があります。

MT-Blacklist -> Hijacked comments.cgi

というわけで対策出ました。必ずパッチ適用をしましょう。

Movable Type Publishing Platform: Movable Type 3.15 released
Movable Type 日本語版サイト: 【重要】 Movable Typeの脆弱性と対策について

STEP 1: 「TypeKey + 承認付き」にする

単に「コメントスパムがブログ内に表示されるのを避けたい」のであれば、TypeKeyを用いた投稿だけを許すか、それに加えて「承認(事前確認)付き」でのコメント投稿を許すようにすればよいだけです。承認付きであるだけで、スパマーのスパミングに対するインセンティブは非常に小さなものになります。

STEP 2: 特定の文字列を含むコメントをdenyする

ときどき見かけるのは、ASCIIのみのコメントをdenyするパッチやプラグインです。私自身も以前同じ目的のプラグインを作りました(単純にアプリケーションレベル・コールバックの機能を試したかっただけですが)。

Ogawa::Buzz: Application-level Callbacks in MT3.1

しかし、個人的にはASCIIのみのコメントをdenyしたくはありません。そもそも英語のブログを書いているのならdenyすべきではありませんし、この日本語ブログに英語やポルトガル語のコメントが付くこともあります。投稿者の端末の都合も考慮したいところです。最近は非ASCII文字を混ぜてくるSpambotもあるらしいので実効性にやや疑問を感じます。

私がやっているのは、コメントスパムに現れる特定の文字列に着目して、それを含むコメントをdenyするという方法です。上記のエントリーのものを若干変更して以下のようなプラグインを使っています(下記はプラグインのコア部分だけ)。

use strict; 
MT->add_callback('CommentFilter', 10, 'Reject Spam Comments', sub {
    my ($eh, $app, $comment) = @_;
    return ($comment->text !~ /PATTERN/i);
}); 
1;

PATTERN」の部分を明示していませんが、この部分には例えばコメントスパムに含まれがちな特定のタグを書いておくと非常に効果があります。以下のエントリに具体例を含むプラグインを公開しています。

Ogawa::Buzz: Quasi-Spam Filter Plugin

STEP 3: 再構築の負荷を低減する

「TypeKey + 承認付き」にしている場合、MT 3.121までのバージョンでは承認付きコメントが投稿されたときにも再構築が発生します。したがって、STEP 2の方法でdenyできない限り、DoSとしてのコメントスパムは依然有効です。また、スパムでなかったとしても承認時点で再構築されるのが望ましいでしょう。MT 3.14では再構築が発生しませんが、「コメントが登録されたら(メールで)通知する」ように設定していても、承認付きコメントに対して通知されなくなります。

まとめると承認付きコメント投稿時の動作は、バージョンによって以下のように異なります。

承認付きコメント投稿時の動作
バージョン再構築の有無通知の有無
3.121までありあり
3.14なしなし
望ましい動作?なしあり

3.14の動作でもよいのかもしれませんが、私自身は再構築「なし」、通知「あり」というのが好みなので、以下のようなパッチを当てています。このパッチはMT 3.121用です。

--- lib/MT/App/Comments.pm.bak 2004-11-24 18:43:21.000000000 +0900
+++ lib/MT/App/Comments.pm 2005-01-22 17:12:13.000000000 +0900
@@ -346,29 +346,25 @@
             $blog->touch;
             $blog->save;
 
-            # Rebuild the entry synchronously so that if the user gets
-            # redirected to the indiv. page it will be up-to-date.
-            $app->rebuild_entry( Entry => $entry )
-                or return $app->error($app->translate(
-                                      "Rebuild failed: [_1]", $app->errstr));
-            # Index rebuilds and notifications are done in the background.
-            MT::Util::start_background_task(sub {
-                $app->rebuild_indexes( Blog => $blog )
+            if ($comment->visible) {
+                # Rebuild the entry synchronously so that if the user gets
+                # redirected to the indiv. page it will be up-to-date.
+                $app->rebuild_entry( Entry => $entry )
                     or return $app->error($app->translate(
                                           "Rebuild failed: [_1]", $app->errstr));
-                my $send_notfn_email = 0;
-                if (!$commenter) {
-                    $send_notfn_email = !$comment->visible();
-                } else {
-                    $send_notfn_email = !$commenter_has_comment
-                        && !$comment->visible();
-                }
-                if ($blog->email_new_comments || $send_notfn_email)
-                {
+                # Index rebuilds and notifications are done in the background.
+                MT::Util::start_background_task(sub {
+                    $app->rebuild_indexes( Blog => $blog )
+                        or return $app->error($app->translate(
+                                              "Rebuild failed: [_1]", $app->errstr));
+                });
+            }
+            if ($blog->email_new_comments) {
+                MT::Util::start_background_task(sub {
                     $app->_send_comment_notification($comment, $comment_link,
                                                      $entry, $blog);
-                }
-            });
+                });
+            }
         }
     }
     MT::Util::start_background_task(

Jan 19, 2005

No Follow, No Life

たかがIndexingのクオリティを上げるトリック(しかも実質的にユーザーやアプリケーションにオフロードしただけ)を10年来の技術革新と呼んでしまうのはどうかと思いますね。まあアプリケーションベンダーがYesと言っているのだからいいのだという言い方もできますが、この調子でやっていればMachine writableだけどHuman writableでないHTMLの拡張仕様が出来上がってしまいます。

ITmediaニュース:Googleなど、リンクスパム対策に乗り出す――「ノーフォロー」タグを導入へ

最近のネタだとTechnorati Tagsあたりも結構怪しいと思っています。Technorati TagsではA要素のrel属性の名前空間を実際「無制限に」「混沌と」使ってしまいます。A要素やLINK要素のrel属性というのはそもそもリンク先の当該文書に対する「関係」を記述するものであって、一般的な「アノテーション」を記述するものではなかったはずです。スタイルシートを示すLINK要素に対して「rel="stylesheet"」と付記するように「ごく秩序立って」利用されてきたはずなのですがね。

そういうわけで将来この属性を使ったまともな拡張ができなくなる可能性もあるでしょう。Google他が「rel="nofollow"」とか言い出したのは、この名前空間を自分たちの都合のいいように早いうちに予約してしまった方がいいんじゃないか、という判断が働いているように思えます。

同じ仕組みを使って、Webブラウザがrel="nofollow"のリンクにアクセスするときにはRefer(r)erを渡さないようにすることも可能ですが、そちらの方が技術的にも社会的にも重要な意味があります。と言うよりもですね、こういう仕様を標準化してWebブラウザにあまねく採用していただき(いやまあそれは無理だとして仮に採用されなかったとしても提案・標準化したことには意義がある)、その仕様を(不肖Google、Yahooも)Indexingに流用させていただきます、という話の方が筋が良いと思います。話としてアサマシクなく、むしろスガスガシイです。

と書きながら自分でもアサマシクも"nofollow"を指定したりしているわけですけれど、私が対象にしているのはPing URLへのリンクだけで、それ以外のところに指定するとリンクの意義を損ないかねないと考えています。ですが、そういう思惑とは逆にと言うか無関係に、安直に指定してしまう人が増える気もしています。特にプラグインはその傾向を加速しがちですよね。つまるところ、お互いにリンクの効果を弱め合うチキンレース(?)の始まりだとも言えます。ブログはリンクの効果が強すぎたのは確かで、だからこそスパマーにも付け狙われやすくもあり、この時点で多少是正される方がよいのかもしれません。が、残念ながらこれはみんなで不幸になるレース、あるいは胴元だけが儲かる博打です。

つまりですね、サーチエンジンやポータルは、それを支える技術やマネーの規模がいつの間にやら増大し、淘汰が進み、ほとんど寡占が完成しつつある状況です。しかもそれらは広告料を主収入としているわけです。いずれこのシステムはテレビや新聞同然(ビジネスモデルはすでに同然です)となるでしょう。あなたが自分のブログにAdSenseを付ける時、それは二次広告収入というメリットをもたらすかもしれませんが、広告の製品やサービスに対してネガティブなことを書けば広告が表示されないように「技術的には」できますし、サーチエンジンのインデックスから削除することも「技術的には」できます。それが嫌なら製品やサービスの宣伝をするしかありません。つまりあなたは『Googleテレビ』の出演者・制作者になったのです。そしてまた今、あなたはスパムコメントが減るという餌に釣られていますが、これは一義的には検索結果の質を上げる(=広告の効果を上げる)ための意味しかありません。「立ち位置をもう少し右にすると画面映りが良くなるよ」という甘言の裏に「そこに立たれたままだと後ろのスポンサーの文字がよく見えない、スポンサーが煩いから何とかしろ」という意図があることに出演者はなかなか気が付かないものなのでしょうか。

さて、もう一度身近な経験を元に自問してみるとよいでしょう、

『スパムフィルタリングを施したからと言って、
 自分のところに送られるスパムメールが本当に減ったのか?
 減ってないとしたら誰が儲かったのか?』

おまけですが、Movable TypeのNofollow pluginを作ろうとしていたのですが、先越されてしまいましたー。しかし、ダイナミック・パブリッシング用のプラグインが私のところでは正常に動きません。

Movable Type Publishing Platform: Movable Type 'nofollow' plugin

コメント欄にも書いてありますが、MTPingsコンテナタグ内部に対する処理が怪しいです。これはちょっと考えたら理由が分かりました。

なぜなら、Nofollow pluginの処理ではまずサニタイズを行い、その結果生成物に対してrel属性を追加しているからです。この前処理のサニタイズでは、Sanitize Specで示されたルールに従ったタグおよび属性しか通過させません。

サニタイス: 入力された文字を安全に

ですから、MTPingsコンテナの内部にこのルールにマッチしない要素が含まれれば、そのタグ部分は除去されます。デフォルトでは、A要素に対してはhref属性だけを通過させることにしていますから、そのままではname属性やrel属性、title属性を通過させることができません。また、やはりデフォルトでは<br />は通過しますが、<hr />は通過させません。そういうわけでユーザーに自分の要求にミートするようなSanitize Specを定義しなくてはならないわけです。

基本的にはMTPingsコンテナ内に何を書くかはユーザーの自由ですし、それにミートするようなSanitize Specを指定したとすれば、それは結局「サニタイズ」として意味があるのかしら、と思います。もう少し説明を加えると、下の赤い部分の方が青い部分よりハードな制約が必要なはずですが、実際には同じSanitize Specが適用されるということです。つまり、赤の部分に合わせれば青の部分の記述力が制約されますし、青に合わせれば赤への制約としては意味がないでしょう。

追記1: よく考えたら、MTPingTitle、MTPingURL、MTPingBlogName、MTPingExcerptには予めサニタイズがかかっているじゃないか。だから上の図で赤の部分に危険なコードは含まれない。ということは、危険があるとしたら青い部分であってMTPingsをまるごとサニタイズするのはまったく意味がない。

したがって、nofollow.plの27行目あたりにあるtags_to_filterのPingsの行をコメントアウトした上で、テンプレートのMTPingsの部分に以下のどちらかの修正を加えればよい。

<MTPings nofollowfy="1">
<p id="p<$MTPingID$>">
&raquo; <a href="<$MTPingURL$>"><$MTPingTitle$></a> from <$MTPingBlogName$><br />
<$MTPingExcerpt$> <a href="<$MTPingURL$>">[Read More]</a>
</p>
<p class="posted">Tracked on <$MTPingDate$></p>
</MTPings>
<MTPings>
<p id="p<$MTPingID$>">
&raquo; <a href="<$MTPingURL$>" rel="nofollow"><$MTPingTitle$></a> from <$MTPingBlogName$><br />
<$MTPingExcerpt$> <a href="<$MTPingURL$>" rel="nofollow">[Read More]</a>
</p>
<p class="posted">Tracked on <$MTPingDate$></p>
</MTPings>

追記2: 一応、こんなBookmarkletも作っておきました。"nofollow"なA要素を見つけてcolorをlimeにします。

Show "nofollow" (Drag this and drop your brower's menu bar)

Jan 13, 2005

MSN Search RSS Feeds

宮川さんのところ(MSN Search Results as RSS: blog.bulknews.net)で知りましたが、MSN Search betaがRSSを生成するようになっていました。

RSS Feeds for Search Results

早速、Ogawa::Buzz: mt-search.cgiを捨てて簡単メタサーチにしてみようでやっていた、サイト内検索にGoogle Web APIsを使う方法を放棄しました(待てど暮らせどうちのサイトにはGoogleがクロールに来てくれないので役に立たないのデス)。で、こっちに乗り換えました。

どういうことかと言うと、例えば、「小川」をサイト内(as-is.net内)検索したい場合、

http://beta.search.msn.co.jp/results.aspx?q=%E5%B0%8F%E5%B7%9D+site:as-is.net&format=rss

というURLで検索結果のRSSが得られます(「%E5%B0%8F%E5%B7%9D」は「小川」をUTF-8に変換してURLエンコーディングしたもの)。したがって、このRSSをMagie RSS(Magpie RSS - PHP RSS Parser)などを使って読み込んで表示するだけで、真っ当なサイト内検索として機能してくれるというわけです。

ざっくり書くとこんな感じ。

<?php
define('CHARSET', '<$MTPublishCharset$>');
define('BLOG_HOST', '<$MTBlogHost$>');
define('BLOG_SITE_PATH', '<$MTBlogSitePath$>');

$search = isset($_GET['search']) ? htmlspecialchars(trim($_GET['search'])) : "";

echo <<<EOD
<form method="get" action="{$_SERVER['PHP_SELF']}">
<h3>サイト内の検索</h3>
<p><input name="search" size="30" value="{$search}" /><input type="submit" value="Search" /></p>
</form>

EOD;

if ($search) {
  $qstring = urlencode(mb_convert_encoding($search, 'utf-8', CHARSET));
  $cond_site = urlencode("site:" . BLOG_HOST);

  echo "<h2>MSN Search betaのサイト内検索結果:</h2>\n\n";

  require_once BLOG_SITE_PATH . 'rss_fetch.inc';
  define('MAGPIE_CACHE_DIR', BLOG_SITE_PATH . 'cache');
  define('MAGPIE_OUTPUT_ENCODING', CHARSET);
  $url = "http://beta.search.msn.co.jp/results.aspx?q={$qstring}+{$cond_site}&format=rss";
  $rss = fetch_rss($url);
  if ($cnt = count($rss->items)) {
    echo "<p>{$cnt}件見つかりました。</p>\n\n";
    echo "<ul>\n";
    foreach ($rss->items as $item) {
      $title = htmlspecialchars($item['title']);
      $url = htmlspecialchars($item['link']);
      $snippet = htmlspecialchars($item['description']);
      echo "<li><a href=\"$url\">$title</a>\n<div class=\"note\">$snippet</div></li>\n\n";
    }
    echo "</ul>\n";
  } else {
    echo "<p>見つかりませんでした。</p>\n\n";
  }
  echo "<p><small>[Powered by <a href=\"http://blogs.msdn.com/msnsearch/archive/2005/01/11/351064.aspx\">RSS Feeds for MSN Search Results</a>]</small></p>\n\n";
}
?>

上のコードはMovable Typeのテンプレートに張り込むことを考慮して、一部MTタグを使って書かれていますが、先頭で定義しているCHARSET、BLOG_HOST、BLOG_SITE_PATHという三つの定数を適当に変更するだけで、大抵のサイトで利用できます。

Feed2jsやprocfeedのように貼り付け用のJavascriptを生成してくれるサービスを利用してもよいのですが、こうしたサービスは総じて負荷が集中しやすく動作速度も遅いため、ページ全体のレスポンスを悪化させるので、私は使っていません。

Jan 11, 2005

ロリポおじさんの秘密

ロリポのレンタルサーバーを使い始めて一年足らずですが、一台のマシンを面倒見るのに比べて細かいサーバー管理に気を使わなくてよい反面、当然できることも少なく、とりわけ私にとってはサーバーの設定を直接に見られないというのはストレスでもあります。このエントリーでは私がロリポを使っていて「んんー?」と思った事柄について書きます(と言っても実質Webサーバーしか使えないので、Apache関連の設定だけです)。

  1. 拡張子plに対してcgi-scriptハンドラが結び付けられています(AddHandler cgi-script pl)。このため、*.plファイルに実行権限を付与してあるとCGIスクリプトとして動作してしまいます。パーミッションには十分注意する必要があります。

  2. 拡張子rdfに対してserver-parsedハンドラが結び付けられています(AddHandler server-parsed rdf)。このため、*.rdfファイルにアクセスしたときにSSI (Server Side Include)ファイルとして処理します。

    RSSファイルにindex.rdfという名前を付ける慣例に従うなら、このindex.rdfファイルはSSIファイルとして処理されます。SSIによってファイル内容が改変されるなどの副作用はまずあり得ませんが、若干効率が悪くなります。また、Last-Modifiedヘッダが返らないため、RSSをフィードするアプリケーションから最新更新日時が正常に得られないという問題があります。

これらのデフォルト設定をクリアするには.htaccessに以下のように記述します。

AddHandler default-handler pl
AddHandler default-handler rdf

Jan 8, 2005

「条件付きGET」のススメ

一般にApacheに代表されるHTTP 1.1サーバーは、Webブラウザが通常のHTMLファイルにアクセスした時に、Last-Modified(更新時刻)ヘッダとETag(更新時刻などから生成されたハッシュ値)ヘッダを返します。次回以降のアクセスでは、この両ヘッダにセットされた更新時刻やハッシュ値が異なる場合だけコンテンツのダウンロードを行い、そうでない場合にはローカルキャッシュを参照することでトラフィックを削減できます(Shift+リロードなどの特定の操作をした場合には無条件でGETされます)。この機能は「条件付きGET(Conditional GET)」と呼ばれており、RFC2068: Hypertext Transfer Protocol -- HTTP/1.1などに動作が規定されています。一方、PHPファイルなどの動的コンテンツにアクセスした時には、上記の両ヘッダを返されないため、そのままでは条件付きGETが行われません。

さて、Movable Type(に限りませんが)でファイルの拡張子を.phpにしている例は結構見かけますが、この「条件付きGET」に配慮している例はあまり見かけません。Webブラウザはこれらのサイトに対して毎回無条件にGETを行っており、無駄にトラフィックやサーバー資源を消費し、レスポンス時間も犠牲にしているはずです。また、Last-Modifiedヘッダを返さないサイトはSEO的にも不利になる可能性があります。なぜならある程度クレバーなクローラーならば、サーバー負荷を抑えるために動的コンテンツの収集を控え目に行うロジックが含まれているはずだからです。

無条件にGETすることに意味がある場合もあります。例えば、PHPで書かれたアクセスカウンターなどのように毎回異なる結果を生成すべき場合がそうです。しかし、再構築時間の短縮を目的として単にモジュール化しているだけのことであれば、無条件にGETすることによるメリットはありません。

そんなわけで、このエントリーではSimon Willison: Supporting Conditional GET in PHPを参考にして、PHPファイルに対して「条件付きGET」を有効にするための方法を述べます。ここで述べているのはスタティック・ページに対する方法です。ダイナミック・パブリッシング(デフォルトではすべて無条件GET)で条件付きGETを有効にするのはもう少しスマートにできますから、興味のある方はダイナミック・パブリッシング: 条件付きリクエストを参照してみてください。

単純なケース

そもそもLast-Modifiedヘッダを返すわけですから、「更新日時」を何らかの方法で決定する必要があります。

まず、一番簡単なケースとして「そのPHPファイルの更新日時」を「更新日時」とする場合を考えます。この場合、以下のコードをファイルの先頭に記述することで条件付きGETが実現できます。doConditionalGetの定義は上記のSimon Willisonのものをわずかに変更してあります。

注意: 「<?php」の前に(空白や改行を含め)文字を入力してはいけません。ファイルの「先頭」に記述することは「必須」です。
<?php
$ts = getlastmod();
doConditionalGet($ts);
 
function doConditionalGet($timestamp) {
  // A PHP implementation of conditional get, see 
  //   http://fishbowl.pastiche.org/archives/001132.html
  $last_modified = gmdate('D, d M Y H:i:s T', $timestamp);
  $etag = '"'.md5($last_modified).'"';
  // Send the headers
  header("Last-Modified: $last_modified");
  header("ETag: $etag");
  // See if the client has provided the required headers
  $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ?
    stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) :
    false;
  $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
    stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : 
    false;
  if (!$if_modified_since && !$if_none_match) {
    return;
  }
  // At least one of the headers is there - check them
  if ($if_none_match && $if_none_match != $etag) {
    return; // etag is there but doesn't match
  }
  if ($if_modified_since && $if_modified_since != $last_modified) {
    return; // if-modified-since is there but doesn't match
  }
  // Nothing has changed since their last request - serve a 304 and exit
  header('HTTP/1.1 304 Not Modified');
  exit;
}
?>

少し複雑なケース

一つのページをモジュール化して複数のファイルに分割し、それを「<?php readfile("..."); ?>」などしてインクルードしている場合には、上に示したコードでは不十分です。

例えば、サイドバー部分をモジュール化してファイルに書き出し、メインのPHPファイルではそれを読み込んでいる場合を考えます。そうすると上記のコードではメインのPHPファイルが更新されたときにしか、「更新日時」が更新されません。メインコンテンツに比較してサイドバーの更新頻度が高い場合には(おそらくこれがモジュール化した理由と推察されるわけですが)これでは不都合があるでしょう。

どちらかと言えば、メインのPHPファイルの更新日時とサイドバー部分の更新日時のうち新しい方をこのPHPファイルの「更新日時」とした方が合理的です。これは、上記のコードの先頭部分を以下のように書き換えることで実現できます。「<$MTBlogSitePath$>left-column.php」、「<$MTBlogSitePath$>right-column.php」は、インクルードしているファイル名に適宜読み替えてください。

<?php
$ts = getlastmod();
$ts_list[] = getlastmod();
$ts_list[] = filemtime('<$MTBlogSitePath$>left-column.php');
$ts_list[] = filemtime('<$MTBlogSitePath$>right-column.php');
sort($ts_list, SORT_NUMERIC);
$ts = array_pop($ts_list);
doConditionalGet($ts);

もっと複雑なケース

…は、考えていません。だいたい上の2つの例で分かったかと思いますが、doConditionalGetに与える「$ts」というタイムスタンプ変数に更新日時となる値をセットすればよいのです。

Jan 5, 2005

I-O DATA WN-G54/R2

3年間毎日使い続けていた自宅の無線LANステーション(メルコのブロードバンドルーターBLR2-TX4 + 無線LANカードWLI-PCM-L11G)が年末ついに壊れました。おかげで帰省先では無線なし年越しですよ。おまけにAirH"も入らないという田舎だし。

うちのマンションの場合、単にアクセスポイントがあればよいし(ルータは要りません)、またa/b/gじゃなくてb/gで十分なのですが、最近は廉価モデルはルータ付きの方が安いくらいなのですね。ビックカメラでもアクセスポイントを探す方が大変くらい製品数も少ないし。仕方がないので売り場でもひときわ売りたくなさ気、買われたくなさ気な空気を漂わせていた↓の無線LAN機能付きブロードバンドルータを買ってみました。

I-O DATA WN-G54/R2 IEEE802.11g/b 無線LANルーター
I-O DATA WN-G54/R2 IEEE802.11g/b 無線LANルーター

何もここまでしなくてもというくらい小さいです。マグネット付きとかカユイところに手が届く部分も含めて機能は十分でしょう。なんせ私はまだ802.11bですし。これで何とか2年くらいは凌げるでしょう。

Jan 4, 2005

キムブログの全文引用に関して

完全に出遅れてますが、これも年末に書いたエントリーです。

木村剛の書くことが私にとって1 picoたりとも意味があった試しはないので、その内容に関して突っ込むことはしない。私が興味を持っているのは、以下のエントリーに寄せられたさまざまなトラックバックである。

週刊!木村剛 powered by ココログ: 「引用」は「リンク」に対する冒涜なのか?

個人的な意見を述べておくと、まず大方の予想通りではあろうが、標準的な引用規則(引用元を明示すべしとか、引用を必要最小限に留めるべしとか、引用に対して本文が主となるべしとか)を定めてそれを共通に運用することが望ましいという考え方に基本的には賛意を示したい。多く議論されている通り、これらの規則はすでに社会的に「スタンダード」としてのコンセンサスを得ており、運用されてもいる。

が、同時にそうした「Good Citizenshipを構成する」スタンダードはGoodなCitizenの間で共通に運用・通用するものであったとしても、そうしたCitizenshipを積極的にか無作為にか結びたがらないBad Citizenも容易に存在し得る、と考えるのが自然であろう。もう少し踏み込んで言うならば、(否定されるべき存在ではあるかもしれないが)そうしたBad Citizenの存在は、Good Citizenの存在と同程度に「当然」である。専ら全文引用したい人と全文引用されたい人「だけ」からなる奇妙な空間が構成できないわけでもないし(c.f. 専ら匿名投稿したい人「だけ」からなる奇妙な空間=2ちゃんねる)、そうした人々が「引用」に関して一般には通用しない論理を展開するのも自由である。彼らの払うべき犠牲は「信頼し得る情報源ではない」という烙印であるが、実世界では「信頼し得ない」出版物が日々出回っており、かつ(単に活字になっているというただそれだけの理由で)それを真に受ける人々も相当数いるという事実に目を背けることはできない。

むしろ、Winnyのように原著作者の権利の行使を困難ならしめる方法、あるいは不可能ならしめる方法を採っていないかどうかが問題である。木村某はそのような方法は採っておらず、無作為な全文引用を避ける方法(トラックバックしない、不本意に全文引用されたらクレームする)が原著作者には担保されているように見える。もっともその運用の信頼性はまた別の問題としてあるだろうし、トラックバックされた記事以外から全文引用したり、PDFファイルという形態の作品を無断で改変して掲載している例がないわけではない。

さて、こういう話の流れではGood Citizenの溜飲は一向に下がらないわけだが、原則論を繰り返すというのは決して美徳ではないのでここは是非とも抜本的な解決を提案したい。私が提案するのは、木村剛のブログを全文引用する「だけ」のブログをボランティアで運営するという甚だ「ネットワーク社会」らしい解決方法である。つまり、こういうことである。

  1. 「原典を明示した上で『引用』されることは、『情報発信者としての名誉』」らしいので、木村ブログを粛々と引用するだけのブログがあってもよい。ゆめゆめ「情報発信者に対する権利侵害」とゴネられることもあるまい。逆に「感謝されるのではないかと推察」されるくらいである。
  2. 引用ブログに対してトラックバックする分には原ブログの「引用規則」が適用される心配はなく、したがって無作為に全文引用される危惧もない。

問題はその手間だ。

Jan 3, 2005

A little hack for MTCommentFields

年末に書いたエントリーですが、今更公開しておきます。

MTCommentFieldsはstaticオプションによって生成されるコメントフォームにstaticという名前のhiddenフィールドを生成します。

  • static=0の場合:
    <input type="hidden" name="static" value="0" />
    
  • static=1の場合、またはstaticオプションが未設定の場合:
    <input type="hidden" name="static" value="1" />
    

このstaticフィールドは、フォームのサブミット後、およびTypeKeyサイン・イン/サイン・アウト後、に誘導するページを「mt-comments.cgiが生成するコメント・リスト」にするか、「個別エントリーアーカイブ」にするかを選択する機能を持ちます。

問題になるのは、staticオプションとしてあらかじめ定められた静的な値(0/1)しか設定できないということです。ここで自然な拡張を試みます。

mt31-comment_fields.patch

このパッチは、MTCommentFieldsのstaticオプションの振る舞いを以下のように変更します。

  • static=0の場合:
    <input type="hidden" name="static" value="0" />
    
  • static=1の場合:
    <input type="hidden" name="static" value="1" />
    
  • staticオプションが未設定の場合:
    <input type="hidden" name="static" value="MTCommentPreviewIsStaticの値" />
    

つまり、MTCommentFieldsにstaticオプションを与えない場合、直前のフォームで入力されたstaticの値に従ったinputフィールドを生成します。これにより、コメントポストのエントリポイントとなるテンプレート(「mt-comments.cgiが生成するコメント・リスト」または「個別エントリーアーカイブ」)においてstaticかdynamicかを指定すれば、その後遷移するすべてのフォームにおいてその指定が有効になります。

このパッチは以下のエントリにあるようなコメンティングシステムを支援することができます。

小粋空間: ポップアップ画面を用いたコメント投稿(その3:公開テンプレート修正方法)

またそれだけではなく、誤ったstatic値をテンプレートに記述してしまうことで生じる混乱を避けることができます。例えば、個別エントリーアーカイブではstatic=1としてあるのに、コメントプレビューやコメントエラーのどちらか、あるいは両方にstatic=0と記述すると、コメント後に誘導されるページが状況によって異なるという混乱が生じます。が、こうしたテンプレート記述時の人為的なミスを幾分自動的に避けられます。

About Me

My Photo

つくばで働く研究者

Total Pageviews

Amazon

Copyright 2012 Ogawa::Buzz | Powered by Blogger
Design by Web2feel | Blogger Template by NewBloggerThemes.com