はてなwebサービスAPIを用いたPerl/Ruby webアプリケーション2題
文・RyuK ( )
2007年01月11日
webサービスというものが新しい技術として流行ったのは2001年頃だった。そこで語られていたビジョンというのは、WSDLで定義したサービスのAPIをUDDIに登録し、それらがSOAPで通信しながらweb上に広がるアプリケーションを構成するというようなものである。MS Windows的に言うと、レジストリに登録されたCOMコンポーネントのインターフェイス発見/呼び出しメカニズムのインターネット版ということになる。ただし、Windowsの場合は、DCOMやCOM+といった、Windowsシステム同士のネットワークやWindowsシステム内部を一貫した分散オブジェクトRPCの文脈でとらえる仕組みを経て、一度レガシーを整理し、.NETに至る。それをビルディングブロックとして利用するとして宣伝されたHailstormというマイクロソフト提供のプラットフォームは、個人向けwebサービスをUDDI経由で提供するという触れ込みだった。その後Hailstormは、シングルサインオン認証をめぐる覇権争いに巻き込まれた挙げ句、開放された世界での商業的キーワードとしては消滅し、同時期にローンチして以来コントロールされた閉鎖環境で運営されてきているシングルサインオンの理想型が有料サービスXbox Liveとして存続している。
その時点まで一旦遡ってから改めて俯瞰すると、最近のwebサービスを巡る再評価の流れもそれがどうしたと斜に構えやすい。XMLも5年くらい前に流行り、当時はXMLスキーマが今後重要になると言われていたが、そのあたりの知識が役に立つ局面が以後拡大したとは思えない。そういうわけで、この辺りの物事は個人的な関心からは長いこと外れていた。2007年の現在それらはつまらない玩具のようなものに留まるのか、それとも個人ユーザにとって真に使える道具なのか。近頃の各種webサービスは、webサービスのAPIを開放し、プレスリリースを出して客寄せを行い、利用者が増えたら閉じるという、提供企業による手軽な宣伝活動の産物であり、個人ユーザ囲い込みの道具である。それを利用する個人の動機はweb広告業界に投じられている金で、その量が5年前とは相当異なる以上、以前とは位相が異なってはいるわけだ。webサービス同士の競争が起こり良質なサービスが数多く提供されるまでになれば面白くなるかもしれない。しかし、webサービスを介して提供される元ネタが数少ない企業に独占されている現状では、そんなシナリオの蓋然性は低く、バリエーションの少ない似通ったwebサービスを「マッシュアップ」などと称してデプロイする個人は、いわば企業の走狗として活動しながら細々と広告収入を稼ぐことになる。
今回の試みでは、「はてな」の認証webサービスAPIと、「はてな」のキーワードwebサービスAPIを利用している。1つ目のwebアプリケーションは、「はてな」認証APIを利用した、「はてな」ユーザが特定の他ユーザに外部サーバを介して任意のデータファイルを渡すためのファイルアップローダである。言語はRubyで、RubyのwebサーバMongrelのモジュールとして書かれている。もう1つのwebアプリケーションは、はてなキーワードAPIを利用した穴埋めクイズ作成と、はてなキーワード連想グラフの視覚化を行う。言語はPerlで、PerlのwebサーバPOE::Component::Server::HTTPのモジュールとして書かれている。
これらは、双方ともスクリプト内でwebサーバをインクルードするので、コンソールからrubyなりperlなりを通してスクリプトを実行すればそのまま動作し、ホスト用のwebサーバを別途必要としない。起動時に設定ファイルの内容を読んで、以降はwebサーバの一部としてリクエストに応じるwebアプリケーションである。前回のPerlとRubyの比較ではマルチスレッドと組み込みがポイントだったのに対し、今回は、webアプリケーションの作成と簡易webサーバという、webアプリケーションをめぐる環境の比較を行うという趣向だ。マルチスレッドに関しては、Mongrelが当然のようにマルチスレッドで、モジュールも対応が必要なのに対し、POEの方はマルチスレッドに対応していないようである。
双方のwebサーバともプロダクション環境に適した性能を有するサーバではなく、またここで解説するwebアプリケーションはXSS対策やSQLインジェクション対策などのセキュリティ上のケアを全く欠いているので、あくまで各webサービスAPIの動作サンプルとしてのみ御覧になっていただきたい。ソースコードの文字コードは双方ともUTF-8で、Windows XP SP2上にて作成し、Microsoft Internet Explorer 7ならびにFirefox 3.0a1 trunk build 20061218で動作確認している。ただし、「はてな」のweb APIの仕様は本記事を書いた2006年末の時点のものに依っているので、仕様変更によって任意の時点でこれらのアプリケーションが動かなくなっている可能性もある。
では一つ目の、Rubyアプリケーションの方から見ていこう。hatenawebapp1.confは設定ファイルで、YAMLフォーマットである。webサーバのポートや、認証APIに与えるキー、データベースファイル名などを設定する。
|
以下はスクリプト本体hatenawebapp1.rbで、ruby 1.8.5 (2006-12-04 patchlevel 2) [i386-mswin32]で動作確認した。必要ライブラリは、
RubyGems-0.9.0
mongrel-0.3.13.3-mswin32
sqlite3-ruby-1.1.0-mswin32
hatenaapiauth-0.1.0
uuidtools-1.0.0
scrapi-1.2.0
と、rubygemsツールで取得可能な依存ライブラリである。尚、sqlite3のライブラリのバイナリが別途必要で、Windowsの場合はスクリプトのディレクトリにsqlite3.dllを、Unixの場合もSQLite公式サイトで入手できるライブラリのバイナリをパスの通った場所へ置く必要がある。また、Mongrelは0.3.13.3を使用しているが、アップデートが頻繁なので異なるバージョンは不具合が出る可能性もある。
|
YAMLの設定ファイルをロードし、SQLiteデータベースのテーブルを無ければ新規作成する。
|
Webサーバのモジュールなので、データベースアクセスのためのグローバルオブジェクト$dbはSync_mを使用してマルチスレッド対応にしておく。アップロードされてくるファイルのファイルサイズと、受け取り済みのデータのサイズとを保存するために、Structクラスを使ってDownloadProgressという構造体を作っておく。さらに、$download_progressというHashのオブジェクトを生成し、このオブジェクトがQUERY_STRINGリクエストパラメータとDownloadProgressオブジェクトとの対応表を保存する。$download_progressは、デザインパターンで言うところのObserverパターンで、ダウンロード状況のview(MVCの'V')として複数のwebからのリクエストによって同時に参照される可能性があるため、同じようにマルチスレッド対応にしなければならない。
|
「はてな」の認証APIの初期化関数を呼び出し、次いで表示するページ内のヘッダをそのままヒアドキュメントを使ってスクリプト内に書いている。
|
以下に、Mongrelのハンドラ関数群が続く。各種デフォルトハンドラをオーバーライドすることにより、webアプリケーションはMongrelの動作
をカスタマイズするというのがMongrelモジュールの基本的なコンセプトである。まずは、ユーザが最初にサーバのルートパス('/')に
アクセスしたときに、「はてな」のIDとパスワードを認証APIに対して差し出すように促す。processというメソッドが
ユーザ定義フィルタの役割を果たし、requestを受けてresponseを返す。
|
このスクリプトの一番最後にMongrel起動時の初期設定を行う部分があるので、そこを見てもらうとして、このUploaderHandlerは、"/uploader"
というパスにアクセスした場合のハンドラである。リクエスト中のクエリ文字列(request.params["QUERY_STRING"])を解析し、認証情報
を取り出して「はてな」認証APIに与える。「はてな」ユーザとして認証されると、$verified_users_ipaddressハッシュ表にリモートIPアドレスとユーザ名の組が保存される。ちなみに、このアプリケーションはIPアドレス1つあたり1ユーザとして認識しており、まともなセッション管理を行っていないので、実際に使用するには厳密なセッション管理が必要である。
|
このページのヘッダはJavaScriptを含んでいて、AJAXの簡単なフレームワークと、「はてな」ユーザ名の実在性を確かめるメソッド、ファイルのアップロード状態をポーリングしながら進捗バーを動的に表示するメソッドなどを含む。AJAXによる画面遷移無しのファイルアップロードを実現するために、ファイルのアップロード先を隠しiframeにするというテクニックが使用されている。
|
pollDownloadProgress関数を1秒毎にタイマー呼び出しして/query_progressへAJAX問い合わせを行い、
ダウンロード済みのファイルサイズを更新しつつ進捗バーを伸ばす。
|
"/check_username"というパスに、あるユーザ名が実在の「はてな」ユーザかどうか確かめるサービス(後述のCheckUsernameHandler)が動いているので、そこに対してAJAXで問い合わせを行う。
|
"/downloader"でアクセスできる、他ユーザが自分宛にアップロードしたファイルを受け取りダウンロードするためのURLのハンドラを定義する。
|
"/receiver"のパスが、ユーザがファイルをアップロードする対象である。 request_progressメソッドはリクエストのデータを一定量受け取る度にMongrelが呼び出すイベントコールバックで、これをオーバーライドすることによって、アップロードされてくるファイルのアップロード済みデータ量の数値を逐次更新する。Mongrel::CGIWrapperを使用してHTMLフォームから送信されてくるデータを解析しているが、使用したMongrelのバージョンではファイルをアップロードしている場合に正常にそれぞれのクエリ要素を受け取れないというバグがあるので、迂回策として生のデータを正規表現で検索している。ファイルのダウンロードが済むと、Mongrel::CGIWrapperの仕様に従って、アップロードされてきたファイルのサイズに応じ、一時バッファもしくは一時ファイルから、実際のファイル保存先へとデータを移す。
|
scrapiのWindows版で1モジュールのdllの読み込みに不具合があるので、問題になるメソッドfind_tidyをここで上書き修正している。この辺は動的言語の面目躍如と言うべきだが、濫用すると収拾が付かなくなるので個人的にはやむをえない場合以外やるべきではないと思っている。
|
あるユーザ名が「はてな」の実在のユーザかどうか確かめるためのwebサービスAPIを「はてな」では提供していないので、「はてな」上に該当ユーザのメンバーページが存在するかどうか、scrapiによるスクレイピングを行って強引に確かめることで代用する。
|
自分がアップロード中のファイルの進捗状況を示すXMLを返すハンドラを定義する。
|
自分宛に他ユーザがアップロードしたファイルの一覧を表示するハンドラを定義する。
|
Mongrelの起動設定と起動、終了処理。どのパス(URI)がどのハンドラクラスによって定義されているか、ここで指定する。
|
hatenawebapp1.rbは以上である。
スクリプトを動作させると、Mongrelが設定ファイル内のポートで起動するので、webブラウザでアクセスすると、「はてな」認証を促すリンクが表示される。それをクリックし、認証を通過すると、コールバックURLのhttp://127.0.0.1/uploaderに転送され、そこでファイルのアップロードが可能となる。自分の送信済みファイルと、自分宛に他ユーザが送信したファイルのリストもそこに表示されている。ファイルをアップロードすると、AJAXを
利用して画面遷移無しで進捗表示とアップロード完了後のリスト更新が行われる。尚、ごく稀に特定のファイルでアップロードが失敗することがあるようだが、MongrelのCGIWrapperのバグに起因する問題でありMongrel側の修正を待つしかない。
つぎに、Perl webアプリの方を見ていくことにする。まずは、設定ファイルのhatenawebapp2.confである。Rubyアプリの方と同様に、YAML形式を用いてサーバのポートなどを設定している。
|
スクリプト本体はhatenawebapp2.plである。perl, v5.8.8 built for MSWin32-x86-multi-threadで動作を確認している。
必要ライブラリは、Perl 5.8の他に、
YAML::Syck
POE::Component::Server::HTTP
HTTP::Status
XML::RSS
XMLRPC::Lite
LWP::Simple
MeCab
URI::Escape
threads::shared
Thread::Semaphore
のそれぞれCPANシェルを使って入手できる最新バージョンと、各々が依存するライブラリである。オープンソース形態素解析エンジンMeCabは、ナマズのブログで入手可能な0.92のWindows用バイナリと辞書を使用させていただいた。尚、Windows下ではMeCabがShiftJISでビルドされているため辞書もShiftJIS版を使用し、スクリプト内で必要な変換を行ったが、他プラットフォームでテストする場合はMeCab、辞書ともUTF-8版が必要である。また、JavaScriptのグラフ視覚化ライブラリであるJSVizと、ツールチップライブラリboxoverを利用しており、これらはスクリプト下にjsdirという名称のディレクトリを作ってその中へ全て展開する必要がある。
|
ここでは、日本語UTF-8文字列を使うときにUTF-8に対応していないXMLRPC::LiteとSOAP::Lite内で問題がある箇所の関数を動的に上書き修正している。
|
webサーバの設定を行うとともに、webサーバ上の各パス毎にハンドラ関数を登録している。
|
これは穴埋めクイズの問題を作る関数で、要は、「はてなキーワード」内の日本語文に対しMeCabで形態素解析を行って、見つかった名詞の部分を隠すことによって穴埋め問題にするという至極単純な仕組みである。
|
webサーバのルートURLのハンドラ。ユーザが任意の単語を入力すると「はてなキーワード」を検索し、キーワード間の連想グラフをロードする。
|
GETリクエストで呼び出されると必要なJavaScriptを表示し、POSTリクエストの場合は入力された単語を「はてなキーワード」で検索した後、キーワードのRSSデータから説明文を抜き出してクイズを作成表示する。
|
JSVizによって「はてなキーワード」の連想グラフを視覚化した物を表示する画面のハンドラ。
|
「はてなキーワード」で検索を行うためのAJAXコールバックを定義する。JSVizはこれを呼び出して見つかった関連単語を次々と検索し、単語のグラフに新しいノードを付け加えていく。
|
関連単語を「はてな」の関連キーワードAPIを用いて探すためのハンドラ。「はてな」のコードサンプルで推奨されているようにXMLRPC::Liteモジュールを使って検索するが、私が試した限りでは「ディスク」など一部単語で問題が起こるようで、いささか実用性に欠ける。
|
クイズの答えが正しいかどうか判定し、正解/不正解を返すAJAXコールバックURLのハンドラ。
|
最後にPOE::Kernelを呼び出し、サーバを起動する。
2つを書いてみての感想は、モジュール周りの扱いがRubyの方が簡潔で、完成された印象を持った。Perlの方はかなり入り組んでいてモジュールのインストールだけで小一時間かかってしまう(ほとんどがPOEに起因しているが、XML関連のモジュールの層も相当ファットである印象を受ける)。Perlの方だけUTF-8を扱う必要がある点も、各モジュールの問題が噴出し、Perlにとって不利な結果となった。もちろんRubyもMongrelの完成度が低いといった問題はあるものの、webアプリケーションそのものの問題ではないし、それを言えばPOEは完全にMongrelに劣るので、webアプリケーションテスト環境を含めた評価としては、やはりRubyの方が洗練されている。今回は順当にRubyに軍配が上がる結果となった。これでRubyそのもののパフォーマンスが向上すれば鬼に金棒といえるだろう。
