PR
リスト1●コメントの表示をテンプレートに組み込む<BR>個別記事を表示するテンプレートに,コメントの表示や入力機能を盛り込む。ここでは将来の拡張も想定して,トラックバックに関連するものも入っている。
リスト1●コメントの表示をテンプレートに組み込む<BR>個別記事を表示するテンプレートに,コメントの表示や入力機能を盛り込む。ここでは将来の拡張も想定して,トラックバックに関連するものも入っている。
[画像のクリックで拡大表示]
リスト2●コメントの登録&lt;BR&gt;POST呼び出しなら,コメントの登録ということになる。データを取り出して,Commentクラスのオブジェクトに格納し,それを保存している。
リスト2●コメントの登録<BR>POST呼び出しなら,コメントの登録ということになる。データを取り出して,Commentクラスのオブジェクトに格納し,それを保存している。
[画像のクリックで拡大表示]
リスト3●一つの記事か,複数の記事か&lt;BR&gt;テンプレート・エンジンの役割が増えたために,多少トリッキーなことをしている。「<!-- Loop Start -->」とある場合は,複数の記事が引数として渡されている。具体的には複数記事表示の場合と,RSS配信である。そうでなければ,引数として渡されるものは一つの記事であると仮定する。
リスト3●一つの記事か,複数の記事か<BR>テンプレート・エンジンの役割が増えたために,多少トリッキーなことをしている。「<!-- Loop Start -->」とある場合は,複数の記事が引数として渡されている。具体的には複数記事表示の場合と,RSS配信である。そうでなければ,引数として渡されるものは一つの記事であると仮定する。
[画像のクリックで拡大表示]
画面1●コメントの表示に成功&lt;BR&gt;デザインはLivedoor Blogの「Blue Frame」を使っている。
画面1●コメントの表示に成功<BR>デザインはLivedoor Blogの「Blue Frame」を使っている。
[画像のクリックで拡大表示]
画面2●トラックバックを送信するフォーム&lt;BR&gt;ごく普通のフォームでトラックバックを送信できる。
画面2●トラックバックを送信するフォーム<BR>ごく普通のフォームでトラックバックを送信できる。
[画像のクリックで拡大表示]
リスト4●トラックバックを受け付ける&lt;BR&gt;トラックバックは一つのページに対するリクエストの形で送られてくるので,Servletとして実装した。規約に従ったレスポンスを一応返している。
リスト4●トラックバックを受け付ける<BR>トラックバックは一つのページに対するリクエストの形で送られてくるので,Servletとして実装した。規約に従ったレスポンスを一応返している。
[画像のクリックで拡大表示]
リスト5●トラックバックの送信&lt;BR&gt;RubyのHTTP操作クラスを使ってリクエストを組み立てている。その際に簡単ではあるが, テンプレートを使っている。レスポンスのチェックはしていない。
リスト5●トラックバックの送信<BR>RubyのHTTP操作クラスを使ってリクエストを組み立てている。その際に簡単ではあるが, テンプレートを使っている。レスポンスのチェックはしていない。
[画像のクリックで拡大表示]
画面3●トラックバックを送信した結果&lt;BR&gt;dasBlogという別のブログツールに対して送信した。UTF-8を使ったら正しく表示されたが,本来どうあるべきかは検討課題の一つ。
画面3●トラックバックを送信した結果<BR>dasBlogという別のブログツールに対して送信した。UTF-8を使ったら正しく表示されたが,本来どうあるべきかは検討課題の一つ。
[画像のクリックで拡大表示]
図1●管理画面のイメージ&lt;BR&gt;このような管理画面があれば,管理しやすくなるはずだ。
図1●管理画面のイメージ<BR>このような管理画面があれば,管理しやすくなるはずだ。
[画像のクリックで拡大表示]
リスト6●エントリを消去する画面のテンプレート&lt;BR&gt;基本的にはエントリの編集と同じ。受け取ったServletがエントリを消去する。
リスト6●エントリを消去する画面のテンプレート<BR>基本的にはエントリの編集と同じ。受け取ったServletがエントリを消去する。
[画像のクリックで拡大表示]
リスト7●タグの除去&lt;BR&gt;正規表現を使ってタグを識別し,取り除いている。正規表現そのものは「Ruby Magic―Rubyで極める正規表現」にあったものをそのまま使っている。
リスト7●タグの除去<BR>正規表現を使ってタグを識別し,取り除いている。正規表現そのものは「Ruby Magic―Rubyで極める正規表現」にあったものをそのまま使っている。
[画像のクリックで拡大表示]
図2●作成したブログツールの全体像&lt;BR&gt;クラスおよびモジュールの関係を示している。TemplateHolderのように,最初に作った結果ほとんど無意味だが盲腸のように残った存在もある。
図2●作成したブログツールの全体像<BR>クラスおよびモジュールの関係を示している。TemplateHolderのように,最初に作った結果ほとんど無意味だが盲腸のように残った存在もある。
[画像のクリックで拡大表示]

 ブログ(Weblog)がいわゆる日記システムと異なるのは,「トラックバック」機能によるブログ間の連携が可能なところにある。そのためにはトラックバック処理の受け入れと発行の両方に対応しなければならない。またコメントもブログらしさの一つだ。コメントとトラックバックの管理方法はほぼ共通にした。さらに記事を管理する仕組みも加えた。ブログとして完ぺきだとは言わないが,十分な機能を備えたものを実装できた。

 最終回となる今回の目玉は,コメントとトラックバックの実装である。Weblog(ブログ)の特徴とも言える機能だ。トラックバックとは本来,似たような情報を掲載しているサイトを引用したりリンクしたときに,「こちらで引用していますよ」ということを逆方向に伝達するための仕掛けである。ブログにおける表示内容を考えると,コメントとトラックバックは基本的に同じようなデータを保持すればよいことが分かる。ただそれを入力する場所がWebページなのか,それともある特定のWebのリクエストなのかという違いである。さらにトラックバックに関しては,相手にそのリクエストを発行する仕組みも必要となる。

 もちろんそのほかにも,穴はいくつか空いている。例えば画像の表示。大体,ブログでは画像を一緒に送信できるようにしているケースが多い。それから,XML-RPCを使った各種サービス。Webブラウザからではなく,専用のクライアント・ソフトを使ってエントリを登録したり,グローバルなWebのPingサーバーに対してPingを発行するといったあたりだ。管理画面も作っていない。

第6ステップ
コメント入力を可能に

 まずコメントの入力と表示の部分に取り組んだ。個々のコメントに必要な情報は,対応する記事のIDと,コメント投稿者,そのURL,メールアドレス(表示するかどうかは別として),コメント本体,コメント登録日時である。コメントの「数」が情報として必要なので,これをコメントの管理とは別にインデックスのように持たせておく。「最近のコメント」などを実装するなら,別途登録順を管理するインデックスも必要かもしれない。

 ここで定義するコメントの枠組みは,トラックバックで送られてくる情報とほぼ同じだ。だから,コメントの実装さえできれば,トラックバックはできたも同然ということになる。実際この考え方は間違いではなかったが,それほど正しくもなかった。

 コメントの表示をする部分を再びライブドアBlogのテンプレートを参考に修正する(リスト1[拡大表示])。当たり前だが「<CommentsLoop>」といった,ループの処理がある。これもテンプレートに組み込むことを考えなければならない。テンプレート処理の入り口は一つ。しかし,「<!-- Loop Start -->」があるのは複数記事の表示部分だと識別できるので,これを使って振り分ければ何とかなりそうだ。

コメントの受け入れ許諾で凡ミス

 次にコメント入力フォームの表示・非表示などを制御できるようにする。そのため記事にコメントやトラックバックの受信許諾を指定するプロパティを追加した。ただこの処理に手間取ってしまった。もちろんごく単純な処理で,記事を更新・追加するときにラジオボタンで指定するだけなのだが,ラジオボタンの値を取得するときに,また「=」と「==」を間違えた。

 なぜかラジオボタンのデフォルト値(CHECKED)が入っていてもうまく表示できなかったこともあってそちらに原因があると読み違え,なかなか特定できず往生した。例えばエンコーディングを指定したり,それに合わせて文字コードを変更してみたりなどの操作を試し,値が設定通りにならないことに悩んだ。

 またこの作業を通じて記事の追加・更新画面のデータ更新がハード・コーディングだったのを,テンプレート・エンジンを使うように修正。我ながらくだらないミスとか,やり残しの作業を忘れていたりするのでイヤになる。

 ここまで進んで,ようやくコメントを受け取るServletの実装である。個別記事を表示するeachServletのPOSTメソッドの処理部を追加し,ここでコメントを受け取るようにする(リスト2[拡大表示])。save Commentするところで,個数情報(インデックス)の更新もしている。

 表示に関しては結局,テンプレート・エンジンへの組み込みを試みた。前述のように,「<!-- Loop Start -->」でループを表現するのは「記事群を表示するもの」であると考え,それの有無で引数の解釈を変えるという方針を採った(リスト3[拡大表示])。多少トリッキーで危ない橋を渡っている気がしないでもないが,呼び出しのフローを押さえておけば問題ないだろう。

 むしろこの処理を実装するにあたり迷ったのは,PStoreの仕様が予想と多少違っていた部分である。公開されているリファレンス・マニュアルによると,PStoreクラスのオブジェクトにはfetchメソッドがあり,

 fetch('key',デフォルト値)

でもしkeyの値が存在しなければデフォルト値を返すとある。しかしデフォルト値を「Hash.new(Hashクラスに新規オブジェクト)」や「Array.new」などと指定しても,結局nilが返される。

 一番可能性が高いのはスコープの問題で,もっとアトミックな値であれば問題なく返してくれるのかもしれない。しかしオブジェクトの生成となると,スコープとしてはそのメソッド呼び出しで完結してしまい,オブジェクトは消滅したことになる。したがってそのポインタが指し示すものはnilとなる,ということだろう。ただ,PStoreはオブジェクト格納のためのものであり,デフォルト値の指定ができるメリットはこういう使い方ではないかと個人的には思う。結局キーから値を取得するときにnilかどうかをすべてチェックするという古典的な対応策を採った。

 これらの処理をした結果,画面1[拡大表示]に示すようにコメントの表示や入力もできるようになった

第7ステップ
トラックバックを実現する

 トラックバックの実装は大きく二つ。トラックバックの受け入れと発行である。トラックバックの受け入れに関しては,基本はコメントの実装と変わらない。あとはどうやってトラックバックが実施されるかを調べればよい。トラックバックの仕様については,ブログツールの大本命である「MovableType」を開発した米SixApart社が仕様書を公開している。これに従えばよい。

Six Apartが公開している仕様 http://www.sixapart.com/pronet/docs/trackback_spec
Iwata Yasushi氏による邦訳 http://lowlife.jp/yasusii/stories/8.html

 仕様書を見るまではXMLの文書をHTTPを使って送り合うのかと思っていたが,そうではなかった。フォームを通じてデータを登録する処理に似ている。データの渡し方がフォームを通じて入力データを受け取るのと同じなのだ。つまり送信の方式を「POST」として,送信する相手のURLを指定する。このとき,「Content-Type」を「application/ x-www-form-urlencoded」と指定する。ポストするフォームには「title」「url」「excerpt」「blog_name」という入力項目があるものとして,それぞれの値を入れて引き渡すのである。

フォームを通じてデータを受け取るのと同じ

 つまりこれはフォームを通じてデータを渡すのとまったく同じである。だからHTMLを使ってフォームを定義すれば,トラックバックを発行できる(画面2[拡大表示])。送信元と実際の記事のURLが同じかどうかは関係がない。これでトラックバックの受け入れ動作の確認もできる。

 このリクエストを受け取るServletを実装する。例えば「trackback?id=xxx」で受け取るようにするわけだ。そのServletで「title」や「excerpt」などを取り出して,トラックバック情報を格納しておけばよい。ただPOSTで受け取ったときにURLにエンコードした引数の情報はリクエスト・メッセージの中に入らない。WebrickではURLそのものは受け取れるので,idを受け取るため呼び出し方を「trackback/xxx」としてidをURLから識別できるようにした(リスト4[拡大表示])。

 返信のフォーマットも決まっているので,ここではハード・コーディングしてしまった。エラーを返すのはトラックバック元のURLがない場合や,トラックバック元のURLがおかしい場合。Rubyでは行指向の表現(ヒアドキュメントと呼ぶ)ができるので,このような処理も気軽に記述できる。多少視認性が落ちる気はするが*1

HTTPライブラリを直接操作

 次にトラックバックの送信である。トラックバックを送信するのは,記事を追加・更新するときである。フォームに記入したURLに対してトラックバックを発行する。したがって実装するのはeditEntryのdo_POSTメソッドの中ということになる。

 処理としてはトラックバック用のメッセージを組み立てて,HTTPを使って送信すればよい。幸いRubyにはURLを処理するためのライブラリ「URI」や,HTTPで送信するためのライブラリ「net/http」が標準で付属する。これを使えばそんなに難しくはなさそうだ。

 まず送信する内容(Ping)の組み立てである。ここでもテンプレート・エンジンの機能を拝借してデータを埋め込む。こうすることでデータの埋め込み処理を一貫したものにできる。このため送信するPingのデータをテンプレートの形式で作成し,それにテンプレート・エンジンの一部を使ってデータを埋め込んでいる(リスト5[拡大表示])。次にトラックバック送信URLの指定から個々のURLを抽出し,それに対してPingを送る処理を記述する。前者についてはURIライブラリの機能に適切なものがあるのでそれをそのまま使った。ターゲットとなるトラックバックURLの指定については,個々の記事をURLのqueryで指定するものもあれば,ディレクトリ・パスで指定するものもある。一応両者に対応できるように,query文字列があればそれを付加するように記述した。

 ただここで悩んだのが,エンコーディングである。仕様書はISO-8859を指定しているが,これでは日本語が送れない。最初はURLなんだから,URI用のエンコードをすればよいのかとも思った。だがいくつかほかの資料などを参照していたら,どうやらUTF-8で送るらしいことが分かった。

 Trackback Pingが正しく送信できているかどうかは,別のBlogシステムに送信することで試した。通信の相手はdasBlogという.NET Frameworkで動くブログツール。以前に編集部内の連絡用に利用していたもので,日本語化についてはUTF-8に統一することで実現している。これを見る限り,どうやら成功したようだ(画面3[拡大表示])。ちなみに画面上の妙な表示は,EUCでトラックバックを受けた結果である。

第8ステップ
いくらか完成度を高める努力をする

 詳細は触れないが,これ以外に画像データの投稿や入力データの簡単なチェック,月別アーカイブの表示などを組み込んだ。基本的に盛り込みたいことはこれで終わりだ。たださすがに,管理用の画面がまったくないというのも正直気に入らない。イメージとしてはこんな感じで操作できればよいだろう(図1[拡大表示])。

 本来はさらにそこから,ユーザー/パスワード管理や,テンプレート・ファイルの入れ替えなどをオンラインでできるように管理する操作もあるべきだ。ただそれよりも,いろいろなところに残っている個人的な気持ち悪さに対応する方を優先させた。

 例えばTemplateHolderの作りである。どのみちテンプレートをロードすることは分かっているので,初期化時点でテンプレート・ファイルを指定する方が正しいあり方だ。初期化時点で別のダミーのテンプレートからデータをロードし,その後実際に使うテンプレートをロードするというムダな処理をしていたのでこれを修正した。

削除確認用テンプレートも作成

 またエントリの削除に関しては,やはり削除用のテンプレートを定義して実装した(リスト6[拡大表示])。認証が必要なServletはまとめて,認証に関する処理を備えた親クラスから継承するように変更。このときにインデックスの処理にバグがあることにも気づいたので,合わせて変更する。以前は編集状態に入った時点でインデックスを抜く処理をしていたが,これだと編集をキャンセルした場合(サブミットしないで別のサイトに移動するなど)に抜けたままになってしまう。セッションレスという形態はこういう場合が面倒だ。

 そこで仕方がないので,ID情報に基づいて更新する際に前のインデックスを取り除くように変更した。あまり美しくないが,仕方がない。これもDBMSを使わなかったことが原因である。

 ただこれを実装して気が付いたことがある。画像データを含むエントリのトラックバックの概要(excerpt)にHTMLタグが含まれてしまう場合があるのだ。一緒にコメントやトラックバック受信の際に受け取った内容をそのまま出すと,クロスサイト・スクリプティングのターゲットになりかねないことにも気が付いた。

 気が付いた以上は対処しなければならない。具体的には,excerptの送受信からタグを取り除くことと,コメントの本体からタグを取り除くことである。タグの除去には,『Ruby Magic—Rubyで極める正規表現(竹内,『Ruby Magic-Rubyで極める正規表現』,オーム社,2002年9月)』におけるhtmlutil.rbにある正規表現を参考にした(Ruby Magic—Rubyで極める正規表現サポート・ページ http://www.namaraii.com/rubymagic/)。ただそこで使っていた方法(破壊的な置換)だと,タグが入っていない場合などにデータがなくなってしまう可能性があるので,やり方を少々変更している(リスト7[拡大表示])。

 全体を通しては,図2[拡大表示]のようなシステム構成になった。規模はそれなり(全体で1254行)だが,Rubyでそれなりの規模のプログラムを組むのは初めてだった割りには,予想外にすんなり作れたように思う。Rubyというプログラミング言語の筋のよさが感じられた。

 ただしインターネットで野生化させるにはまだ作り込みが足りない。機能面で足りないだけではない。レンタルサーバーを利用するには,Servletを起動するための仕組みの問題がある。またWebrickに適用したパッチがおそらくサーバーで提供されるRubyには施されていないので,メソッドを動的に変更する仕組みを組み込む必要もある。認証の仕組み自体を変更すべきかもしれない。実用化するにはこういったさまざまな問題を解決しなければならないし,おそらくバグも多々残されているのではないかと思う。だが,当初の目的である,「ブログの仕組みを理解する」には十分なものになったのではないだろうか。

 ブログの中核はテンプレート・エンジンであり,そこをいかに拡張しやすく実装するかがカギである。以前dasBlogのソースを見たときに,かなりトリッキーなことをしていると感じた部分があった。Rubyのような動的に評価する機構を標準で備えているプログラミング環境であれば,それがこれほど簡単にできると分かったことも収穫である。