<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ryukware</title>
	<atom:link href="http://zzz.zggg.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://zzz.zggg.com</link>
	<description>is not a blog</description>
	<lastBuildDate>Tue, 07 Sep 2010 19:13:35 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Whisky-Tango-Foxtrot</title>
		<link>http://zzz.zggg.com/2009/04/15/whisky-tango-foxtrot/</link>
		<comments>http://zzz.zggg.com/2009/04/15/whisky-tango-foxtrot/#comments</comments>
		<pubDate>Tue, 14 Apr 2009 22:59:39 +0000</pubDate>
		<dc:creator>RyuK</dc:creator>
				<category><![CDATA[DICE]]></category>
		<category><![CDATA[sup]]></category>

		<guid isPermaLink="false">http://zzz.zggg.com/?p=137</guid>
		<description><![CDATA[



更新を再開しようかと思ったのだが、まだネタがないので仕方なくblog風に先日撮った写真(芝公園にて、2009年4月3日)で茶を濁す。
昨年も書いているように、サイト自体をCMSかblogツールにいい加減移行 [...]]]></description>
			<content:encoded><![CDATA[<p><img class=" alignright" title="芝公園" src="http://zzz.zggg.com/j/20090403.png" alt="芝公園" width="396" height="528" /></p>
<p>更新を再開しようかと思ったのだが、まだネタがないので仕方なくblog風に先日撮った写真(芝公園にて、2009年4月3日)で茶を濁す。</p>
<p>昨年も書いているように、サイト自体をCMSかblogツールにいい加減移行したいと本当は思っていて、そのくせ時間がないので踏み切れないでいる。</p>
<p>VS2008はやっと自分のPCにインストールしたところで、昨年書いた課題に取りかかる以前の段階で1年以上足踏みしてしまっている。その間に生活環境が変化したため、以前描いていた計画も、実装に取りかかる遙か以前の段階で既に再検討を余儀なくされている。具体的には、DICEに関して、ある部分のみC++/CLIを用いて.NET化することを考えていた。それというのも、ネイティブコードもそろそろ潮時かと考えて、C++を利用する部分を徐々に縮小させることを意図していたからであった。現在もその方針自体は誤りではなかったと考えているが、今それを為すべきか、そのための時間があるかということを熟慮すると、同じ時間を使うにしても他にやらなければならないことがあるのではないかというのが今回至った結論である。<br />
<span id="more-137"></span><br />
また、去年計画・調査だけ行って結局頓挫したものとして、どのみち日の目を見ないだろうから忘れないうちに書いてしまうと、RTMPをDICEに実装することを考えていた。それというのも、抽象サーバーフレームワークであるDICEのプロトコル実装の試金石として商業的に意味のありそうなプロトコルを是非実装しておきたかったのと、 Flashとの親和性を高めておきたかったという事情がある。サーバー用のソフトウェアスタック(と.NETで作った簡易クライアント)しかコントロールできない私にとって、Flash       +  JavaScriptはクライアントアプリ作成のためのRAD環境たり得た。ところが状況は変わり、C++の非常に有望なクライアント用ソフトウェアスタックが自由なライセンスの元で利用できることになり、今はそれをどう活かすかについて思案している。勘の良い諸兄ならそれが何かもうお分かりだろう。</p>
]]></content:encoded>
			<wfw:commentRss>http://zzz.zggg.com/2009/04/15/whisky-tango-foxtrot/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>WindowsにおけるC++へのPHP組み込み環境の構築</title>
		<link>http://zzz.zggg.com/2008/02/11/windows-cpp-php-embedding/</link>
		<comments>http://zzz.zggg.com/2008/02/11/windows-cpp-php-embedding/#comments</comments>
		<pubDate>Sun, 10 Feb 2008 23:33:40 +0000</pubDate>
		<dc:creator>RyuK</dc:creator>
				<category><![CDATA[code]]></category>

		<guid isPermaLink="false">http://zzz.zggg.com/?p=157</guid>
		<description><![CDATA[前回の記事では、C/C++コードへのPerlとRubyの組み込みを扱った。DICEへの組み込みの評価を兼ねていて、当時はPerlを使うことになった。一方で、DICEのWebサーバとしての側面をもっと強調せねばという課題が [...]]]></description>
			<content:encoded><![CDATA[<p><a href="../../perl_ruby_multithreading_embedding.html" target="_blank">前回の記事</a>では、C/C++コードへのPerlとRubyの組み込みを扱った。<a href="http://zzz.zggg.com/dice/" target="_blank">DICE</a>への組み込みの評価を兼ねていて、当時はPerlを使うことになった。一方で、DICEのWebサーバとしての側面をもっと強調せねばという課題が最近わりと念頭にあり、Webサーバを名乗るからには現在のWeb向けスクリプト言語No1としてのPHPをサポートしていないというのはいかにも心苦しい。 PHPはApacheと関連付けて語られることも多い以上、Apacheの代替を目指しているわけではないDICEでサポートする意味も薄いと判断し敬遠してきたという経緯もあったものの、あまりにもPHPの勢いがありすぎるので仕方なくサポートに向けて舵を切ったというわけだ。特に海外では、 VBulletinやWordPressといった代表的Webアプリケーションが利用できてようやくそれなりのWebサーバとしてユーザの検討の俎上に載せられることもあるだろう。<br />
<span id="more-157"></span><br />
PHPの魅力は、言語仕様ではなくバックエンドにある。特に、実行時間を限定できるオプションは有り難い。中をあまり見ていないので実装の詳細は知らないが、実行スレッド以外にスレッドを起動するか、あるいは元々のディスパッチャがディスパッチしたタスクを定期的にチェックするなりして監視していると思われる。WindowsだとTerminateThreadというAPIでスレッドを止めることが出来るけれども、これはリソースリークを起こす不安定なAPIとして知られている。特に、OSのスレッドプールを使っている場合は、スレッドを勝手に止めるとどんな予期しない悪影響が起こるかわかったものではない。PHP側で非ネイティブスレッドの実行を途中停止しているとすれば、PHPを組み込んで使うクライアントコード側としては手間が省けて大いに助かるというわけである。</p>
<p>本記事の環境はVS2005(VC8)である。PHPはPHP 5.25を用いた。PHP組み込みに関する公式の資料は<a href="http://www.php.net/manual/en/internals2.php" target="_blank">PHPマニュアル第7章</a>に存在する。46節の&#8221;<a href="http://www.php.net/manual/en/internals2.ze1.zendapi.php" target="_blank">Zend   API: Hacking the Core of PHP</a>&#8220;に変数の操作に関して記述がある。ところが、現時点のPHPマニュアルには、組み込みの際の初期化についてすら解説がない。PHPソースコードを展開した際に<code>php-5.2.5\sapi\embed</code>フォルダに見つかる<code>php_embed.h</code>ならびに<code>php_embed.c</code>を参照すると、初期化/終了マクロがあり、初期化の際に<code>sapi_startup</code>関数を呼んでいるのが分かる。これは<code>main\SAPI.h</code>ならびに<code>SAPI.c</code>に定義されており、引数となる構造体<code>_sapi_module_struct</code>にコールバックを登録してPHPのSAPI(Server   API)を初期化している。そのコールバックの中でも、<code>php_embed_ub_write</code>がprintなどの出力を受ける関数で、<code>php_embed.c</code>内では<code>php_embed_ub_write</code>を経由して<code>php_embed_single_write</code>に至り、標準出力へ出力されている。つまりそこをオーバライドしてやればユーザ側でスクリプトの出力を受け取れるのであり、基本的にはRuby組み込みで使われる方法と同一だが、PHPはSAPIフレームワークの中でインターフェイスをユーザ側に提供している点が異なる。</p>
<p>ユーザコードの概要が掴めたところで、次にPHP組み込みに必要な環境を作らなければならない。先に書いておくが、ここにまず問題がある。PHPのソースコードを<a href="http://www.php.net/downloads.php" target="_blank">ダウンロード</a>してローカルに展開した後に、マニュアルの<a href="http://jp.php.net/manual/ja/install.windows.building.php" target="_blank">Windows上でのビルド方法</a>に従ってビルドを行う。<code>win32build.zip</code>と   <code>bindlib_w32.zip</code>もビルドに必要なGNUツールなどを含んでいるので所定の場所へ展開しておく必要がある。ビルドには、Visual C++ツールのコマンドプロンプトを使用する。今回は組み込みを行うので、WSHを経由して<code>configure.js</code>を呼び出しmakeの設定を行う際に、<code>--enable-embed</code>を引数に加えることになる。</p>
<p>ところが、そのままビルドを始めると、エラーが出てストップしてしまう。エラーコードを見ると、libxmlに問題が起こっているらしい。そこで libxml2をダウンロードしてパスを通すも、やはりビルドが通らない。今度はどうやらiconvが必要なようで、最新版libiconvをダウンロー ドしてみると、なんと以前は入っていたVC++サポートがどういうわけか削除されていた。mingwでビルドせよとあるので試してみたがどうも上手くいかない。しかも、ダウンロード元がGNUだったことを思い出し、これはライセンス的にまずいのではないかと後から気付いた。iconvに由来するコードが PHPのスタティックライブラリに含まれているとすれば、PHP組み込みを行った時点でLGPLに汚染され、ユーザプログラムの配布に支障が出てしまう。 そこで、仕方なく<code>--without-libxml</code>をconfigure.jsのオプションに加え、XMLサポートを諦めた。調べてみると、iconv自体はPHP   5からPHP本体に組み込まれており、<code>php-5.2.5\ext\iconv</code>を見る限りクリーンルーム実装がなされている。このあたりlibxmlもどうにかできないものかと思うがここでは深く追及しない。</p>
<p>libxml/iconvの問題以外にもビルドプロセスには問題があり、ZLIBの関数も使用できないので<code>--disable-zlib</code>で無効にする。これでようやく<code>php-5.2.5\Release_TS</code>ないし<code>php-5.2.5\Debug_TS</code>に、アプリケーションにリンクするスタティックライブラリ(<code>php5embed.lib</code>)と実行時に必要なdllファイル(<code>php5ts.dll</code>)が出来上がる。次にVC++の設定として、メニューの「ツール   | オプション」からVC++のディレクトリにlibファイルの場所を追加し、さらにプロジェクト設定内のincludeファイルのフォルダに<code>php-5.2.5\</code>、<code>php-5.2.5\sapi\embed</code>、<code>php-5.2.5\Zend</code>、<code>php-5.2.5\TSRM</code>を加える。さらに、<code>PHP_WIN32</code>、<code>ZEND_WIN32</code>各シンボルの定義が必要となる。また、実際には、VC8からtime_tが64ビットになっているため<code>_USE_32BIT_TIME_T</code>も定義しなければクライアントコードのビルドは成功しない。以上でどうにか環境が整ったので、以下のコードがPHP組み込みのサンプルとしてビルド、実行できる。</p>
<pre class="brush: cpp;">
#include &lt;stdio.h&gt;
#include &lt;tchar.h&gt;

#include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;cstdio&gt;

#include &lt;php.h&gt;
#include &lt;php_embed.h&gt;

using namespace std;

static int php_ub_write(const char* str, unsigned int str_length TSRMLS_DC)
{
	string s(str, str_length);

	cout &lt;&lt; s;

	return str_length;
}

static void php_log_message(char* message)
{
	cerr &lt;&lt; &quot;php_log_message: &quot; &lt;&lt; message  &lt;&lt; endl;
}

static void php_sapi_error(int type, const char* fmt, ...)
{
	va_list va;
	va_start(va, fmt);
	printf(&quot;php_sapi_error: &quot;);
	vprintf(fmt, va);
	va_end(va);
}

int _tmain(int argc, _TCHAR* argv[])
{
	php_embed_module.ub_write = php_ub_write;
	php_embed_module.log_message = php_log_message;
	php_embed_module.sapi_error = php_sapi_error;

	char* argv2[] = {&quot;&quot;};

	PHP_EMBED_START_BLOCK(0, argv2);

	zval* v = NULL;

	MAKE_STD_ZVAL(v);
	ZVAL_STRING(v, &quot;Hello&quot;, 1);
	ZEND_SET_SYMBOL(&amp;amp;EG(symbol_table), &quot;hello&quot;, v);

	zend_eval_string(&quot;print \&quot;$hello \&quot;;$hello = \&quot;World\&quot;;&quot;, NULL, &quot;&quot; TSRMLS_CC);

	if (v-&gt;type == IS_STRING)
	{
		cout &lt;&lt; string(v-&gt;value.str.val, v-&gt;value.str.len) &lt;&lt; endl;
	}

	PHP_EMBED_END_BLOCK();

    return 0;
}
</pre>
<p>PHP関係のヘッダは他の標準ヘッダの後に置かなければエラーが出るようだ。コードの内容は、上述のように出力関数をオーバライドし、さらに<code>$hello</code>という名称の変数を作成して操作し最終的にHello   Worldを表示するというたわいのないものである。<code>PHP_EMBED_START_BLOCK</code>、<code>PHP_EMBED_END_BLOCK</code>が初期化、終了のマクロで、内部で<code>zend_first_try</code>/<code>zend_catch</code>のブロックを作っているので変数のスコープに注意しなければならない。前回のPerl組み込みの場合だとPerlでの例外をラップする仕組みを自前で作る必要があったのに対し、PHPの方はZendが面倒を見てくれる点は有り難い。</p>
<p>とはいえ、このようにしてビルドした物だとPHP  5の特徴的機能であるところのXMLがおそらく使用できない事はかなりの損失である。そこで、次に考えられる方法として、PHPの配布バイナリ内のライブラリを利用するという道がある。PHPの配布バイナリには、(一体どうやってビルドしたのかは謎だが)<code>php5embed.lib</code>と<code>php5ts.dll</code>が含まれているのである。Dependency   Walkerで<code>php5ts.dll</code>を覗いてみてもCライブラリがスタティックリンクされているので一見使い勝手が良いように見える。ところが、落とし穴があり、デバッグ版だとVC++のdllが異なってくるので、これを使った場合正常にデバッグが出来なくなってしまう。そこで、開発の際には自分でビルドしたlibファイルを使い、リリース版は配布バイナリの方を利用することになる。</p>
<p>しかし、拍子抜けではあるが、最終的にDICEでは上述のようなストレートな組み込み方法は採らなかった。というのも、不透明なライセンスの問題に加え、上述のやり方ではPHPスクリプトやASP(Active   Server  Pages)の最も有用な機能である、コードブロックのHTML埋め込みが出来ないのである(出来る方法があるのかも知れないがそれは本稿の範囲を超える)。これではこちらで配布するWebサーバに組み込んで利用するという本来の目的にそぐわない。そこで、結局DICEの方にISAPIエクステンションのサポートを入れ、ISAPIを経由してPHPをロードする形に落ち着いた。PHPはSAPIという組み込みインタフェイスを用意しており、PHPの配布バイナリにはISAPIエクステンションのdllが含まれている。この組み込み方法であればライセンス問題は完全にクリーンであり、PHP設定ファイルの読み込みなどの付随的処理もPHP側で全て自動的に行ってくれる。PHPはかなり素直なISAPIアクセスしか行わないので、別にwebサーバへの組み込み用途ではなくても、Windows上で ライセンスが気になる場合等はPHPそのものの組み込みの代わりにISAPIを経由してコントロールするのも一つの有力な選択肢ではないかと思う。他方で、配布を目的とせずサーバ側でのみ利用する場合はライセンスに関する問題はもちろん発生しないため、最初に取り上げた組み込み手順もそれはそれで有用となる局面もあるだろう。</p>
]]></content:encoded>
			<wfw:commentRss>http://zzz.zggg.com/2008/02/11/windows-cpp-php-embedding/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Denial Ain&#8217;t Just a River in Egypt</title>
		<link>http://zzz.zggg.com/2008/01/15/denial-aint-just-a-river-in-egypt/</link>
		<comments>http://zzz.zggg.com/2008/01/15/denial-aint-just-a-river-in-egypt/#comments</comments>
		<pubDate>Mon, 14 Jan 2008 22:58:31 +0000</pubDate>
		<dc:creator>RyuK</dc:creator>
				<category><![CDATA[DICE]]></category>
		<category><![CDATA[sup]]></category>

		<guid isPermaLink="false">http://zzz.zggg.com/?p=135</guid>
		<description><![CDATA[久々にDICEの新バージョンをリリースすることが出来た。今回は互換性を破る変更もあったので、インクリメンタルに新バージョンをリリースすると いう以前の目標はひとまず措いて、必要な部品が全て入るまで待たざるを得なかった。つ [...]]]></description>
			<content:encoded><![CDATA[<p>久々にDICEの新バージョンをリリースすることが出来た。今回は互換性を破る変更もあったので、インクリメンタルに新バージョンをリリースすると いう以前の目標はひとまず措いて、必要な部品が全て入るまで待たざるを得なかった。ついでというわけではないが、自分のハンドルもKLからRyuKに変更 した。KLでは短すぎるというのが主な理由だが、元のサイト名(KLassphere)をとある理由で変更したかったという動機が先に存在したかもしれない。 そちらはryukwareへ変わっている。サイトのデザインもDICEのWeb       UIで採用しているテンプレートを再利用して改装した。webサイト自体も去年秋から新アドレスへ移転している。<br />
<span id="more-135"></span><br />
実はこのページもいよいよblogツールで生成するように変更しようかと思っていて、去年初めに各種ツールを検討してみた。が、一番最初に触ってみたMovable  Typeがあまりにダメダメだったのでこの案はそこで一旦棚上げになってしまった。ファイルを置いているサーバはMySQLが使えるので環境は問題ない。ただ自分としてはデータのXCOPY        deploymentができるSQLiteを使いたい。SSHも使えるのでSQLiteのビルドも出来、そこまでは問題なかった。問題だったのは、肝腎のMTの管理者ツールが非常に使いにくいということだ。カスタマイズの際の独自マークアップも汚く、そんな下らない物を学習する気など毛頭無い。使える既成 テーマもろくにない。何故MTが最初の候補に挙がったかというと、オリジナルサイトから古い記事をインポートしたときに、記事の日付をオリジナルの物に変更したいという事があり、さしあたりそれが可能なのがMTであったというわけだ。いつか機会が有れば今度はWordPressを試してみようと思ってはいるけれどSQLiteを用いるのにこちらはこちらで手間がかかるようで痛し痒しである。</p>
<p>他人の作ったツールを使う場合の居心地の悪さというものはソフトウェアを書く際に利用するミドルウェアについてもしばしば感じることになる。オープンソースでソースコードとシンボルが手に入るならともかくそうでない場合は、いや手に入る場合であったとしても、分厚いミドルウェアの利用者はハッカーたることを強制されてしまう。その意味ではWindowsというものも巨大なミドルウェアであって、最近はMSDNのMicrosoft社員blogで様々な情報公開が行われていて助かることしきりである。しかし実態として見れば、不十分なAPIドキュメントあるいはAPIそのものについての補遺を非公式の場で行うことは現状を追認するエクスキューズでしかない。ミドルウェアによってもたらされる利益がある一方で、ミドルウェアを原因とするフラストレーションは相手がはっきりしているだけに顕在化しやすい。Windowsのような、選択の余地がないケースでは尚更である。</p>
<p>DICEについて言うと、今回からSSPIを使うのをやめてOpenSSLに切り替えた。もう1つの変更は逆方向で、これまで8年間C++を用いる開発には常にSTLportを使い続けてきたのをやめて、今回からVC++のC++ライブラリを使うように変更した。VS2005からSxSになってインストールが面倒になったにも関わらず、である。C++標準ライブラリの変更自体は、これまでデバッグの関係で両対応にしていたので何ら問題はない。STLportの利点 は、かつてVC6時代にあったiostreamやロケールのバグが修正されていたり、SGI        STL譲りのhash_map、ropeといったコンテナ、それからmalloc呼び出しを抑えるノードアロケータ、さらに名称の由来である移植性などで ある。加えて、後年メインテナーが変更になって以来、iostreamのさらなる修正やbig        fileサポート、組み込みプラットフォームサポート、ユニットテスト追加、basic_stringのスタティックバッファ、+=ではなく+でbasic_stringを繋げる場合の最適化、ノードアロケータのロックフリー化、など様々な改良が施されてきた。VC++添付のライブラリはDinkumwareという会社のOEMで、そこのP.J.        Plauger氏(以前Cマガジンで連載があった)が去年だったかニュースグループでSTLportの状況をかなり口汚く大人げないスタンスで論難し、反論に遭っていたのを見たことがある。</p>
<p>STLportを捨てることを決めたのは、VS2008の機能リスト中に、STLとマネージド型のマーシャリングが自動的になされるといった.NET絡みの親和性を見つけて、これはもう避けられないと感じた事による。STLportを使っていると、他のC++ライブラリと併せて使った場合に問題が起こることが多く、その度に苦しめられたという問題に関しては、長年使ってきたという既成事実のおかげでどうにか切り抜けられてきていた。それでも、マルチコア環境でSTLportノー ドアロケータが実際は標準mallocに劣るというテスト結果(OSのヒープアロケータの薄いラッパーであるmallocの方がスレッドやスレッドローカルデータについてよく知っているのだから当たり前だ)や、VC++がC++例外のソースコードを提供していないせいでSTLportを使う場合もVC++のdllが丸ごと必要になるという問題は以前から不愉快に思っていた。ノードアロケータはユーザランドでのメモリリークになるが、引き替えにパフォーマンスが得られるならと思い許容していたにもかかわらず、実は期待した効果を得られていないというのは特にショックだった。また、DICEではそもそもiostreamを使っていないのでその部分でのSTLportの恩恵は全くない。STLportをやめてみてまず最初に判明したのは、これまでIntellisenseが正常に機能しなかった原因はSTLportにあったということだ。てっきりLokiが問題なのだと思い込んでいたが、そうではなかった。当然、MSVCのC++ライブラリだとSTLコンテナやbasic_stringの中身もデバッガで易々と見ることが出来る。唯一勿体ないと感じるのはbasic_stringの最適化くらいだけれども、どうしても無ければならないという類の物でもない。むしろ、デバッガでbasic_stringの中身を見るときに一々スタティックバッファとダイナミックバッファの両方を見たりせずに済むようになって、せいせいした。unordered       associative container (hash_map)も今後TR1絡みで動いていくだろうから変更にはいい機会だったと思う。</p>
<p>他方、VS2008には困ったこともあった。ATL属性が廃止になっていて、実はDICEのサービスはこれを使っている。しかし、実際に汚い物なので、廃止されるのもむべなるかなといったところである。今回のDICEは、こういったWindows世界での波風からできるだけ逃れるというのも一つのテーマになっていて、上記のMSVCのC++ライブラリへの回帰は残念ながらそれに逆行するが、このページの2006年11月30日の項で述べたWeb       UIへの移行はついに達成した。Web  UIでカバーしきれない部分は、これもMicrosoftの束縛の象徴であるMFCを使っていたDICEAdminShellを廃止し、DICE       Managerという、機能を最低限に絞ったヘルパーアプリケーションをC#/Windows  Formsで作って新たに入れた。2006年11月30日の項でも書いたようにMFCは本当に無茶苦茶で、DICEAdminShell廃止も MFCに直接起因している。VS2003からVS2005に移行したときにビルドし直したところ、実行ファイルがXPでは動くのにWindows       Server  2003では動かないようになってしまったのだ。VS2008では驚くべき事にMFCがまだ残っていて機能拡張までされているそうだが、この辺で引導を渡してやっていた方が結果的に各方面が幸せだったのではないか。DICEの.NET WebアプリケーションコンテナもVS2005移行に伴って.NET Framework  1.1から2.0に移行し、マネージドコード部分に多少書き換えを要した。.NET Framework       3.5が2.0ベースであるのは賢明な方針である。</p>
<p>Web  UIは、それなりに道具が揃うとかなり楽に作れる。今回は勉強を兼ねて、prototype.jsに代表される外部のAJAXライブラリは一切使わず、自前でフレームワークを 作った。どのみちIDEが無いのだから大して手間は変わらない。無意味にファットなライブラリに興味はなく、また標準ライブラリを書き換えてあったり、一 旦導入すると以降はそのライブラリのスタイルを強要されたりするのも煩わしい。道具の一つとして、PHPのSmartyのようなテンプレートエンジンを JavaScriptでXHTMLのカスタムタグとDOMを利用して作ろうと思ったところ、XML名前空間やxhtmlについてIEはIE7でもサポートがろくになされていないのがわかり、またFirefoxにせよapplication/xml+xhtmlが必要になるため、仕方なくspanタグを使って作った。もう一つテンプレートエンジン絡みで参ったのは、&lt;p&gt;などのブロックタグが実行時にJavaScriptによって他のブロックタグの内側に挿入されると、IEが「未知の実行時エラー」を出すことだ。勿論規格的には正しい動作だが、普段はエラーに寛容なIEらしからぬ振る舞いで、VSのデバッガでもよくわからない部分へ行くので、原因を探すのに苦労した。</p>
<p>今のところWeb       UIは通常のwebページの外見で、Web  IRCクライアントで利用したときにせいぜいタブインターフェイスをサポートするくらいだが、将来的にはWebデスクトップに必要なMDIをサポートできるように、ウィジェットクラスの階層を定義できたらと思っている。今回は、デモとして、管理者用にリモートファイルダウンロードマネージャを入れた。 GetRightやIrvineのようなダウンロードマネージャアプリケーションに似た機能をWebから利用できる。サポート機能はまだプリミティブで、ベーシック認証やリザルトコード302などはサポー トしていないが、HTTP/1.1のchunked       encodingは完全にサポートしているので、<a href="../../perl_ruby_multithreading_embedding.html">Perl/Rubyで以前書いたツール</a>のスーパーセットになっている。</p>
<p>Web  UIについて書いていて今気付いたが、DICEに新しいライブラリをもう一つ導入している。メンバー登録時に表示されるCAPTCHAを、GDI+を使って実装した。今回Vistaを正式サポートした際に、諸々の理由でWindows        2000を動作環境から外したので、これが可能になった。とはいえGDIを使えば済む話でパフォーマンスもそちらの方が有利ではあるものの、GDI+は何かと楽である。オープンソースの画像ライブラリだと、最近はFirefoxなどで使われている、ハードウェアアクセラレーションが入ったCairoが一番有名だろうか。ただ、これも他のオープンソースライブラリの例に漏れずlibpngなどに依存しているので、環境を作るのに労力が要る。昔FreeBSD のサーバで使うC++の掲示板用にCAPTCHAを入れた時にはpaintlibというクロスプラットフォームライブラリを使っていて、これは2年前に開発中止になってしまっている。</p>
<p>さて、今後は、DICEについてもう一段さらにジャンプを予定している。それが済んだらようやっと頻繁にリリースを出来る環境が出来るのではないかと思う。ゲームの方は、かなりの長期間離れていた後に、最近UT3が出たので下手ながらたまにオンライン対戦をやっている。UT2004は時間の無駄遣いが恐ろしくなりアンインストールするほどにプレイしたので今回も期待していたが、若干物足りない。売り上げは残念な結果に終わっているのでいよいよPCゲームも(MMOとSteamで買えるゲーム以外は)終焉に至ったかと感慨深い。</p>
]]></content:encoded>
			<wfw:commentRss>http://zzz.zggg.com/2008/01/15/denial-aint-just-a-river-in-egypt/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SSL Server with OpenSSL Memory BIO a.k.a. Prerequisite to Asynchronous OpenSSL</title>
		<link>http://zzz.zggg.com/2007/12/22/ssl-server-with-openssl-memory-bio-a-k-a-prerequisite-to-asynchronous-openssl/</link>
		<comments>http://zzz.zggg.com/2007/12/22/ssl-server-with-openssl-memory-bio-a-k-a-prerequisite-to-asynchronous-openssl/#comments</comments>
		<pubDate>Fri, 21 Dec 2007 22:02:57 +0000</pubDate>
		<dc:creator>RyuK</dc:creator>
				<category><![CDATA[code]]></category>

		<guid isPermaLink="false">http://zzz.zggg.com/?p=83</guid>
		<description><![CDATA[In the last article of mine about SSL-related programming, the API to handle SSL transaction for the DICE was the SSPI (Security Support Provider Interface) that is one of the standard API sets provided by Microsoft Windows. Though I outlined why I chose SSPI over OpenSSL in the article, recently I replaced SSPI with OpenSSL [...]]]></description>
			<content:encoded><![CDATA[<p>In the <a href="http://zzz.zggg.com/2005/05/05/how-to-programmatically-create-self-signed-cert-key-pair-windows-sspi/" target="_blank">last article of mine about     SSL-related programming</a>, the API to handle SSL transaction for  the <a href="http://zzz.zggg.com/dice/" target="_blank">DICE</a> was the <a href="http://msdn2.microsoft.com/en-us/library/aa380493.aspx" target="_blank">SSPI</a> (Security     Support Provider Interface) that is one of the standard API sets provided by Microsoft Windows. Though I outlined     why I chose SSPI over <a href="http://www.openssl.org/" target="_blank">OpenSSL</a> in the article,     recently I replaced SSPI with OpenSSL in     the latest version of the DICE that was released with HTTPS implemented.     The rationale behind the switch of the SSL engine was not so straightforward.</p>
<p>For me, the main concern about OpenSSL   had been its putative close relationship with the BSD socket architecture that is not compatible with   asynchronous sockets and I/O completion ports. Another concern was about OpenSSL&#8217;s vulnerabilities against   security breaches. OpenSSL has been an active target by crackers and one of the most scrutinized library.   Not that Microsoft&#8217;s implementation is any better, but as far as I know OpenSSL gets many security   advisories about it through its update history.<br />
<span id="more-83"></span></p>
<p>On the other hand, SSPI&#8217;s complex nature   as an abstraction layer for multiple protocols had been a growing pain.   Since the DICE is built with I/O completion ports, it was pretty messy to embed SSPI negotiation in combination with asynchronous sockets.  After the sour experience of implementing IRC   over SSL with SSPI on asynchrounous sockets and I/O completion ports some years ago, I was not so thrilled   to touch it again to implement HTTPS. I needed a better, simpler framework to get the job done.</p>
<p>In updating the   DICE to 0.9.0.0 after a long hiatus, I took the new version as a chance to address awkward points that had been long   overlooked, sometimes even breaking compatibility with older versions. One of such problems in the   DICE was SSL-related implementation. That&#8217;s why I took a second look at OpenSSL. Though it may contain unknown security flaws, its core cryptographic functions are actively upgraded to the latest   standard spec unlike Microsoft&#8217;s implementation. Also it&#8217;s nice in terms of compiler/linker   global optimization that the source code is available.</p>
<p>So what is the key to the actual usage of OpenSSL in asynchronous sockets? I browsed the sparse user manual and   found the necessary tool for me: <a href="http://www.openssl.org/docs/crypto/BIO_s_mem.html" target="_blank">memory     BIO</a>.</p>
<p><a href="http://www.openssl.org/docs/crypto/bio.html" target="_blank">BIO</a> is an &#8220;I/O abstraction,   it hides many of the underlying I/O details from an application&#8221; according to the OpenSSL document   (What does B in BIO stand for anyway?). Also a &#8220;memory BIO is a source/sink BIO which uses memory for   its I/O.&#8221; This basically means you can do SSL negotiation without touching a BSD socket or other platform-specific   stuff at all. All you need to do is feed encrypted data into a memory BIO until it&#8217;s ready to dump    something meaningful decrypted out of it.</p>
<p>While the title of this article mentions &#8220;asynchronous&#8221;, the sample code below uses traditional   synchronous sockets. In fact, the simple abstraction by memory BIO allows   smooth transition from synchronous sockets to asynchronous sockets since buffering memory BIO is exactly   the same process as parsing a plain message by waiting for a whole message packet to arrive in asynchronous   callbacks. In addition,  you had to manually set up an SSL handshake at the beginning of a connection   by yourself in SSPI while BIO in OpenSSL completely hides it from a client code. I could replace SSPI with   OpenSSL for the already implemented IRC over SSL, and then could implement HTTPS in the latest version of   the DICE by making the flow described in this sample code asynchronous.</p>
<p>The only caveat in making   it asynchronous is it&#8217;s a bit difficult to support SSL renegotiation. Though it&#8217;s rare, sometimes SSL   resets SSL properties at the middle of connection to make it difficult for crackers to collect necessary   data. When it happens, you have to save the context such as a partially decrypted message, then do   handshake again, and go back to where you originally were by restoring the original state     in a new SSL context. In synchronous connection all these steps can occur between 2 braces in   a certain function, but in asynchronous sockets it&#8217;s hard to suspend it by saving the context and resume   it from the original point, since you have to juggle many more states in your session state machine    to fully support renegotiation.</p>
<p>This test code of a synchronous HTTPS server was written and tested with VS2005 SP1 on Windows XP   SP2. You have to build OpenSSL by obtaining its source code prior to building this sample. The OpenSSL   version I used was 0.9.8g. After building OpenSSL, you have to add <code>libeay32.lib</code> and <code>ssleay32.lib</code> in   the <code>out32</code> folder   under the OpenSSL source root directory to the linker input option in the VC++ project of the sample code, in addition to <code>ws2_32.lib</code> for   Winsock 2.</p>
<pre class="brush: cpp;">// openssltest.cpp

// InitializeCriticalSectionAndSpinCount
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif _WIN32_WINNT

#include &lt;windows.h&gt;

#include &lt;tchar.h&gt;

#include &lt;openssl/ssl.h&gt;
#include &lt;openssl/err.h&gt;

#include &lt;iostream&gt;
#include &lt;string&gt;

using namespace std;
</pre>
<p>To use OpenSSL in a multithread application you have to implement necessary <a href="http://www.openssl.org/docs/crypto/threads.html" target="_blank">synchronization   primitives for OpenSSL</a> with your platform-supplied functions. In this sample application, I used   CRITICAL_SECTION and <a href="http://msdn2.microsoft.com/en-us/library/ms683476.aspx" target="_blank"><code>InitializeCriticalSectionAndSpinCount</code></a> available   in Windows 2000 and later. Its wrapper class <code>Synchronizer</code> does synchronization in various   OpenSSL callbacks.</p>
<pre class="brush: cpp;">class Synchronizer
{
private:
	CRITICAL_SECTION lock_;

public:
	__forceinline Synchronizer()
	{
		InitializeCriticalSectionAndSpinCount(&amp;amp;lock_, 4000);
	}
	__forceinline ~Synchronizer()
	{
		DeleteCriticalSection(&amp;amp;lock_);
	}

	__forceinline void acquire()
	{
		__try
		{
			EnterCriticalSection(&amp;amp;lock_);
		}
		__except (EXCEPTION_EXECUTE_HANDLER)
		{
		}
	}

	__forceinline void release()
	{
		LeaveCriticalSection(&amp;amp;lock_);
	}
};

static Synchronizer* openssl_locks = 0;

struct CRYPTO_dynlock_value
{
	Synchronizer s;
};

static void funcOpenSSLLockingCallback(
	int mode,
	int type,
	const char* file,
	int line
)
{
	if (mode &amp;amp; CRYPTO_LOCK)
		openssl_locks[type].acquire();
	else
		openssl_locks[type].release();
}

static unsigned long funcOpenSSLIDCallback(void)
{
	return GetCurrentThreadId();
}

static CRYPTO_dynlock_value* funcOpenSSLDynCreateCallback(
	const char* file,
	int line
)
{
	return new CRYPTO_dynlock_value;
}

static void funcOpenSSLDynDestroyCallback(
	CRYPTO_dynlock_value* l,
	const char* file,
	int line
)
{
    delete l;
}

static void funcOpenSSLDynLockCallback(
	int mode,
	CRYPTO_dynlock_value* l,
	const char* file,
	int line
)
{
	if (mode &amp;amp; CRYPTO_LOCK)
		l-&gt;s.acquire();
	else
		l-&gt;s.release();
}
</pre>
<p>A function to get an <code>EVP_PKEY</code> RSA private key object.</p>
<pre class="brush: cpp;">EVP_PKEY* get_evp_pkey(RSA* rsa, int priv)
{
	RSA* key = (priv ? RSAPrivateKey_dup(rsa): RSAPublicKey_dup(rsa));
	if (!key)
		goto error;

	EVP_PKEY* pkey = EVP_PKEY_new();
	if (!pkey)
		goto error;

	if (!(EVP_PKEY_assign_RSA(pkey, key)))
		goto error;

	return pkey;

error:
	if (pkey)
		EVP_PKEY_free(pkey);

	if (key)
		RSA_free(key);

	return NULL;
}
</pre>
<p>A function to generate an X509-format certificate and RSA public key. It&#8217;s signed by a private key.</p>
<pre class="brush: cpp;">static X509* create_certificate(
	RSA* rsa,
	RSA* rsaSign,
	const char* cname,
	const char* cnameSign,
	const char* pszOrgName,
	unsigned int certLifetime
)
{
	time_t start_time = time(NULL);
	time_t end_time = start_time + certLifetime;

	EVP_PKEY* sign_pkey = get_evp_pkey(rsaSign, 1);
	if (!sign_pkey)
		goto error;

	EVP_PKEY* pkey = get_evp_pkey(rsa, 0);
	if (!pkey)
		goto error;

	X509* x509 = X509_new();
	if (!x509)
		goto error;
	if (!(X509_set_version(x509, 2)))
		goto error;
	if (!(ASN1_INTEGER_set(X509_get_serialNumber(x509), (long)start_time)))
		goto error;

	X509_NAME* name = X509_NAME_new();
	if (!name)
		goto error;

	int nid = OBJ_txt2nid(&quot;organizationName&quot;);
	if (nid == NID_undef)
		goto error;
	if (!(X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC, (unsigned char*)pszOrgName, -1, -1, 0)))
		goto error;
	if ((nid = OBJ_txt2nid(&quot;commonName&quot;)) == NID_undef)
		goto error;
	if (!(X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC, (unsigned char*)cname, -1, -1, 0)))
		goto error;
	if (!(X509_set_subject_name(x509, name)))
		goto error;

	X509_NAME* name_issuer = X509_NAME_new();
	if (!name_issuer)
		goto error;
	if ((nid = OBJ_txt2nid(&quot;organizationName&quot;)) == NID_undef)
		goto error;
	if (!(X509_NAME_add_entry_by_NID(name_issuer, nid, MBSTRING_ASC, (unsigned char*)pszOrgName, -1, -1, 0)))
		goto error;
	if ((nid = OBJ_txt2nid(&quot;commonName&quot;)) == NID_undef)
		goto error;
	if (!(X509_NAME_add_entry_by_NID(name_issuer, nid, MBSTRING_ASC, (unsigned char*)cnameSign, -1, -1, 0)))
		goto error;
	if (!(X509_set_issuer_name(x509, name_issuer)))
		goto error;

	if (!X509_time_adj(X509_get_notBefore(x509), 0, &amp;amp;start_time))
		goto error;

	if (!X509_time_adj(X509_get_notAfter(x509), 0, &amp;amp;end_time))
		goto error;
	if (!X509_set_pubkey(x509, pkey))
		goto error;
	if (!X509_sign(x509, sign_pkey, EVP_sha1()))
		goto error;

	goto done;

error:
	if (x509)
	{
		X509_free(x509);
		x509 = NULL;
	}

done:
	if (sign_pkey)
		EVP_PKEY_free(sign_pkey);
	if (pkey)
		EVP_PKEY_free(pkey);
	if (name)
		X509_NAME_free(name);
	if (name_issuer)
		X509_NAME_free(name_issuer);

	return x509;
}
</pre>
<p>An error report function for an SSL object.</p>
<pre class="brush: cpp;">void reportError(SSL* ssl, int result)
{
	if (result &lt;= 0)
	{
		int error = SSL_get_error(ssl, result);

		switch (error)
		{
			case SSL_ERROR_ZERO_RETURN:
				cout &lt;&lt; &quot;SSL_ERROR_ZERO_RETURN&quot; &lt;&lt; endl;
				break;
			case SSL_ERROR_NONE:
				cout &lt;&lt; &quot;SSL_ERROR_NONE&quot; &lt;&lt; endl;
				break;
			case SSL_ERROR_WANT_READ:
				cout &lt;&lt; &quot;SSL_ERROR_WANT_READ&quot; &lt;&lt; endl;
				break;
			default:
			{
				char buffer[256];

				while (error != 0)
				{
					ERR_error_string_n(error, buffer, sizeof(buffer));

					cout &lt;&lt; &quot;Error: &quot; &lt;&lt; error &lt;&lt; &quot; - &quot; &lt;&lt; buffer &lt;&lt; endl;

					error = ERR_get_error();
				}
			}

			break;
		}
	}
}
</pre>
<p>The main function begins by setting up OpenSSL synchronization primitives, then creates a new SSL   context object <code>SSL_CTX</code>. This global OpenSSL context should not be mistaken as the per-connection   context object <code>SSL</code> that will be introduced later.</p>
<pre class="brush: cpp;">int _tmain(int argc, _TCHAR* argv[])
{
#ifdef _DEBUG
	// OpenSSL internal memory-leak checkers
	CRYPTO_malloc_debug_init();
	CRYPTO_dbg_set_options(V_CRYPTO_MDEBUG_ALL);
	CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
#endif

	openssl_locks = new Synchronizer[CRYPTO_num_locks()];

	// callbacks for static lock
	CRYPTO_set_locking_callback(funcOpenSSLLockingCallback);
	CRYPTO_set_id_callback(funcOpenSSLIDCallback);

	// callbacks for dynamic lock
	CRYPTO_set_dynlock_create_callback(funcOpenSSLDynCreateCallback);
	CRYPTO_set_dynlock_destroy_callback(funcOpenSSLDynDestroyCallback);
	CRYPTO_set_dynlock_lock_callback(funcOpenSSLDynLockCallback);

	// Load algorithms and error strings.
	SSL_load_error_strings();
	SSL_library_init();

	// Compatible with SSLv2, SSLv3 and TLSv1
	SSL_METHOD* method = SSLv23_server_method();

	// Create new context from method.
	SSL_CTX* ctx = SSL_CTX_new(method);

	// SSL_new() creates a new SSL structure which is needed to hold the data for a TLS/SSL connection.
	// The new structure inherits the settings of the underlying context ctx: connection method (SSLv2/v3/TLSv1),
	// options, verification settings, timeout settings.
</pre>
<p>The next thing to do is key-pair/certificate generation for public key en/decryption. This sample   application uses a self-signed certificate to omit the need of a valid certificate.</p>
<pre class="brush: cpp;">	RSA* rsa = RSA_generate_key(1024, 65537, NULL, NULL);
	if (!rsa)
	{
		cerr &lt;&lt; &quot;RSA_generate_key&quot; &lt;&lt; endl;
		return 1;
	}

	const char* pszCommonName = &quot;test name&quot;;
	X509* cert = create_certificate(rsa, rsa, pszCommonName, pszCommonName, &quot;DICE&quot;, 3 * 365 * 24 * 60 * 60 /* 3 years */);
	if (!cert)
	{
		cerr &lt;&lt; &quot;Couldn't create a certificate&quot; &lt;&lt; endl;
		return 1;
	}

	///////////////////////////////////////////////////////////////////////////

	if (SSL_CTX_use_RSAPrivateKey(ctx, rsa) &lt;= 0)
	{
		ERR_print_errors_fp(stderr);
		return 1;
	}

	if (SSL_CTX_use_certificate(ctx, cert) &lt;= 0)
	{
		ERR_print_errors_fp(stderr);
		return 1;
	}

	RSA_free(rsa);

	X509_free(cert);

	if (!SSL_CTX_check_private_key(ctx))
	{
		cerr &lt;&lt; &quot;Private key is invalid.&quot; &lt;&lt; endl;
		return 1;
	}
	else
		cout &lt;&lt; &quot;Private key is OK&quot; &lt;&lt; endl;

	//////////////////////////////////////////////////////////////////////////////////
</pre>
<p>Setting up Winsock 2 and a listener socket to create a network server.</p>
<pre class="brush: cpp;">	cout &lt;&lt; &quot;Preparing socket&quot; &lt;&lt; endl;

	WORD wVersionRequested = MAKEWORD(2,1);
	WSADATA wsaData;
	int nRet = WSAStartup(wVersionRequested, &amp;amp;wsaData);

	if (wsaData.wVersion != wVersionRequested)
	{
		cerr &lt;&lt; &quot;Wrong version&quot; &lt;&lt; endl;
		return 1;
	}

	SOCKET	listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (listenSocket == INVALID_SOCKET)
	{
		cerr &lt;&lt; &quot;socket()&quot; &lt;&lt; endl;
		return 1;
	}

	SOCKADDR_IN saServer;
	saServer.sin_family = AF_INET;
	saServer.sin_addr.s_addr = INADDR_ANY;
	saServer.sin_port = htons(443);

	nRet = bind(listenSocket, (LPSOCKADDR)&amp;amp;saServer, sizeof(struct sockaddr));
	if (nRet == SOCKET_ERROR)
	{
		closesocket(listenSocket);
		cerr &lt;&lt; &quot;bind()&quot; &lt;&lt; endl;
		return 1;
	}

	nRet = listen(listenSocket, SOMAXCONN);
	if (nRet == SOCKET_ERROR)
	{
		closesocket(listenSocket);
		cerr &lt;&lt; &quot;listen()&quot; &lt;&lt; endl;
		return 1;
	}

	bool bQuit = false;

	cout &lt;&lt; &quot;Ready&quot; &lt;&lt; endl;
</pre>
<p>Everything is ready, now let&#8217;s get into the actual server connection loop. When it accepts a new connection,   it creates a new SSL object that encapsulates a single SSL connection. Also it creates memory BIO buffers   for input and output.</p>
<pre class="brush: cpp;">	while (1)
	{
		SSL* ssl = SSL_new(ctx);
		BIO* bioIn = BIO_new(BIO_s_mem());
		BIO* bioOut = BIO_new(BIO_s_mem());

		// SSL_set_bio() connects the BIOs rbio and wbio for the read and write operations of the TLS/SSL
		// (encrypted) side of ssl.
		SSL_set_bio(ssl, bioIn, bioOut);

		SSL_set_accept_state(ssl);

		SOCKET remoteSocket = accept(
			listenSocket,
			NULL,
			NULL
		);

		if (remoteSocket == INVALID_SOCKET)
		{
			closesocket(listenSocket);
			cerr &lt;&lt; &quot;accept()&quot; &lt;&lt; endl;
			return 1;
		}

		cout &lt;&lt; &quot;Accepted&quot; &lt;&lt; endl;

		char szBuf[128];

		memset(szBuf, 0, sizeof(szBuf));

		bool bHandShakeOver = false;
		bool bReplyOver = false;

		const char* reply = &quot;HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 14\r\nConnection: close\r\n\r\nthis is a test&quot;;

		string strBuffer;

		while (1)
		{
			nRet = recv(remoteSocket, szBuf, sizeof(szBuf),	0);
			if (nRet == SOCKET_ERROR)
			{
				cerr &lt;&lt; &quot;recv() - SOCKET_ERROR&quot; &lt;&lt; endl;
				break;
			}

			if (nRet &gt; 0)
			{
				int bufferUsed = BIO_write(bioIn, szBuf, nRet);
				if (bufferUsed == -1 || bufferUsed == -2 || bufferUsed == 0)
				{
					// error
				}

				// SSL read loop
				while (1)
				{
					// SSL_read() tries to read num bytes from the specified ssl into the buffer buf.
					int bytesOut = SSL_read(ssl, (void*)szBuf, sizeof(szBuf));
					if (bytesOut &gt; 0)
					{
						strBuffer.append(szBuf, bytesOut);

						if ((strBuffer.size() &gt; 4 &amp;amp;&amp;amp; strBuffer.find(&quot;\r\n\r\n&quot;, strBuffer.size() - 4) != string::npos)
							|| (strBuffer.size() &gt; 2 &amp;amp;&amp;amp; strBuffer.find(&quot;\n\n&quot;, strBuffer.size() - 2) != string::npos)
						)
						{
							cout &lt;&lt; &quot;message size: &quot; &lt;&lt; strBuffer.size() &lt;&lt; endl &lt;&lt; &quot;message: &quot; &lt;&lt; strBuffer &lt;&lt; endl;

							bReplyOver = true;

							if (strBuffer.find(&quot;quit&quot;) != string::npos)
								bQuit = true;

							// SSL_write() writes num bytes from the buffer buf into the specified ssl connection.
							int result = SSL_write(ssl, reply, strlen(reply));
							if (result &lt;= 0)
							{
								reportError(ssl, result);
								bReplyOver = true;
							}

							break;
						}
					}
					else
					{
						if (SSL_want_read(ssl))
						{
							cout &lt;&lt; &quot;SSL_want_read&quot; &lt;&lt; endl;
						}
						else
						{
							reportError(ssl, bytesOut);
							bReplyOver = true;
							break;
						}

						if (!bHandShakeOver &amp;amp;&amp;amp; SSL_is_init_finished(ssl))
						{
							cout &lt;&lt; &quot;Handshake has been finished&quot; &lt;&lt; endl;
							bHandShakeOver = true;

							char cipdesc[128];
							SSL_CIPHER* sc = SSL_get_current_cipher(ssl);
							if (sc)
								cout &lt;&lt; &quot;encryption: &quot; &lt;&lt; SSL_CIPHER_description(sc, cipdesc, sizeof(cipdesc)) &lt;&lt; endl;
						}

						break;
					}
				}
			}
			else if (nRet == 0)
			{ // the connection has been gracefully closed
				break;
			}

			while (1)
			{
				// BIO_ctrl_pending() returns the number of bytes buffered in a BIO.
				size_t pending = BIO_ctrl_pending(bioOut);
				if (pending &gt; 0)
				{
					cout &lt;&lt; &quot;BIO_ctrl_pending(bioOut) == &quot; &lt;&lt; pending &lt;&lt; endl;

					//  BIO_read() attempts to read len bytes from BIO b and places the data in buf.
					int bytesToSend = BIO_read(bioOut, (void*)szBuf, sizeof(szBuf) &gt; pending ? pending : sizeof(szBuf));
					if (bytesToSend &gt; 0)
					{
						cout &lt;&lt; &quot;BIO_read(bioOut) == &quot; &lt;&lt; bytesToSend &lt;&lt; endl;

						int sent = 0;
						while (1)
						{
							nRet = send(remoteSocket, szBuf + sent, bytesToSend, 0);
							if (nRet == SOCKET_ERROR)
							{
								bReplyOver = true;
								cerr &lt;&lt; &quot;send() - SOCKET_ERROR&quot; &lt;&lt; endl;
								break;
							}
							else
							{
								sent += nRet;
								bytesToSend -= nRet;
								if (bytesToSend == 0)
									break;
							}
						}
					}
					else if (!BIO_should_retry(bioOut))
					{// BIO_should_retry() is true if the call that produced this condition should then be retried at a later time.
						reportError(ssl, bytesToSend);
					}
				}
				else
				{
					cout &lt;&lt; &quot;BIO_ctrl_pending(bioOut) == 0&quot; &lt;&lt; endl;
					break;
				}
			}

			if (bReplyOver)
			{
				cout &lt;&lt; &quot;post-reply&quot; &lt;&lt; endl;
				break;
			}
		}
</pre>
<p>After it&#8217;s done you have to clean up all resources. If there&#8217;s a memory leak, CRYPTO_mem_leaks_fp will print it out.</p>
<pre class="brush: cpp;">		closesocket(remoteSocket);

		// this frees associated BIO too, so no need for BIO_free
		SSL_free(ssl);

		if (bQuit)
			break;
	}

	closesocket(listenSocket);

	WSACleanup();

	//////////////////////////////////////////////////////////////////////////////////

	SSL_CTX_free(ctx);

	CRYPTO_set_dynlock_create_callback(NULL);
	CRYPTO_set_dynlock_destroy_callback(NULL);
	CRYPTO_set_dynlock_lock_callback(NULL);

	CRYPTO_set_locking_callback(NULL);
	CRYPTO_set_id_callback(NULL);

	delete[] openssl_locks;
	openssl_locks = 0;

	EVP_cleanup();
	CRYPTO_cleanup_all_ex_data();
	ERR_remove_state(0);
	ERR_free_strings();

#ifdef _DEBUG
	CRYPTO_mem_leaks_fp(stdout);
#endif

	return 0;
}
</pre>
<p>When you run this sample code it listens to the standard HTTPS port 443. Start with a web   browser and open &#8220;https://127.0.0.1/&#8221; to see how an SSL connection is processed. To stop this server,   put &#8220;quit&#8221; in a request URL (&#8220;https://127.0.0.1/quit&#8221;).</p>
]]></content:encoded>
			<wfw:commentRss>http://zzz.zggg.com/2007/12/22/ssl-server-with-openssl-memory-bio-a-k-a-prerequisite-to-asynchronous-openssl/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>C++ Asynchronous Delegate for Microsoft Windows</title>
		<link>http://zzz.zggg.com/2007/12/12/cpp-asynchronous-delegate-for-microsoft-windows/</link>
		<comments>http://zzz.zggg.com/2007/12/12/cpp-asynchronous-delegate-for-microsoft-windows/#comments</comments>
		<pubDate>Tue, 11 Dec 2007 18:07:44 +0000</pubDate>
		<dc:creator>RyuK</dc:creator>
				<category><![CDATA[code]]></category>

		<guid isPermaLink="false">http://zzz.zggg.com/?p=1</guid>
		<description><![CDATA[Microsoft Windows 2000 and later have a very useful system function to make an asynchronous function call: QueueUserWorkItem. With this function and its thread pool that is aware of what Windows is actually doing at a given time, Windows takes care of all asynchronous function call complicatedness for you in the simplest form. This high-level [...]]]></description>
			<content:encoded><![CDATA[<p>Microsoft Windows 2000 and later have a very useful system function to make an asynchronous function   call: <a href="http://msdn2.microsoft.com/en-us/library/ms684957.aspx" target="_blank"><code>QueueUserWorkItem</code></a>.   With this function and its thread   pool that is aware of what Windows is actually doing at a given time, Windows takes care of all asynchronous   function call complicatedness for you in the simplest form. This high-level function is a god-send   for lazy programmers who would concentrate on what an application can do in a reasonable performance   range rather than bothering about how it does things with the smallest performance hit.</p>
<p>But people can never be lazy enough, setting it up with context   information each time will soon become a boring task especially when you want to asynchronously call a member function of a C++ object. But it&#8217;s not possible to make it completelly dynamic,   either. You have to manually write a wrapper function, since <a href="http://msdn2.microsoft.com/en-us/library/ms684957.aspx" target="_blank"><code>QueueUserWorkItem</code></a> is   a mere C function that  knows jack about C++. This article introduces a minimalistic toolkit <code>AsyncDelegate.h</code> that lends itself to solving this  issue by using C++ templates.<br />
<span id="more-1"></span><br />
To use <code>AsyncDelegate.h</code>, declare an asynchronously called member function   and define an inner class for context information. This inner class can have a ctor/dtor if needed.</p>
<pre class="brush: cpp;">#include &lt;string&gt;
#include &quot;AsyncDelegate.h&quot;

class X
{
public:

// ...

	struct ContextY : AsyncDelegateContextHeader&lt;X&gt;
	{ // you can add parameters as members.
		std::string strParameter_;
	};

	void callbackY(ContextY* pContext);
};
</pre>
<p><code>X::callbackY</code> is defined like this in a .cpp file:</p>
<pre class="brush: cpp;">void X::callbackY(ContextY* pContext)
{
	// do something with pContext_-&gt;strParameter_
}
</pre>
<p>Now you can activate it anywhere you want where a pointer to a target X object is available.</p>
<pre class="brush: cpp;">X* p = new X;

// ...

{
	AsyncDelegate&lt;
		X,
		X::ContextY,
		&amp;amp;amp;X::callbackY
	&gt; c(p);

	c.pContext_-&gt;strParameter_ = &amp;quot;parameter&amp;quot;;

	// an asynchronous call to X::callbackY is implicitly initiated here by the destructor of c
}
</pre>
<p><code>X::callbackY</code> is called in the context of the specific <code>X</code> object <code>p</code> with   a <code>ContextY</code> object   as a parameter. In other words, <code>AsyncDelegate.h</code> is a tool to do deferred   execution of this member function call.</p>
<pre class="brush: cpp;">X* p = new X;

ContextY pContext;

p-&gt;callbackY(pContext);
</pre>
<p>Let&#8217;s take a look at the actual <code>AsyncDelegate.h</code>.</p>
<pre class="brush: cpp;">// AsyncDelegate.h

#ifndef CALC_ASYNCDELEGATE_H
#define CALC_ASYNCDELEGATE_H

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif _WIN32_WINNT

#include &lt;windows.h&gt;
</pre>
<p><code>AsyncDelegateContextHeader</code> is inserted before a context object as a hidden information header that knows to which object this call belongs.</p>
<pre class="brush: cpp;">
template &lt;class T&gt;
struct AsyncDelegateContextHeader
{
	T* pSelf_;
};
</pre>
<p>This class casts a void parameter into T and destructs it at the end of a function call.</p>
<pre class="brush: cpp;">
template &lt;class T&gt;
class AsyncDelegateContextCleaner
{
private:
	AsyncDelegateContextCleaner() {}
public:
	T* pContext_;

	explicit AsyncDelegateContextCleaner(LPVOID lpParameter) : pContext_((T*)lpParameter) {}
	~AsyncDelegateContextCleaner()
	{
		delete(pContext_);
	}
};
</pre>
<p><code>MemFunPointerCreator</code> is a class to make a C++ member  function pointer type from 2 template parameter types. This class is  required to put a member function pointer in the template parameters of <code>queueUserWorkItemThreadProc</code>.</p>
<pre class="brush: cpp;">template &lt;class T, class C&gt;
struct MemFunPointerCreator
{
	typedef void (T::* ClientMemFunPointerType)(C*);
};
</pre>
<p>This  function template creates a function called by <code>QueueUserWorkItem</code>. A C++ member   function pointer created by <code>MemFunPointerCreator</code> is set as a non-type template parameter.</p>
<pre class="brush: cpp;">template &lt;class T, class C, typename MemFunPointerCreator&lt;T, C&gt;::ClientMemFunPointerType pMemberFunction&gt;
DWORD WINAPI queueUserWorkItemThreadProc(LPVOID lpParameter)
{
	AsyncDelegateContextCleaner&lt;C&gt; c(lpParameter);

	((c.pContext_-&gt;pSelf_)-&gt;*pMemberFunction)(c.pContext_);

	return 1;
}
</pre>
<p><span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; line-height: 19px; white-space: normal; font-size: 13px;">This is the main class exposed to a user. If <code>bLong</code> is true <code>QueueUserWorkItem</code> is   called with <code>WT_EXECUTELONGFUNCTION</code> that implies spawning a new thread which is the default   behavior of this tool.</span></p>
<pre class="brush: cpp;">template &lt;
	class T,
	class C,
	typename MemFunPointerCreator&lt;T, C&gt;::ClientMemFunPointerType pMemberFunction,
	bool bLong_ = true
&gt;
struct AsyncDelegate
{
	C* pContext_;

	explicit AsyncDelegate(T* p)
	{
		pContext_ = new C;
		pContext_-&gt;pSelf_ = p;
	}

	~AsyncDelegate()
	{
		QueueUserWorkItem(
			queueUserWorkItemThreadProc&lt;T, C, pMemberFunction&gt;, // starting address
			pContext_, // function data
			bLong_ ? WT_EXECUTELONGFUNCTION : WT_EXECUTEDEFAULT // worker options
		);
	}
};

#endif CALC_ASYNCDELEGATE_H
</pre>
<p><span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; line-height: 19px; white-space: normal; font-size: 13px;">It may be counter-intuitive to call this tool a &#8220;delegate&#8221; as it doesn&#8217;t support copy and other smart   pointer properties, but it&#8217;s OK for me because its usage is limited to kicking a worker thread immediately.</span></p>
]]></content:encoded>
			<wfw:commentRss>http://zzz.zggg.com/2007/12/12/cpp-asynchronous-delegate-for-microsoft-windows/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>はてなwebサービスAPIを用いたPerl/Ruby webアプリケーション2題</title>
		<link>http://zzz.zggg.com/2007/01/11/%e3%81%af%e3%81%a6%e3%81%aaweb%e3%82%b5%e3%83%bc%e3%83%93%e3%82%b9api%e3%82%92%e7%94%a8%e3%81%84%e3%81%9fperlruby-web%e3%82%a2%e3%83%97%e3%83%aa%e3%82%b1%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b32%e9%a1%8c/</link>
		<comments>http://zzz.zggg.com/2007/01/11/%e3%81%af%e3%81%a6%e3%81%aaweb%e3%82%b5%e3%83%bc%e3%83%93%e3%82%b9api%e3%82%92%e7%94%a8%e3%81%84%e3%81%9fperlruby-web%e3%82%a2%e3%83%97%e3%83%aa%e3%82%b1%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b32%e9%a1%8c/#comments</comments>
		<pubDate>Wed, 10 Jan 2007 23:22:33 +0000</pubDate>
		<dc:creator>RyuK</dc:creator>
				<category><![CDATA[code]]></category>

		<guid isPermaLink="false">http://zzz.zggg.com/?p=153</guid>
		<description><![CDATA[Webサービスというものが新しい技術として流行ったのは2001年頃だった。そこで語られていたビジョンというのは、WSDLで定義したWebサービスAPIをUDDIに登録し、それらがSOAPで通信しながらWeb上に広がるアプ [...]]]></description>
			<content:encoded><![CDATA[<p>Webサービスというものが新しい技術として流行ったのは2001年頃だった。そこで語られていたビジョンというのは、WSDLで定義したWebサービスAPIをUDDIに登録し、それらがSOAPで通信しながらWeb上に広がるアプリケーションを構成するというようなものである。MS          Windows的に言うと、レジストリに登録されたCOMコンポーネントのインターフェイス発見/呼び出しメカニズムのインターネット版ということにな る。ただし、Windowsの場合は、DCOMやCOM+といった、Windowsシステム同士のネットワークやWindowsシステム内部を一貫した分散オブジェクトRPCの文脈でとらえる仕組みを経て、一度レガシーを整理し、.NETに至る。このシステムをビルディングブロックとして利用することが宣伝されたHailstormというマイクロソフト提供のプラットフォームは、個人向けWebサービスをUDDI経由で提供するという触れ込みだった。その後Hailstormは、シングルサインオン認証をめぐる覇権争いに巻き込まれた挙げ句、開放された世界での商業的キーワードとしては消滅してしまった。他方、同時期にローンチして以来コントロールされた閉鎖環境で運営されてきているシングルサインオンの理想型が、有料サービスXbox         Liveとして存続している。<br />
<span id="more-153"></span></p>
<p>その時点まで一旦遡ってから改めて俯瞰すると、最近のWebサービスを巡る再評価の流れもそれがどうしたと斜に構えやすい。XMLも5年くらい前に流行り、当時はXMLスキーマが今後重要になると言われていたが、そのあたりの知識が役に立つ局面が以後拡大したとは思えない。そういうわけで、この辺りの物事は個人的な関心からは長いこと外れていた。2007年の現在それらはつまらない玩具のようなものに留まるのか、それとも個人ユーザにとって真に使える道具なのか。近頃の各種Webサービスは、APIを開放し、プレスリリースを出して客寄せを行い、利用者が増えたら閉じるという、提供企業による手軽な宣伝活動の産物であり、個人ユーザ囲い込みの道具である。それを利用する個人の動機はWeb広告業界に投じられている金で、その量が5年前とは相当異なる以上、以前とは位相が異なってはいるわけだ。Webサービス同士の競争が起こり良質なサービスが数多く提供されるまでになれば面白くなるかもしれない。しかし、Webサービスを介して提供される元ネタが数少ない企業に独占されている現状では、そんなシナリオが実現する蓋然性は低く、バリエーションの少ない似通ったWebサービスを「マッシュアップ」と称してデプロイする個人は、IT企業の走狗として活動しながら細々と広告収入を稼 ぐことになる。</p>
<p>今回の試みでは、「<a href="http://www.hatena.ne.jp/" target="_blank">はてな</a>」 の認証WebサービスAPIと、「はてな」のキーワードWebサービスAPIを利用している。1つ目のWebアプリケーションは、「はてな」認証APIを 利用した、「はてな」ユーザが特定の他ユーザに外部サーバを介して任意のデータファイルを渡すためのファイルアップローダである。言語はRubyで、RubyのWebサーバ<a href="http://mongrel.rubyforge.org/" target="_blank">Mongrel</a>のモジュールとして書かれている。もう1つのWebアプリケーションは、はてなキーワードAPIを利用した穴埋めクイズ作成と、はてなキーワード連想グラフの視覚化を行う。言語はPerlで、PerlのWebサーバ<a href="http://search.cpan.org/dist/POE-Component-Server-HTTP/" target="_blank">POE::Component::Server::HTTP</a>のモジュールとして書かれている。</p>
<p>これらは、双方ともスクリプト内でWebサーバモジュールをインクルードするので、コンソールからrubyなりperlなりを通してスクリプトを実行すればそのまま動作し、ホスト用のWebサーバを別途必要としない。起動時に設定ファイルの内容を読んで、以降はWebサーバの一部としてリクエストに応じるWebアプリケーションである。<a href="http://zzz.zggg.com/2006/11/15/perl-ruby-multithreading-embedding/" target="_blank">前回のPerlとRubyの比較</a>ではマルチスレッドと組み込みがポイントだったのに対し、今回は、Webアプリケーションの作成と簡易Webサーバという、Webアプリケーションをめぐる 環境の比較を行うという趣向だ。マルチスレッドに関しては、Mongrelが当然のようにマルチスレッドで、モジュールも対応が必要なのに対し、POEの 方はマルチスレッドに対応していないようである。</p>
<p>双方のWebサーバともプロダクション環境に適した性能を有するサーバではなく、またここで解説するWebアプリケーションはXSS対策や SQLインジェクション対策などのセキュリティ上のケアを欠いているので、あくまで各WebサービスAPIの動作サンプルとしてのみ御覧いた だきたい。ソースコードの文字コードは双方ともUTF-8で、Windows       XP SP2上にて作成し、Microsoft Internet Explorer 7ならびにFirefox 3.0a1 trunk  build 20061218で動作確認している。「はてな」のWeb        APIの仕様は本記事を書いた2006年末の時点のものに依っているので、仕様変更によって任意の時点でこれらのアプリケーションが動かなくなっている可能性もある。</p>
<p>では一つ目の、Rubyアプリケーションの方から見ていこう。<code>hatenawebapp1.conf</code>は設定ファイルで、YAMLフォーマットである。Webサーバのポートや、認証APIに与えるキー、データベースファイル名などを設定する。</p>
<pre class="brush: ruby;"># hatenawebapp1.conf
#
# configuration file for hatenawebapp1.rb

# Bound address
bound_address: &quot;127.0.0.1&quot;

# Bound port
bound_port: 80

# API key for Hatena Auth
api_key: &quot;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;

# Private key for Hatena Auth
private_key: &quot;xxxxxxxxxxxxxxxx&quot;

# File name for the SQLite database
db_filename: &quot;hatenawebapp1.db&quot;

# Path for uploaded files
file_store_path: &quot;./store&quot;
</pre>
<p>以下はスクリプト本体<code>hatenawebapp1.rb</code>で、<code>ruby 1.8.5 (2006-12-04 patchlevel 2) [i386-mswin32]</code>で動作確認した。必要ライブラリは、<br />
<code>RubyGems-0.9.0<br />
mongrel-0.3.13.3-mswin32<br />
sqlite3-ruby-1.1.0-mswin32<br />
hatenaapiauth-0.1.0<br />
uuidtools-1.0.0<br />
scrapi-1.2.0</code><br />
と、<a href="http://rubyforge.org/projects/rubygems/" target="_blank">rubygems</a>ツールで取得可能な依存ライブラリである。尚、sqlite3のライブラリのバイナリが別途必要で、Windowsの場合はスクリプトのディレクトリにsqlite3.dllを、Unixの場合も<a href="http://www.sqlite.org/" target="_blank">SQLite公式サイト</a>で入手できるライブラリのバイナリをパスの通った場所へ置く必要がある。また、Mongrelは0.3.13.3を使用しているが、アップデートが頻繁なので異なるバージョンは不具合が出る可能性もある。</p>
<pre class="brush: ruby;">
=begin

hatenawebapp1.rb

Sample Web Application 1 with Hatena Web Service API : File Transfer between Hatena Users

by RyuK (klassphere[at.mark]gmail.com)

http://zzz.zggg.com/

http://aiueo.da.ru/

[Requirements (tested on Microsoft Windows XP)]

ruby 1.8.5 (2006-12-04 patchlevel 2) [i386-mswin32]

mongrel-0.3.13.3-mswin32
sqlite3-ruby-1.1.0-mswin32
hatenaapiauth-0.1.0
uuidtools-1.0.0
scrapi-1.2.0

... and other dependent Ruby gems

=end

require 'rubygems'

require 'mongrel'
require 'yaml'
require 'sync'
require 'fileutils'
require 'pathname'
require 'sqlite3'
require 'hatena/api/auth'
require 'uuidtools'
require 'scrapi'
</pre>
<p>YAMLの設定ファイルをロードし、SQLiteデータベースのテーブルを無ければ新規作成する。</p>
<pre class="brush: ruby;">

conf_filename = $PROGRAM_NAME.clone
begin
	$conf = YAML.load_file conf_filename.sub!(/\.rb/, '.conf')

	pn = Pathname.new($conf[&quot;file_store_path&quot;])
	begin
		$conf[&quot;file_store_path&quot;] = pn.realpath
	rescue SystemCallError
		Dir.mkdir(pn.to_s, 0701)
		$conf[&quot;file_store_path&quot;] = pn.realpath
	end

	$db = SQLite3::Database.new($conf[&quot;db_filename&quot;])

rescue Exception =&gt; e
	STDERR.puts e.to_s
	exit(1)
end

# ofn = original file name, rfn = real file name

begin
	$db.execute(&lt;&lt;SQL
create table files(
	sender TEXT,
	receiver TEXT,
	ofn TEXT,
	rfn TEXT,
	date INTEGER,
	size INTEGER
);
SQL
	)
rescue SQLite3::SQLException =&gt; e
	if e.to_s != &quot;table files already exists&quot;
		puts e.to_s
		exit(1)
	end
end

$db.extend(Sync_m)
</pre>
<p>Webサーバのモジュールなので、データベースアクセスのためのグローバルオブジェクト$dbはSync_mを使用してマルチスレッド対応にしておく。アップロードされてくるファイルのファイルサイズと、受け取り済みのデータのサイズとを保存するために、<code>Struct</code>クラスを使って<code>DownloadProgress</code>という構造体を作っておく。さらに、<code>$download_progress</code>という<code>Hash</code>のオブジェクトを生成し、このオブジェクトが<code>QUERY_STRING</code>リクエストパラメータと<code>DownloadProgress</code>オブジェクトとの対応表を保存する。<code>$download_progress</code>は、デザインパターンで言うところのObserverパターンで、ダウンロード状況のview(MVCの&#8217;V')として複数のwebからのリクエストによって同時に参照される可能性があるため、同じようにマルチスレッド対応にしなければならない。</p>
<pre class="brush: ruby;">

$verified_users_ipaddress = Hash.new # ip - user
$verified_users_ipaddress.extend(Sync_m)

$existent_users = Hash.new # user - dummy
$existent_users.extend(Sync_m)

DownloadProgress = Struct.new(&quot;DownloadProgress&quot;, :total_size, :current_size)

$download_progress = Hash.new
$download_progress.extend(Sync_m) # user - DP
</pre>
<p>「はてな」の認証APIの初期化関数を呼び出し、次いで表示するページ内のヘッダをそのままヒアドキュメントを使ってスクリプト内に書いている。</p>
<pre class="brush: ruby;">

$hatena_auth = Hatena::API::Auth.new(:api_key =&gt; $conf[&quot;api_key&quot;], :secret =&gt; $conf[&quot;private_key&quot;])

$page_head =&lt;&lt;EOS
&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt;
&lt;title&gt;#{$PROGRAM_NAME}&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;Proof of Concept: File Transfer between Hatena Users&lt;/p&gt;
EOS

$page_end =&lt;&lt;EOS
&lt;/body&gt;
&lt;/html&gt;
EOS

def page_head_with_onload(onload)
	&lt;&lt;EOS
&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt;
&lt;title&gt;#{$PROGRAM_NAME}&lt;/title&gt;
&lt;/head&gt;
&lt;body onload=&quot;#{onload}&quot;&gt;
&lt;p&gt;Proof of Concept: File Transfer between Hatena Users&lt;/p&gt;
EOS

end
</pre>
<p>以下に、Mongrelのハンドラ関数群が続く。各種デフォルトハンドラをオーバーライドすることにより、WebアプリケーションはMongrelの動作をカスタマイズするというのがMongrelモジュールの基本コンセプトである。まずは、ユーザが最初にサーバのルートパス(&#8216;/&#8217;)にアクセスしたときに、「はてな」のIDとパスワードを認証APIに対して差し出すように促す。<code>process</code>というメソッドが       ユーザ定義フィルタの役割を果たし、<code>request</code>を受けて<code>response</code>を返す。</p>
<pre class="brush: ruby;">

# Mongrel handlers #########################################

class RootHandler &lt; Mongrel::HttpHandler
	def process(request, response)
		response.start do |head, out|
			head[&quot;Content-Type&quot;] = &quot;text/html&quot;

			out &lt;&lt; &lt;&lt;EOS
#{$page_head}

&lt;p&gt;If you are a Hatena user and want to transfer a file to another user,
&lt;a href=\&quot;#{$hatena_auth.uri_to_login}\&quot;&gt;please follow this link&lt;/a&gt; to the uploader.&lt;/p&gt;

#{$page_end}
EOS
		end
	end
end
</pre>
<p>このスクリプトの一番最後にMongrel起動時の初期設定を行う部分があるので、そこを見てもらうとして、この<code>UploaderHandler</code>は、&#8221;/uploader&#8221;       というパスにアクセスした場合のハンドラである。リクエスト中のクエリ文字列(<code>request.params["QUERY_STRING"]</code>)を解析し、認証情報       を取り出して「はてな」認証APIに与える。「はてな」ユーザとして認証されると、<code>$verified_users_ipaddress</code>ハッシュ表にリモートIPアドレスとユーザ名の組が保存される。ちなみに、このアプリケーションはIPアドレス1つあたり1ユーザとして認識しており、まともなセッション管理を行っていないので、実際に使用するには厳密なセッション管理が必要である。</p>
<pre class="brush: ruby;">

class UploaderHandler &lt; Mongrel::HttpHandler
	def show_error(response, message)
		response.start do |head, out|
			head[&quot;Content-Type&quot;] = &quot;text/html&quot;
			out &lt;&lt; message
		end
	end

	def process(request, response)
		unknown_error_page =&lt;&lt;EOS
#{$page_head}

&lt;p&gt;Error: Unknown Error&lt;/p&gt;
&lt;script language=&quot;JavaScript&quot;&gt;
&lt;!--
history.go(-1)
//--&gt;
&lt;/script&gt;

#{$page_end}
EOS

		auth_error_page =&lt;&lt;EOS
#{$page_head}

&lt;p&gt;Error: Authorization Failed&lt;/p&gt;

#{$page_end}
EOS

		cert_key = &quot;&quot;
		if request.params[&quot;QUERY_STRING&quot;] =~ /cert=([^&amp;amp;]+)/
			cert_key = $1
		else
			show_error(response, unknown_error_page)
			return
		end

		user = nil
		begin
			user = $hatena_auth.login(cert_key)
		rescue Hatena::API::AuthError
			show_error(response, auth_error_page)
			return
		end

		$verified_users_ipaddress.synchronize() do
			if $verified_users_ipaddress.size &gt; 10000
				$verified_users_ipaddress.clear
			end
			$verified_users_ipaddress[request.params[Mongrel::Const::REMOTE_ADDR]] = user['name']
		end
</pre>
<p>このページのヘッダはJavaScriptを含んでいて、AJAXの簡単なフレームワークと、「はてな」ユーザ名の実在性を確かめるメソッド、ファイルのアップロード状態をポーリングしながら進捗バーを動的に表示するメソッドなどを含む。AJAXによる画面遷移無しのファイルアップロードを実現するために、ファイルのアップロード先を隠しiframeにするというテクニックが使用されている。</p>
<pre class="brush: ruby;">
		response.start do |head, out|
			head[&quot;Content-Type&quot;] = &quot;text/html&quot;

			results =&lt;&lt;EOS
#{page_head_with_onload(&quot;setup('#{user['name']}')&quot;)}
&lt;script language=&quot;JavaScript&quot;&gt;
&lt;!--

var isMozilla = navigator.userAgent.indexOf('Gecko') != -1;
var isIE = window.ActiveXObject;

function createHttpRequest()
{
	if (isIE)
	{
		try
		{ // CLSID_XMLHTTP
			// v 3.0
			return new ActiveXObject(&quot;Msxml2.XMLHTTP&quot;);
		}
		catch (e)
		{
			try
			{// v 2.x
				return new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;);
			}
			catch (e2)
			{
				return null;
			}
		}
	}
	else if (window.XMLHttpRequest) // non-IE
	{
		var hr = new XMLHttpRequest();
		if (isMozilla)
			hr.overrideMimeType('text/xml');
		return hr;
	}
	else
	{
		return null;
	}
}

function sendHTTP(data, method, uri, callback, async, caller)
{
	var hr = createHttpRequest();

	var args = new Array();
	args.push(hr);
	for (var i = 6; i &lt; arguments.length; ++i)
	{
		args.push(arguments[i]);
	}

	try
	{
		hr.open(method, uri, async);
		hr.setRequestHeader(&quot;If-Modified-Since&quot;, &quot;Thu, 01 Jun 1970 00:00:00 GMT&quot;);

		hr.onreadystatechange = function()
		{
			if (hr.readyState == 4)
			{
				callback.apply(caller, args);
			}
		}

		hr.send(data);
		delete hr;
	}
	catch(e)
	{
		alert(&quot;sendHTTP: &quot; + e);
	}
}

function setup(owner_name)
{
	loadFileList(owner_name);
	checkUsername();
}

var filename = &quot;&quot;;
var query_progress = &quot;&quot;;
var time_start = 0;

function startPolling(form)
{
	if (form.filename.value == &quot;&quot;)
	{
		alert(&quot;Invalid file name&quot;);
		return;
	}

	if (form.receiver.value == &quot;&quot;)
	{
		alert(&quot;Invalid user name&quot;);
		return;
	}

	var x = document.getElementById(&quot;hidden_div&quot;);
	if (x)
		document.body.removeChild(x);

	var d = document.createElement('div');
	d.setAttribute(&quot;id&quot;, &quot;hidden_div&quot;);
	d.innerHTML='&lt;iframe id=&quot;hidden_iframe&quot; name=&quot;hidden_iframe&quot; style=&quot;display: none; width: 0px; height: 0px; border: 0px&quot;&gt;&lt;/iframe&gt;';
	document.body.appendChild(d);

	form.button_upload.disabled = true;
	filename = form.filename.value;

	document.getElementById(&quot;form_area&quot;).innerHTML = (&quot;&lt;p&gt;Uploading &lt;b&gt;&quot; + filename + &quot;&lt;/b&gt;&lt;/p&gt;&quot;);

	query_progress = (form.sender.value + '&amp;amp;' + form.receiver.value + '&amp;amp;' + filename);

	form.action = (&quot;/receiver?&quot; + query_progress);

	var d = new Date();
	time_start = d.getTime();

	form.submit();

	pollDownloadProgress();
}
</pre>
<p><code>pollDownloadProgress</code>関数を1秒毎にタイマー呼び出しして/query_progressへAJAX問い合わせを行い、       ダウンロード済みのファイルサイズを更新しつつ進捗バーを伸ばす。</p>
<pre class="brush: jscript;">

function pollDownloadProgress()
{
	sendHTTP('', 'POST', './query_progress' + '?' + query_progress, pollDownloadProgressCallback, true);

	if (filename != &quot;&quot;)
		setTimeout(&quot;pollDownloadProgress();&quot;, 1000);
}

function pollDownloadProgressCallback(hr)
{
	var nodelist = hr.responseXML.getElementsByTagName(&quot;f&quot;);
	if (!nodelist || nodelist.length == 0)
		return;

	var current_size = 0;
	var total_size = 0;

	for (var i = 0; i &lt; nodelist.length; ++i)
	{
		var n = nodelist.item(i);
		if (n == null)
			return;

		var nn = n.firstChild;
		if (nn == null)
			return;

		while (nn != null)
		{
			// nn.textContent == Mozilla only
			// NODE_TEXT == 3 || NODE_CDATA_SECTION == 4
			if (nn != null &amp;amp;&amp;amp; (nn.nodeType == 3 || nn.nodeType == 4))
			{
				if (nn.nodeValue != &quot;&quot;)
				{
					var matched = nn.nodeValue.match(/(\\d+)\\/(\\d+)/);
					if (matched)
					{
						current_size = parseInt(matched[1], 10);
						total_size = parseInt(matched[2], 10);

						setProgressBar(current_size, total_size);
					}
				}
			}

			nn = nn.nextSibling;
		}
	}
}

function setProgressBar(received_size, total_size)
{
	document.getElementById(&quot;d2&quot;).style.width = 400 * (received_size / total_size) + &quot;px&quot;;

	var d = new Date();
	var elapsed_time = (d.getTime() - time_start) / 1000;
	if (elapsed_time &lt;= 0)
		elapsed_time = 1;

	var speed = parseInt(received_size / elapsed_time / 1024, 10);

	if (400 * (received_size / total_size) &gt;= 75)
		document.getElementById(&quot;d1&quot;).innerHTML
			= &quot;&lt;font size=-1&gt;&quot; + parseInt(received_size / total_size * 100) + &quot;% (&quot; + speed.toString() + &quot;KB/s)&lt;/font&gt;&quot;;
}
</pre>
<p>&#8220;/check_username&#8221;というパスに、あるユーザ名が実在の「はてな」ユーザかどうか確かめるサービス(後述の<code>CheckUsernameHandler</code>)が動いているので、そこに対してAJAXで問い合わせを行う。</p>
<pre class="brush: jscript;">

var checked_user = &quot;&quot;;
var check_username_requesting = false;

// onchange doesn't work until the focus is out
function checkUsername()
{
	if (document.getElementById(&quot;receiver&quot;).value != &quot;&quot;
    	&amp;amp;&amp;amp; document.getElementById(&quot;receiver&quot;).value != checked_user &amp;amp;&amp;amp; !check_username_requesting)
	{
		check_username_requesting = true;
		checked_user = document.getElementById(&quot;receiver&quot;).value;
		document.getElementById(&quot;form_area&quot;).innerHTML = (&quot;&lt;p&gt;&lt;blink&gt;Checking if &lt;b&quot;
        		+ checked_user + &quot;&lt;/b&gt; is an existing Hatena user...&lt;/blink&gt;&lt;/p&gt;&quot;);
		sendHTTP(checked_user, 'POST', './check_username', checkUsernameCallback, true);
	}

	setTimeout(checkUsername, 3000);
}

function isNotUser()
{
	document.getElementById(&quot;form_area&quot;).innerHTML = (&quot;&lt;p&gt;&lt;b&gt;&quot; + checked_user + &quot;&lt;/b&gt; is not a Hatena user.&lt;/p&gt;&quot;);
	document.getElementById(&quot;button_upload&quot;).disabled = true;
}

function checkUsernameCallback(hr)
{
	// Can't use responseXML.getElementById() -
	// For responseXML.getElementById() to return an element with the matching id value, XMLHttpRequest
	// implementations must be aware of the underlying schema/DTD that defines an id attribute of type ID.
	// Currently browsers are not schema/DTD aware for XMLHttpRequests, although they support well-known DTDs
	// like HTML and XHTML for documents.

	var nodelist = hr.responseXML.getElementsByTagName(&quot;r&quot;);
	if (!nodelist || nodelist.length == 0)
		isNotUser();

	check_username_requesting = false;
	for (var i = 0; i &lt; nodelist.length; ++i)
	{
		var n = nodelist.item(i);
		if (n == null)
		{
			isNotUser();
			continue;
		}

		var nn = n.firstChild;
		if (nn == null)
		{
			isNotUser();
			continue;
		}

		while (nn != null)
		{
			// nn.textContent == Mozilla only
			// NODE_TEXT == 3 || NODE_CDATA_SECTION == 4
			if (nn != null &amp;amp;&amp;amp; (nn.nodeType == 3 || nn.nodeType == 4))
			{
				if (nn.nodeValue != &quot;&quot;)
				{
					document.getElementById(&quot;button_upload&quot;).disabled = false;
					checked_user = nn.nodeValue;
					document.getElementById(&quot;receiver&quot;).value = nn.nodeValue;
					document.getElementById(&quot;form_area&quot;).innerHTML = (&quot;&lt;p&gt;&lt;b&gt;&quot; + checked_user + &quot;&lt;/b&gt; is an existing Hatena user.&lt;/p&gt;&quot;);
				}
			}

			nn = nn.nextSibling;
		}
	}

	check_username_requesting = false;
}

function loadFileList(owner_name)
{
	sendHTTP('', 'POST', './list_files' + '?' + owner_name, loadFileListCallback, true);
}

function unixtime2localdate(t)
{
	var d = new Date;
	d.setTime(t * 1000);
	return d.toLocaleString();
}

function loadFileListCallback(hr)
{
	var out = &quot;&quot;;

	var nodelist = hr.responseXML.getElementsByTagName(&quot;myfile&quot;);
	if (nodelist)
	{
		out += &quot;&lt;p&gt;Your files sent to other users:&lt;/p&gt;&quot;;
		for (var i = 0; i &lt; nodelist.length; ++i)
		{
			var e = nodelist.item(i);
			out += &quot;&lt;p&gt;&lt;a href='./downloader/&quot;;
			out += e.getAttribute(&quot;rfn&quot;);
			out += &quot;'&gt;&lt;b&gt;&quot;;
			out += e.getAttribute(&quot;ofn&quot;);
			out += &quot;&lt;/b&gt;&lt;/a&gt; (Size: &quot;;
			out += Math.floor(parseInt(e.getAttribute(&quot;size&quot;), 10) / 1024).toString();
			out += &quot;KB - To: &lt;a target='_blank' href='http://www.hatena.ne.jp/user?userid=&quot;;
			out += e.getAttribute(&quot;r&quot;);
			out += &quot;'&gt;&quot;;
			out += e.getAttribute(&quot;r&quot;);
			out += &quot;&lt;/a&gt; - Date: &quot;;
			out += unixtime2localdate(parseInt(e.getAttribute(&quot;d&quot;), 10));
			out += &quot;)&lt;/p&gt;&quot;;
		}
		if (nodelist.length == 0)
			out += &quot;&lt;p&gt;(no files)&lt;/p&gt;&quot;;
	}

	nodelist = hr.responseXML.getElementsByTagName(&quot;sentfile&quot;);
	if (nodelist)
	{
		out += &quot;&lt;p&gt;Files sent to you from other users:&lt;/p&gt;&quot;;
		for (var i = 0; i &lt; nodelist.length; ++i)
		{
			var e = nodelist.item(i);
			out += &quot;&lt;p&gt;&lt;a href='./downloader/&quot;;
			out += e.getAttribute(&quot;rfn&quot;);
			out += &quot;'&gt;&lt;b&gt;&quot;;
			out += e.getAttribute(&quot;ofn&quot;);
			out += &quot;&lt;/b&gt;&lt;/a&gt; (Size: &quot;;
			out += Math.floor(parseInt(e.getAttribute(&quot;size&quot;), 10) / 1024).toString();
			out += &quot;KB - From: &lt;a target='_blank' href='http://www.hatena.ne.jp/user?userid=&quot;;
			out += e.getAttribute(&quot;s&quot;);
			out += &quot;'&gt;&quot;;
			out += e.getAttribute(&quot;s&quot;);
			out += &quot;&lt;/a&gt; - Date: &quot;;
			out += unixtime2localdate(parseInt(e.getAttribute(&quot;d&quot;), 10));
			out += &quot;)&lt;/p&gt;&quot;;
		}
		if (nodelist.length == 0)
			out += &quot;&lt;p&gt;(no files)&lt;/p&gt;&quot;;
	}

	document.getElementById(&quot;files_list&quot;).innerHTML = out;
}

//--&gt;
&lt;/script&gt;

&lt;p&gt;Welcome &lt;b&gt;#{user['name']}&lt;/b&gt; @ Hatena.&lt;/p&gt;
&lt;p&gt;You are now authorized to upload a file to transfer to another Hatena user.&lt;/p&gt;

&lt;span id=&quot;form_area&quot;&gt;&lt;p&gt;Fill in the name of the receiving user and upload a file.&lt;/p&gt;&lt;/span&gt;

&lt;p&gt;
&lt;!-- the colon at the end of startPolling() is required. --&gt;
&lt;form method=&quot;POST&quot; id=&quot;file_form&quot; action=&quot;&quot; enctype=&quot;multipart/form-data&quot;
	target=&quot;hidden_iframe&quot; onsubmit=&quot;startPolling(this); return false;&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;sender&quot; value=&quot;#{user['name']}&quot;&gt;
Receiving Hatena User: &lt;input type=&quot;text&quot; id=&quot;receiver&quot; name=&quot;receiver&quot; size=&quot;16&quot;&gt;&lt;br&gt;&lt;br&gt;
File to upload: &lt;input type=&quot;file&quot; name=&quot;filename&quot; size=&quot;80&quot;&gt;
&lt;input type=&quot;submit&quot; id=&quot;button_upload&quot; value=&quot;Upload this file&quot; disabled&gt;
&lt;/form&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;div id=&quot;empty&quot; style=&quot;background-color: #cccccc; border: 1px solid black; height: 30px; width: 400px; padding: 0px;&quot; align=&quot;left&quot;/&gt;
&lt;div id=&quot;d2&quot; style=&quot;position: relative; top: 0px; left: 0px; background-color: #333333;
height: 30px; width: 0px; padding-top: 5px; padding: 0px;&quot;/&gt;
&lt;div id=&quot;d1&quot; style=&quot;position: relative; top: 0px; left: 0px; color: #f0ffff; height: 30px; text-align: center; font: bold;
	padding: 0px; padding-top: 5px;&quot;/&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/p&gt;

&lt;span id=&quot;files_list&quot;&gt;&lt;/span&gt;

#{$page_end}
EOS
			out &lt;&lt; results
		end
	end
end
</pre>
<p>&#8220;/downloader&#8221;でアクセスできる、他ユーザが自分宛にアップロードしたファイルを受け取りダウンロードするためのURLのハンドラを定義する。</p>
<pre class="brush: ruby;">
class DownloaderHandler &lt; Mongrel::DirHandler
	def initialize(path, listing_allowed=true, index_html=&quot;index.html&quot;)
		super(path, listing_allowed, index_html)

		#Mongrel::DirHandler::add_mime_type(&quot;.zip&quot;, &quot;application/zip&quot;)
		#Mongrel::DirHandler::add_mime_type(&quot;.rar&quot;, &quot;application/x-rar-compressed&quot;)
		#Mongrel::DirHandler::add_mime_type(&quot;.lzh&quot;, &quot;application/x-lzh&quot;);
		#Mongrel::DirHandler::add_mime_type(&quot;.xml&quot;, &quot;text/xml&quot;);
	end

	def process(request, response)
		user = nil
		$verified_users_ipaddress.synchronize(Sync_m::SH) do
			if $verified_users_ipaddress.include?(request.params[Mongrel::Const::REMOTE_ADDR])
				user = $verified_users_ipaddress[request.params[Mongrel::Const::REMOTE_ADDR]]
			end
		end

		unless user
			response.reset
			response.start do |head, out|
				head[&quot;Content-Type&quot;] = &quot;text/html&quot;
				out &lt;&lt; &quot;Authorization Failed - &lt;a href=\&quot;#{$hatena_auth.uri_to_login}\&quot;&gt;Verify again&lt;/a&gt;&quot;
			end
			return
		end

		req_method = request.params[Mongrel::Const::REQUEST_METHOD] || Mongrel::Const::GET
		req_path = can_serve request.params[Mongrel::Const::PATH_INFO]

		if not req_path
			response.reset
			response.start(404) do |head, out|
				out &lt;&lt; &quot;File not found&quot;
			end
     		else
			original_filename = &quot;&quot;
			real_filename = request.params[Mongrel::Const::PATH_INFO].clone
			real_filename.gsub!(&quot;/&quot;, &quot;&quot;);

			if real_filename =~ /[^a-zA-Z0-9_-\.]/ || user =~ /[^a-zA-Z0-9_-]/
				response.reset
				response.start(403) do |head, out|
					out &lt;&lt; &quot;Invalid Request&quot;
				end
				return
			end

			$db.synchronize(Sync_m::SH) do
				$db.execute(&quot;select * from files where rfn = '#{real_filename}' AND receiver = '#{user}'&quot;) do |row|
					original_filename = row[2]
				end
			end

			if original_filename == &quot;&quot;
				response.reset
				response.start(403) do |head, out|
					out &lt;&lt; &quot;Not Authorized&quot;
				end
				return
			end

			response.header[&quot;Content-Disposition&quot;] = &quot;filename=\&quot;#{original_filename}\&quot;&quot;;
			begin
				if req_method == Mongrel::Const::HEAD
					send_file(req_path, request, response, true)
				elsif req_method == Mongrel::Const::GET
					send_file(req_path, request, response, false)
				else
					response.start(403) {|head, out| out.write(Mongrel::ONLY_HEAD_GET) }
				end
			rescue =&gt; details
				STDERR.puts &quot;Error sending file #{req_path}: #{details}&quot;
			end
		end
	end
end
</pre>
<p>&#8220;/receiver&#8221;のパスが、ユーザがファイルをアップロードする対象である。 <code>request_progress</code>メソッドはリクエストのデータを一定量受け取る度にMongrelが呼び出すイベントコールバックで、これをオーバーライドすることによって、アップロードされてくるファイルのアップロード済みデータ量の数値を逐次更新する。<code>Mongrel::CGIWrapper</code>を 使用してHTMLフォームから送信されてくるデータを解析しているが、使用したMongrelのバージョンではファイルをアップロードしている場合に正常 にそれぞれのクエリ要素を受け取れないというバグがあるので、迂回策として生のデータを正規表現で検索している。ファイルのダウンロードが済むと、<code>Mongrel::CGIWrapper</code>の仕様に従って、アップロードされてきたファイルのサイズに応じ、一時バッファもしくは一時ファイルから、実際のファイル保存先へとデータを移す。</p>
<pre class="brush: ruby;">
class ReceiverHandler &lt; Mongrel::HttpHandler
	def initialize
		@request_notify = true
	end

	def request_progress(params, clen, total)
		$download_progress.synchronize() do
			if $download_progress.size &gt; 1000
				$download_progress.clear
			end
			$download_progress[params[&quot;QUERY_STRING&quot;]] = Struct::DownloadProgress.new(total, total - clen)
		end
	end

	def gen_stored_filename()
		return UUID.timestamp_create.to_s
	end

	def process(request, response)
		cgi = Mongrel::CGIWrapper.new(request, response)

		# can't use cgi[&quot;sender&quot;] and cgi[&quot;receiver&quot;] for multipart/form-data due to a possible bug of Mongrel 0.3.13.3

		sender = nil
		receiver = nil
		original_filename = nil

		if request.params[&quot;QUERY_STRING&quot;] =~ /^([^&amp;amp;]+)&amp;amp;([^&amp;amp;]+)&amp;amp;(.+)/
			sender = $1
			receiver = $2
			original_filename = $3
			original_filename.gsub!(&quot;'&quot;, &quot;&quot;)
			original_filename.gsub!(/.*\\/, &quot;&quot;)
			original_filename.gsub!(&quot;.*/&quot;, &quot;&quot;)
		end

		if sender =~ /[^a-zA-Z0-9_-]/ || receiver =~ /[^a-zA-Z0-9_-]/ || original_filename =~ /[^a-zA-Z0-9_-\.\\]/
			response.reset
			response.start(403) do |head, out|
				out &lt;&lt; &quot;Invalid Request&quot;
			end
			return
		end

		$verified_users_ipaddress.synchronize(Sync_m::SH) do
			if $verified_users_ipaddress.include?(request.params[Mongrel::Const::REMOTE_ADDR])
				if sender != $verified_users_ipaddress[request.params[Mongrel::Const::REMOTE_ADDR]]
					sender = nil
				end
			else
				sender = nil
			end
		end

		$existent_users.synchronize(Sync_m::SH) do
			unless $existent_users.include?(receiver)
				receiver = nil
			end
		end

		unless sender &amp;amp;&amp;amp; receiver &amp;amp;&amp;amp; original_filename
			response.start do |head, out|
				head[&quot;Content-Type&quot;] = &quot;text/html&quot;

				out &lt;&lt; &lt;&lt;EOS
#{$page_head}

&lt;script language=&quot;JavaScript&quot;&gt;
&lt;!--

parent.document.getElementById(&quot;form_area&quot;).innerHTML
		= (&quot;&lt;p&gt;An authorization error happened in uploading &lt;b&gt;&quot; + parent.filename
        	+ &quot;&lt;/b&gt;. &lt;a href=\\&quot;#{$hatena_auth.uri_to_login}\\&quot;&gt;Please retry&lt;/a&gt;&lt;/p&gt;&quot;);
parent.filename = &quot;&quot;;
parent.setProgressBar(0, 0);

//--&gt;
&lt;/script&gt;

#{$page_end}
EOS
			end
			return
		end

		$download_progress.synchronize() do
			$download_progress.delete(request.params[&quot;QUERY_STRING&quot;])
		end

		file = cgi['filename']

		real_filename = gen_stored_filename()

		begin
			if file.size &gt;= 10240 then
				FileUtils.cp(file.path, $conf[&quot;file_store_path&quot;] + real_filename)
			else
				open($conf[&quot;file_store_path&quot;] + real_filename, &quot;wb&quot;) do |fh|
					fh.write(file.read)
	       	 		end
			end

			$db.synchronize() do
				$db.transaction do |d|
					d.execute(&quot;insert into files values('#{sender}', '#{receiver}',
                    	'#{original_filename}', '#{real_filename}', #{Time.now.tv_sec}, #{file.size})&quot;)
				end
			end
		rescue Exception =&gt; e
			p e
			puts e.backtrace
		end

		response.start do |head, out|
			head[&quot;Content-Type&quot;] = &quot;text/html&quot;

			out &lt;&lt; &lt;&lt;EOS
#{$page_head}

&lt;script language=&quot;JavaScript&quot;&gt;
&lt;!--

parent.document.getElementById(&quot;form_area&quot;).innerHTML = (&quot;&lt;p&gt;&lt;b&gt;&quot;
+ parent.filename + &quot;&lt;/b&gt; has been successfully uploaded.&lt;/p&gt;&quot;);
parent.filename = &quot;&quot;;
parent.setProgressBar(#{file.size}, #{file.size});
parent.loadFileList('#{sender}');
parent.document.getElementById(&quot;button_upload&quot;).disabled = false;

//--&gt;
&lt;/script&gt;

#{$page_end}
EOS
		end
	end
end
</pre>
<p><code>scrapi</code>のWindows版で1モジュールのdllの読み込みに不具合があるので、問題になるメソッド<code>find_tidy</code>をここで上書き修正している。この辺は動的言語の面目躍如と言うべきだが、濫用すると収拾が付かなくなるので個人的にはやむをえない場合以外やるべきではないと思っている。</p>
<pre class="brush: ruby;">

# Redefine the find_tidy method in scrapi for Windows to load the correct dll first
module Scraper
	module Reader
		module_function

		def find_tidy()
			return if Tidy.path

			begin
				$LOAD_PATH.each do |path|
					if path =~ /scrapi/ &amp;amp;&amp;amp; path =~ /lib$/
						if Config::CONFIG['arch'] =~ /mswin/
							Tidy.path = File.join(path, &quot;/tidy&quot;, &quot;libtidy.dll&quot;)
						else
							Tidy.path = File.join(path, &quot;/tidy&quot;, &quot;libtidy.so&quot;)
						end
						break
					end
				end
			rescue LoadError =&gt; e
				puts e.to_s
			end
		end
	end
end
</pre>
<p>あるユーザ名が「はてな」の実在のユーザかどうか確かめるためのwebサービスAPIを「はてな」では提供していないので、「はてな」上に該当ユーザのメンバーページが存在するかどうか、<code>scrapi</code>によるスクレイピングを行って強引に確かめることで代用する。</p>
<pre class="brush: ruby;">

class CheckUsernameHandler &lt; Mongrel::HttpHandler

	@@scraper = Scraper.define do
		process &quot;td[align='center'] a[href='/q']&quot;, :ret =&gt; :text
		result :ret
	end

	def process(request, response)
		verified = false
		$verified_users_ipaddress.synchronize(Sync_m::SH) do
			if $verified_users_ipaddress.include?(request.params[Mongrel::Const::REMOTE_ADDR])
				verified = true
			end
		end

		unless verified
			response.start(403) do |head, out|
				out.write(&quot;Not Authorized&quot;)
			end
			return
		end

		found = false
		$existent_users.synchronize(Sync_m::SH) do
			found = $existent_users.include?(request.body.string)
		end

		unless found
			uri = &quot;http://www.hatena.ne.jp/user?userid=&quot;
			uri += request.body.string # StringIO

			found = (@@scraper.scrape(URI.parse(uri)) == nil)

			$existent_users.synchronize() do
				if $existent_users.size &gt; 1000
					$existent_users.clear()
				end
				$existent_users[request.body.string] = 1
			end
		end

		response.start do |head, out|
			head[&quot;Content-Type&quot;] = &quot;text/xml&quot;
			out.write(
            &quot;&lt;?xml version=\&quot;1.0\&quot; encoding=\&quot;UTF-8\&quot; standalone=\&quot;yes\&quot;?&gt;&lt;r&gt;#{found ? request.body.string : &quot;&quot;}&lt;/r&gt;&quot;)
		end
	end
end
</pre>
<p>自分がアップロード中のファイルの進捗状況を示すXMLを返すハンドラを定義する。</p>
<pre class="brush: ruby;">
class QueryProgressHandler &lt; Mongrel::HttpHandler
	def process(request, response)
		$verified_users_ipaddress.synchronize(Sync_m::SH) do
			unless $verified_users_ipaddress.include?(request.params[Mongrel::Const::REMOTE_ADDR])
				response.start(403) do |head, out|
					out.write(&quot;Not Authorized&quot;)
				end
				return
			end
		end

		found = false
		$download_progress.synchronize(Sync_m::SH) do
			if $download_progress.include?(request.params[&quot;QUERY_STRING&quot;])
				found = $download_progress[request.params[&quot;QUERY_STRING&quot;]]
			end
		end

		response.start do |head, out|
			head[&quot;Content-Type&quot;] = &quot;text/xml&quot;
			if found
				out.write(
    &quot;&lt;?xml version=\&quot;1.0\&quot; encoding=\&quot;UTF-8\&quot; standalone=\&quot;yes\&quot;?&gt;&lt;f&gt;#{found.current_size}/#{found.total_size}&lt;/f&gt;&quot;)
			else
				out.write(&quot;&lt;?xml version=\&quot;1.0\&quot; encoding=\&quot;UTF-8\&quot; standalone=\&quot;yes\&quot;?&gt;&lt;f&gt;&lt;/f&gt;&quot;)
			end
		end
	end
end
</pre>
<p>自分宛に他ユーザがアップロードしたファイルの一覧を表示するハンドラを定義する。</p>
<pre class="brush: ruby;">
class  ListFilesHandler &lt; Mongrel::HttpHandler
	def process(request, response)
		$verified_users_ipaddress.synchronize(Sync_m::SH) do
			if $verified_users_ipaddress.include?(request.params[Mongrel::Const::REMOTE_ADDR])
				if $verified_users_ipaddress[request.params[Mongrel::Const::REMOTE_ADDR]] != request.params[&quot;QUERY_STRING&quot;]
					response.start(403) do |head, out|
						out.write(&quot;Not Authorized&quot;)
					end
					return
				end
			else
				response.start(403) do |head, out|
					out.write(&quot;Not Authorized&quot;)
				end
				return
			end
		end

		if request.params[&quot;QUERY_STRING&quot;] =~ /[^a-zA-Z0-9_-\.\\]/
			response.reset
			response.start(403) do |head, out|
				out &lt;&lt; &quot;Invalid Request&quot;
			end
			return
		end

		xml = '&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;yes&quot;?&gt;&lt;files&gt;'

		$db.synchronize(Sync_m::SH) do
			$db.execute(&quot;select * from files where sender = '#{request.params[&quot;QUERY_STRING&quot;]}'&quot;) do |row|
				xml += &quot;&lt;myfile r=\&quot;#{row[1]}\&quot; ofn=\&quot;#{row[2]}\&quot; rfn=\&quot;#{row[3]}\&quot; d=\&quot;#{row[4]}\&quot; size=\&quot;#{row[5]}\&quot;/&gt;&quot;
			end
			$db.execute(&quot;select * from files where receiver = '#{request.params[&quot;QUERY_STRING&quot;]}'&quot;) do |row|
				xml += &quot;&lt;sentfile s=\&quot;#{row[0]}\&quot; ofn=\&quot;#{row[2]}\&quot; rfn=\&quot;#{row[3]}\&quot; d=\&quot;#{row[4]}\&quot; size=\&quot;#{row[5]}\&quot;/&gt;&quot;
			end
		end

		xml += &quot;&lt;/files&gt;&quot;

		response.start do |head, out|
			head[&quot;Content-Type&quot;] = &quot;text/xml&quot;
			out.write(xml)
		end
	end
end
</pre>
<p>Mongrelの起動設定と起動、終了処理。どのパス(URI)がどのハンドラクラスによって定義されているか、ここで指定する。</p>
<pre class="brush: ruby;">

#stats = Mongrel::StatisticsFilter.new(:sample_rate =&gt; 1)

# new(defaults={}, &amp;amp;blk)
# You pass in initial defaults and then a block to continue configuring.
config = Mongrel::Configurator.new :host =&gt; $conf[&quot;bound_address&quot;], :port =&gt; $conf[&quot;bound_port&quot;] do
	listener do
		uri &quot;/&quot;, :handler =&gt; RootHandler.new
		#uri &quot;/&quot;, :handler =&gt; Mongrel::DeflateFilter.new # This messes up IE
		#uri &quot;/&quot;, :handler =&gt; stats

		uri &quot;/uploader&quot;, :handler =&gt; UploaderHandler.new

		uri &quot;/downloader&quot;, :handler =&gt; DownloaderHandler.new($conf[&quot;file_store_path&quot;], false)

		uri &quot;/receiver&quot;, :handler =&gt; ReceiverHandler.new

		uri &quot;/check_username&quot;, :handler =&gt; CheckUsernameHandler.new

		uri &quot;/query_progress&quot;, :handler =&gt; QueryProgressHandler.new

		uri &quot;/list_files&quot;, :handler =&gt; ListFilesHandler.new

		#uri &quot;/status&quot;, :handler =&gt; Mongrel::StatusHandler.new(:stats_filter =&gt; stats)
	end

	trap(&quot;INT&quot;) { stop }
	run
end

puts &quot;Mongrel running on #{$conf[&quot;bound_address&quot;]}:#{$conf[&quot;bound_port&quot;]}&quot;

config.join
</pre>
<p><code>hatenawebapp1.rb</code>は以上である。        スクリプトを動作させると、Mongrelが設定ファイル内のポートで起動するので、Webブラウザでアクセスすると、「はてな」認証を促すリンクが表示される。それをクリックし、認証を通過すると、コールバックURLのhttp://127.0.0.1/uploaderに転送され、そこでファイルのアップロードが可能となる。自分の送信済みファイルと、自分宛に他ユーザが送信したファイルのリストもそこに表示されている。ファイルをアップロードする と、AJAXを利用して画面遷移無しで進捗表示とアップロード完了後のリスト更新が行われる。尚、ごく稀に特定のファイルでアップロードが失敗することがあるようだが、Mongrelの<code>CGIWrapper</code>のバグに起因する問題でありMongrel側の修正を待つしかない。</p>
<p>つぎに、Perl webアプリの方を見ていくことにする。まずは、設定ファイルの<code>hatenawebapp2.conf</code>である。Rubyアプリの方と同様に、YAML形式を用いてサーバのポートなどを設定している。</p>
<pre class="brush: ruby;">
# hatenawebapp2.conf
#
# configuration file for hatenawebapp2.pl

# Bound port
bound_port: 80

# JavaScript directory name (not path)
javascript_directory: jsdir

# Mask for a hidden text in a quiz question
quiz_mask: &quot;&lt;font color=red&gt;******&lt;/font&gt;&quot;
</pre>
<p>スクリプト本体は<code>hatenawebapp2.pl</code>である。<code>perl, v5.8.8 built for MSWin32-x86-multi-thread</code>で動作を確認している。</p>
<p>必要ライブラリは、Perl 5.8の他に、<br />
<code>YAML::Syck<br />
POE::Component::Server::HTTP<br />
HTTP::Status<br />
XML::RSS<br />
XMLRPC::Lite<br />
LWP::Simple<br />
MeCab<br />
URI::Escape<br />
threads::shared<br />
Thread::Semaphore</code><br />
のそれぞれCPANシェルを使って入手できる最新バージョンと、各々が依存するライブラリである。オープンソース形態素解析エンジン<a href="http://mecab.sourceforge.net/" target="_blank">MeCab</a>は、<a href="http://namazu.asablo.jp/blog/2006/07/12/442318" target="_blank">ナマズのブログ</a>で入手可能な0.92のWindows用バイナリと辞書を使用させていただいた。尚、Windows下ではMeCabがShiftJISでビルドされている ため辞書もShiftJIS版を使用し、スクリプト内で必要な変換を行ったが、他プラットフォームでテストする場合はMeCab、辞書ともUTF-8版が必要である。また、JavaScriptのグラフ視覚化ライブラリである<a href="http://www.jsviz.org/" target="_blank">JSViz</a>と、ツールチップライブラリ<a href="http://boxover.swazz.org/" target="_blank">boxover</a>を利用しており、これらはスクリプト下に<code>jsdir</code>という名称のディレクトリを作ってその中へ全て展開する必要がある。</p>
<pre class="brush: perl;">

=pod

hatenawebapp2.pl

Sample Web Application 2 with Hatena Web Service API : Hatena Keyword Quiz &amp;amp; Visualization

by RyuK (klassphere[at.mark]gmail.com)

http://zzz.zggg.com/

http://aiueo.da.ru/

[Requirements (tested on Microsoft Windows XP)]

Mecab is compiled with ShiftJIS with the ShiftJIS dictionary.
For platforms other than Windows, use UTF-8 for Mecab and its dic.

=cut

use 5.8.0;

use strict;
use warnings;

use utf8;
use Encode;

use YAML::Syck;
use POE::Component::Server::HTTP;
use HTTP::Status;
use XML::RSS;
use XMLRPC::Lite;
use LWP::Simple;
use MeCab;
use URI::Escape;

$YAML::Syck::ImplicitTyping = 1;

my %quiz_answer = (); # IP address - answer

my $conf = YAML::Syck::LoadFile(&quot;hatenawebapp2.conf&quot;);
</pre>
<p>ここでは、日本語UTF-8文字列を使うときにUTF-8に対応していない<code>XMLRPC::Lite</code>と<code>SOAP::Lite</code>内で問題がある箇所の関数を動的に上書き修正している。</p>
<pre class="brush: perl;">

# Patches some functions in XMLRPC::Lite and SOAP::Lite to pass a UTF-8 Japanese string
# in its HTTP transport that is a subclass of LWP::UserAgent
$SOAP::Constants::DO_NOT_USE_LWP_LENGTH_HACK = 1;

{
	my $s =&lt;&lt;'SUBDOC';
package XMLRPC::Serializer;

sub new
{
	my $self = shift;

	unless (ref $self)
	{
		my $class = ref($self) || $self;
		$self = $class-&gt;SUPER::new(
			typelookup =&gt;
			{
				base64 =&gt; [10, sub {1}, 'as_string'],
				int    =&gt; [20, sub {$_[0] =~ /^[+-]?\d+$/}, 'as_int'],
				double =&gt; [30, sub {$_[0] =~ /^(-?(?:\d+(?:\.\d*)?|\.\d+)|([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?)$/}, 'as_double'],
				dateTime =&gt; [35, sub {$_[0] =~ /^\d{8}T\d\d:\d\d:\d\d$/}, 'as_dateTime'],
				string =&gt; [40, sub {1}, 'as_string'],
			},
			attr =&gt; {},
			namespaces =&gt; {},
			@_,
		);
	}

	return $self;
}

1;

package SOAP::Utils;

sub bytelength
{
	return length($_[0]);
}

1;

SUBDOC

	no warnings;
	local $^W = 0;
	eval &quot;$s&quot;;
	use warnings;
}
</pre>
<p>Webサーバの設定を行うとともに、Webサーバ上の各パス毎にハンドラ関数を登録している。</p>
<pre class="brush: perl;">

my $aliases = POE::Component::Server::HTTP-&gt;new(
	Port =&gt; $conf-&gt;{bound_port},
	ContentHandler =&gt;
	{
		'/' =&gt; \&amp;amp;handlerRoot,
		'/viz' =&gt; \&amp;amp;handlerViz,
		'/quiz' =&gt; \&amp;amp;handlerQuiz,
		'/answer' =&gt; \&amp;amp;handlerAnswer,
		'/assoc' =&gt; \&amp;amp;handlerAssoc,
		'/search' =&gt; \&amp;amp;handlerSearch
	},
	Headers =&gt; { Server =&gt; 'My Server' },
);
</pre>
<p>これは穴埋めクイズの問題を作る関数で、要は、「はてなキーワード」内の日本語文に対しMeCabで形態素解析を行って、見つかった名詞の部分を隠すことによって穴埋め問題にするという至極単純な仕組みである。</p>
<pre class="brush: perl;">

sub makeQuiz
{
	my $s = shift;
	my $ip = shift;

	my $sjis = ($^O =~ /mswin/i);
	if ($sjis)
	{
		utf8::encode($s);
		Encode::from_to($s, 'utf8', 'shiftjis');
	}

	my $m = new MeCab::Tagger(&quot;&quot;);
	my $n = $m-&gt;parseToNode($s);

	my @fragments = ();

	my $count = 0;
	while ($n = $n-&gt;{next})
	{
		if (defined($n-&gt;{surface}))
		{
			my $w = $n-&gt;{surface};
			my $f = $n-&gt;{feature};

			if ($sjis)
			{
				Encode::from_to($f, 'shiftjis', 'utf8');
				utf8::decode($f);

				Encode::from_to($w, 'shiftjis', 'utf8');
				utf8::decode($w);
			}

			if ($f =~ /^名詞/ &amp;amp;&amp;amp; length($w) &gt;= 2)
			{
				push @fragments, [$w, 1];
				++$count;
			}
			else
			{
				push @fragments, [$w, 0];
			}
		}
	}

	if ($count == 0)
	{
		return &quot;&quot;;
	}

	my $masked_index = int(rand $count);

	my $final = &quot;&quot;;
	my $current_noun_index = 0;
	foreach my $f (@fragments)
	{
		if ($f-&gt;[1] == 0)
		{
			$final .= $f-&gt;[0];
		}
		elsif ($current_noun_index++ == $masked_index)
		{
			$final .= $conf-&gt;{quiz_mask};

			if (keys(%quiz_answer) &gt; 1000)
			{
				%quiz_answer = ();
			}

			$quiz_answer{$ip} = $f-&gt;[0];
		}
		else
		{
			$final .= $f-&gt;[0];
		}
	}

	return $final;
}
</pre>
<p>webサーバのルートURLのハンドラ。ユーザが任意の単語を入力すると「はてなキーワード」を検索し、キーワード間の連想グラフをロードする。</p>
<pre class="brush: perl;">
sub handlerRoot
{
	my ($request, $response) = @_;
	$response-&gt;code(RC_OK);

	my $out = &quot;&quot;;
	my $jsdir = $conf-&gt;{javascript_directory};

	if ($request-&gt;uri =~ /$jsdir\/([^\/]+)$/)
	{
		my $f = $1;
		if (!open(FILE, &quot;./$jsdir/$f&quot;))
		{
			return RC_DENY;
		}

		while (&lt;FILE&gt;)
		{
			$out .= $_;
		}

		$response-&gt;content($out);
		$response-&gt;header('Content-type' =&gt; &quot;application/x-javascript&quot;);

		close(FILE);

		return RC_OK;
	}

	$out =&lt;&lt;'HEREDOC';
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Hatena web app 2&lt;/title&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt;

&lt;script language=&quot;JavaScript&quot;&gt;
&lt;!--

function setup()
{

}

function resizeIframe(f)
{
	if (document.body.clientWidth)
	{
		f.style.width = document.body.clientWidth + &quot;px&quot;;
		f.style.height = (document.body.clientHeight - 100) + &quot;px&quot;;
	}
}

function resizeIframe2(f)
{
	if (document.body.clientWidth)
	{
		f.style.width = document.body.clientWidth + &quot;px&quot;;
	}
}

//--&gt;

&lt;/script&gt;

&lt;style type=&quot;text/css&quot;&gt;

body
{
	margin: 0;
	padding: 0;
	overflow: hidden;
}

p
{
	text-decoration: none;
	font-size: 13px;
	font-weight: normal;
	font-family: Verdana, Geneva, san-serif;
	line-height: 150%;
	margin-top: 0px;
	margin-bottom: 1em;
	padding-left: 8px;
	padding-right: 8px;
}

form
{
	margin: 0;
	padding: 0;
}

&lt;/style&gt;
&lt;/head&gt;
&lt;body onload=&quot;setup();&quot; onresize=&quot;resizeIframe(document.getElementById('ifr'));resizeIframe2(document.getElementById('quiz'));&quot;&gt;
&lt;br&gt;
&lt;p&gt;Proof of Concept: Hatena Keyword Quiz &amp;amp; Visualization&lt;/p&gt;

&lt;form onsubmit=&quot;frames.ifr.hatenaKeywords.getKeywords(this.word.value); return false;&quot;&gt;
&lt;p&gt;はてなキーワード内から検索したい単語を &lt;input type=&quot;text&quot; id=&quot;word&quot; size=&quot;40&quot;&gt; に入力し
&lt;input type=&quot;submit&quot; value=&quot;検索&quot;&gt; して、見つかった単語をクリックして下さい。
&lt;/p&gt;
&lt;/form&gt;
&lt;p&gt;(キーワードのノードはマウスでドラッグ可能です)&lt;/p&gt;
&lt;iframe src=&quot;/quiz&quot; id=&quot;quiz&quot; name=&quot;quiz&quot; width=&quot;1000&quot; height=&quot;160&quot; scrolling=&quot;yes&quot; valign=&quot;top&quot;
 onload=&quot;resizeIframe2(this);&quot;&gt;&lt;/iframe&gt;
&lt;iframe src=&quot;/viz&quot; id=&quot;ifr&quot; name=&quot;ifr&quot; width=&quot;1000&quot; height=&quot;600&quot; scrolling=&quot;yes&quot; valign=&quot;top&quot;
 onload=&quot;resizeIframe(this);&quot;&gt;&lt;/iframe&gt;

&lt;/body&gt;
&lt;/html&gt;

HEREDOC

	$response-&gt;content($out);

	return RC_OK;
}
</pre>
<p>GETリクエストで呼び出されると必要なJavaScriptを表示し、POSTリクエストの場合は入力された単語を「はてなキーワード」で検索した後、キーワードのRSSデータから説明文を抜き出してクイズを作成表示する。</p>
<pre class="brush: perl;">

sub handlerQuiz
{
	my ($request, $response) = @_;
	$response-&gt;code(RC_OK);

	my $out = &quot;&quot;;

	if ($request-&gt;method =~ /get/i)
	{
		$out =&lt;&lt;'HEREDOC';
&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt;

&lt;script language=&quot;JavaScript&quot;&gt;
&lt;!--

var isIE = window.ActiveXObject;
var isMozilla = navigator.userAgent.indexOf('Gecko') != -1;

function createHttpRequest()
{
	if (isIE)
	{
		try
		{ // CLSID_XMLHTTP
			// v 3.0
			return new ActiveXObject(&quot;Msxml2.XMLHTTP&quot;);
		}
		catch (e)
		{
			try
			{// v 2.x
				return new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;);
			}
			catch (e2)
			{
				return null;
			}
		}
	}
	else if (window.XMLHttpRequest) // non-IE
	{
		var hr = new XMLHttpRequest();
		if (isMozilla)
			hr.overrideMimeType('text/xml');

		return hr;
	}
	else
	{
		return null;
	}
}

function sendHTTP(data, method, uri, callback, async, caller)
{
	var hr = createHttpRequest();

	var args = new Array();
	args.push(hr);
	for (var i = 6; i &lt; arguments.length; ++i)
	{
		args.push(arguments[i]);
	}

	try
	{
		hr.open(method, uri, async);
		hr.setRequestHeader(&quot;If-Modified-Since&quot;, &quot;Thu, 01 Jun 1970 00:00:00 GMT&quot;);

		hr.onreadystatechange = function()
		{
			if (hr.readyState == 4)
			{
				callback.apply(caller, args);
			}
		}

		hr.send(data);
		delete hr;
	}
	catch(e)
	{
		alert(&quot;sendHTTP: &quot; + e);
	}
}

function getTextContent(xml)
{
	if (xml)
	{
		if (xml.textContent)
			return xml.textContent; // Mozilla
		// IE
		if (xml.innerText)
			return xml.innerText;
		if (xml.text)
			return xml.text;
	}
}

function setup()
{

}

function checkAnswer(answer)
{
	sendHTTP(answer, &quot;POST&quot;, &quot;/answer&quot;, checkAnswerCallback, true, this, answer);
}

function checkAnswerCallback(request)
{
	var nodelist = request.responseXML.getElementsByTagName(&quot;a&quot;);
	if (nodelist &amp;amp;&amp;amp; nodelist.length != 0)
	{
		for (var i = 0; i &lt; nodelist.length &amp;amp;&amp;amp; i &lt; 10; ++i)
		{
			var e = nodelist.item(i);
			if (e.getAttribute(&quot;r&quot;) == &quot;true&quot;)
			{
				alert(&quot;正解&quot;);
			}
			else
			{
				var answer = &quot;&quot;;
				var n = e.firstChild;
				while (n != null)
				{
					// n.textContent == Mozilla only
					// NODE_TEXT == 3 || NODE_CDATA_SECTION == 4
					if (n != null &amp;amp;&amp;amp; (n.nodeType == 3 || n.nodeType == 4))
					{
						answer = n.nodeValue;
					}

					n = n.nextSibling;
				}

				alert(&quot;不正解 - 解答: &quot; + answer);
			}
		}
	}
}

//--&gt;

&lt;/script&gt;

&lt;style type=&quot;text/css&quot;&gt;

body
{
	margin: 0;
	padding: 0;
	overflow: hidden;
}

p
{
	text-decoration: none;
	font-size: 13px;
	font-weight: normal;
	font-family: Verdana, Geneva, san-serif;
	line-height: 150%;
	margin-top: 0px;
	margin-bottom: 1em;
	padding-left: 8px;
	padding-right: 8px;
}

form
{
	margin: 0;
	padding: 0;
}

&lt;/style&gt;
&lt;/head&gt;
&lt;body onload=&quot;setup();&quot;&gt;
&lt;span id=&quot;quiz&quot;&gt;&lt;/span&gt;
&lt;/body&gt;
&lt;/html&gt;

HEREDOC

	}
	else
	{
		$response-&gt;header('Content-Type' =&gt; 'text/xml');
		$out = &quot;&lt;?xml version=\&quot;1.0\&quot; encoding=\&quot;UTF-8\&quot; standalone=\&quot;yes\&quot;?&gt;\n&lt;r&gt;\n&quot;;

		my $w = $request-&gt;content;
		utf8::decode($w);
		$w =~ tr/Ａ-Ｚａ-ｚ０-９/A-Za-z0-9/;
		$w = URI::Escape::uri_escape_utf8($w);

		my $content = get(&quot;http://d.hatena.ne.jp/keyword?word=$w&amp;amp;mode=rss&amp;amp;ie=utf8&quot;);
		if ($content)
		{
			my $rss = new XML::RSS;

			utf8::decode($content);

			$rss-&gt;parse($content);

			my $item = ${$rss-&gt;{items}}[0];

			for my $item (@{$rss-&gt;{items}})
			{
				$out .= &quot;&lt;i a='&quot;;
				$out .= $item-&gt;{link};
				$out .= &quot;'&gt;&quot;;

				if (defined($item-&gt;{title}))
				{
					$out .= &quot;&lt;t&gt;&lt;![CDATA[&quot;;

					my $t = $item-&gt;{title};
					utf8::decode($t);
					$t =~ s/]]&gt;/]]&gt;/g;
					$out .= $t;

					$out .= &quot;]]&gt;&lt;/t&gt;\n&quot;;
				}

				if (defined($item-&gt;{description}))
				{
					$out .= &quot;&lt;d&gt;&lt;![CDATA[&quot;;

					my $d = $item-&gt;{description};
					utf8::decode($d);
					$d =~ s/]]&gt;/]]&gt;/g;
					$d =~ s|&lt;a[^&gt;]+&gt;||g;

					$out .= makeQuiz($d, $request-&gt;{connection}-&gt;{remote_ip});

					$out .= &quot;]]&gt;&lt;/d&gt;&quot;;
				}

				$out .= &quot;&lt;/i&gt;\n&quot;;
			}
		}

		$out .= &quot;&lt;/r&gt;&quot;;
	}

	$response-&gt;content($out);

	return RC_OK;
}
</pre>
<p><a href="http://www.kylescholz.com/blog/projects/jsviz/" target="_blank">JSViz</a>によって「はてなキーワード」の連想グラフを視覚化した物を表示する画面のハンドラ。</p>
<pre class="brush: perl;">

sub handlerViz
{
	my ($request, $response) = @_;
	$response-&gt;code(RC_OK);

	my $out = &quot;&quot;;
	my $jsdir = $conf-&gt;{javascript_directory};

	if ($request-&gt;uri =~ /$jsdir\/([^\/]+)$/)
	{
		my $f = $1;
		if (!open(FILE, &quot;./$jsdir/$f&quot;))
		{
			return RC_DENY;
		}

		while (&lt;FILE&gt;)
		{
			$out .= $_;
		}

		$response-&gt;content($out);
		$response-&gt;header('Content-type' =&gt; &quot;application/x-javascript&quot;);

		close(FILE);

		return RC_OK;
	}

	$out =&lt;&lt;'HEREDOC';
&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt;

&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/DataGraph.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/Magnet.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/Spring.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/Particle.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/ParticleModel.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/Timer.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/EventHandler.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/HTMLGraphView.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/SVGGraphView.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/RungeKuttaIntegrator.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/Control.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;/jsdir/boxover.js&quot;&gt;&lt;/script&gt;

&lt;script language=&quot;JavaScript&quot;&gt;
&lt;!--

var isIE = window.ActiveXObject;
var isMozilla = navigator.userAgent.indexOf('Gecko') != -1;

// Suppress IE6 SP1 flicker
if (isIE)
{
	try
	{
		document.execCommand(&quot;BackgroundImageCache&quot;, false, true);
	}
	catch (e)
	{

	}
}

function createHttpRequest()
{
	if (isIE)
	{
		try
		{ // CLSID_XMLHTTP
			// v 3.0
			return new ActiveXObject(&quot;Msxml2.XMLHTTP&quot;);
		}
		catch (e)
		{
			try
			{// v 2.x
				return new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;);
			}
			catch (e2)
			{
				return null;
			}
		}
	}
	else if (window.XMLHttpRequest) // non-IE
	{
		var hr = new XMLHttpRequest();
		if (isMozilla)
			hr.overrideMimeType('text/xml');

		return hr;
	}
	else
	{
		return null;
	}
}

function sendHTTP(data, method, uri, callback, async, caller)
{
	var hr = createHttpRequest();

	var args = new Array();
	args.push(hr);
	for (var i = 6; i &lt; arguments.length; ++i)
	{
		args.push(arguments[i]);
	}

	try
	{
		hr.open(method, uri, async);
		hr.setRequestHeader(&quot;If-Modified-Since&quot;, &quot;Thu, 01 Jun 1970 00:00:00 GMT&quot;);

		hr.onreadystatechange = function()
		{
			if (hr.readyState == 4)
			{
				callback.apply(caller, args);
			}
		}

		hr.send(data);
		delete hr;
	}
	catch(e)
	{
		alert(&quot;sendHTTP: &quot; + e);
	}
}

function getTextContent(xml)
{
	if (xml)
	{
		if (xml.textContent)
			return xml.textContent; // Mozilla
		// IE
		if (xml.innerText)
			return xml.innerText;
		if (xml.text)
			return xml.text;
	}
}

var HatenaKeywords = function(dataGraph, particleModel)
{
	this.init(dataGraph, particleModel);
}

HatenaKeywords.prototype =
{
	init: function(dataGraph, particleModel)
	{
		this.dataGraph = dataGraph;
		this.particleModel = particleModel;

		this.TRAVERSE_DEPTH = 1;
		this.MAX_PRODUCTS_ORIGIN = 8;
		this.MAX_PRODUCTS_PER_SIMILARITY = 8;
		this.MAX_NODES = 20;

		this.nodesByName = {};
		this.nodesCount = 0;
	},

	search: function(keyword)
	{
		document.getElementById('searchResults').innerHTML = &quot;&quot;;
		document.getElementById('keywordResults').style.display = &quot;none&quot;;

		parent.document.getElementById('word').value = keyword;

		this.particleModel.clear();
		this.nodesByName = {};
		this.nodesCount = 0;

		if (this.particleModel.timer.interupt)
			this.particleModel.timer.start();

		var node = new DataGraphNode(true, 2);
		node.keyword = keyword;

		this.dataGraph.addNode(node);
		this.nodesByName[keyword] = node;
		this.getSimilarKeywords(keyword, 0);

		sendHTTP(keyword, &quot;POST&quot;, &quot;/quiz&quot;, this.getQuizCallback, true, this, keyword);
	},

	getQuizCallback : function(request)
	{
		var nodelist = request.responseXML.getElementsByTagName(&quot;i&quot;);
		if (nodelist &amp;amp;&amp;amp; nodelist.length != 0)
		{
			for (var i = 0; i &lt; nodelist.length &amp;amp;&amp;amp; i &lt; 10; ++i)
			{
				var e = nodelist.item(i);
				var keyword = getTextContent(e.getElementsByTagName(&quot;t&quot;)[0]);
				if (!keyword)
					keyword = &quot;&quot;;

				var desc = getTextContent(e.getElementsByTagName(&quot;d&quot;)[0]);
				if (!desc)
					desc = &quot;&quot;;

				parent.frames.quiz.document.getElementById('quiz').innerHTML
                	= &quot;&lt;p&gt;クイズ: 隠された単語は何でしょう?&lt;/p&gt;&lt;p&gt;&quot;
                    + desc
                    + &quot;&lt;/p&gt;&lt;form onsubmit=\&quot;checkAnswer(this.answer.value);return false;\&quot;&gt;&lt;p&gt;&quot;
                    + &quot;あなたの答え: &lt;input id=\&quot;answer\&quot; type=\&quot;text\&quot; size=\&quot;20\&quot;&gt;&lt;input type=\&quot;submit\&quot;&quot;
                    + &quot; value=\&quot;解答をチェック\&quot;&gt;&lt;/p&gt;&lt;/form&gt;&quot;;
			}
		}
	},

	getKeywords : function(keyword)
	{
		document.getElementById('searchResults').innerHTML = &quot;&lt;p&gt;&lt;blink&gt;Searching...&lt;/blink&gt;&lt;/p&gt;&quot;;

		this.particleModel.clear();
		this.nodesByName = {};
		this.nodesCount = 0;

		if (this.particleModel.timer.interupt)
			this.particleModel.timer.start();

		sendHTTP(keyword, &quot;POST&quot;, &quot;/search&quot;, this.getKeywordsCallback, true, this, keyword);
	},

	getKeywordsCallback : function(request)
	{
		document.getElementById('searchResults').innerHTML = &quot;&quot;;

		var nodelist = request.responseXML.getElementsByTagName(&quot;i&quot;);
		if (nodelist &amp;amp;&amp;amp; nodelist.length != 0)
		{
			var h = document.createElement('p');
			h.innerHTML = (nodelist.length.toString() + &quot;個のキーワードが見つかりました。クリックすると関係する単語群を探せます。&quot;);
			document.getElementById('searchResults').appendChild(h);

			for (var i = 0; i &lt; nodelist.length &amp;amp;&amp;amp; i &lt; 10; ++i)
			{
				var e = nodelist.item(i);
				var keyword = getTextContent(e.getElementsByTagName(&quot;t&quot;)[0]);
				if (!keyword)
					keyword = &quot;&quot;;

				var desc = getTextContent(e.getElementsByTagName(&quot;d&quot;)[0]);
				if (!desc)
					desc = &quot;&quot;;

				var r = document.createElement('div');
				r.className = &quot;keyword&quot;;
				var title = (&quot;fade=[on] header=[&quot; + keyword.replace(/[/g, &quot;&quot;).replace(/]/g, &quot;&quot;)
                + &quot;] body=[&quot; + desc.replace(/[/g, &quot;&quot;).replace(/]/g, &quot;&quot;) + &quot;]&quot;);
				r.setAttribute(&quot;title&quot;, title);
				r.setAttribute(&quot;style&quot;, &quot;padding-left: 50px;&quot;);
				r.innerHTML = '&lt;p onclick=&quot;' +
					&quot;hatenaKeywords.search('&quot; + keyword.replace(/&quot;/g, '&quot;') + &quot;')&quot; + '&quot;&gt;'
                    + &quot;&lt;b&gt;&lt;a onmouseover=\&quot;this.style.textDecoration = 'underline'\&quot; onmouseout=\&quot;this.style.textDecoration = 'none'\&quot;&gt;&quot;
                    + keyword + '&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;';

				document.getElementById('searchResults').appendChild(r);

			}
		}
	},

	getSimilarKeywords: function(word, ordinal)
	{
		sendHTTP(word, &quot;POST&quot;, &quot;/assoc&quot;, this.getSimilarKeywordsCallback, true, this, word, ordinal);
	},

	getSimilarKeywordsCallback: function(request, parentWord, ordinal)
	{
		var max = this.MAX_PRODUCTS_PER_SIMILARITY;
		if (ordinal == 0)
			max = this.MAX_PRODUCTS_ORIGIN;

		var nodelist = request.responseXML.getElementsByTagName(&quot;related&quot;);
		for (var i = 0; i &lt; nodelist.length &amp;amp;&amp;amp; i &lt; max &amp;amp;&amp;amp; this.nodesCount &lt; this.MAX_NODES; i++)
		{
			var word = nodelist[i].getAttribute(&quot;w&quot;);

			if (this.nodesByName[word])
			{
				var node = this.nodesByName[word];
				this.dataGraph.addEdge(node, this.nodesByName[parentWord]);
			}
			else
			{
				var node = new DataGraphNode(false, 1);
				node.keyword = word;

				node.addEdge(this.nodesByName[parentWord], 1);
				this.dataGraph.addNode(node);
				this.nodesCount++;

				this.nodesByName[word] = node;

				if (ordinal &lt; this.TRAVERSE_DEPTH)
					this.getSimilarKeywords(word, ordinal + 1);
			}
		}
	}
}

var hatenaKeywords;

function setup()
{
	var FRAME_WIDTH;
	var FRAME_HEIGHT;

	if (document.all)
	{
		FRAME_WIDTH = document.body.offsetWidth - 10;
		FRAME_HEIGHT = document.documentElement.offsetHeight - 10 - 28;
	}
	else
	{
		FRAME_WIDTH = window.innerWidth - 10;
		FRAME_HEIGHT = window.innerHeight - 10 - 28;
	}

	var view = document.implementation.hasFeature(&quot;org.w3c.dom.svg&quot;, '1.1') ?
		new SVGGraphView(0, 26, FRAME_WIDTH, FRAME_HEIGHT, true)
		: new HTMLGraphView(0, 26, FRAME_WIDTH, FRAME_HEIGHT, true);

	var particleModel = new ParticleModel(view);
	particleModel.start();

	var control = new Control(particleModel, view);
	var dataGraph = new DataGraph();

	hatenaKeywords = new HatenaKeywords(dataGraph, particleModel);

	var nodeHandler = new NodeHandler(dataGraph, particleModel, view, control);
	dataGraph.subscribe(nodeHandler);

	var buildTimer = new Timer(150);
	buildTimer.subscribe(nodeHandler);
	buildTimer.start();
}

var NodeHandler = function( dataGraph, particleModel, view, control )
{
	this.dataGraph = dataGraph;
	this.particleModel = particleModel;
	this.view = view;

	this.nodeQueue = new Array();
	this.relationshipQueue = new Array();

	this['newDataGraphNode'] = function(dataGraphNode)
	{
		this.enqueueNode(dataGraphNode);
	}

	this['newDataGraphEdge'] = function(nodeA, nodeB)
	{
		this.enqueueRelationship(nodeA, nodeB);
	}

	this['enqueueNode'] = function(dataGraphNode)
	{
		this.nodeQueue.push(dataGraphNode);
	}

	this['enqueueRelationship'] = function(nodeA, nodeB)
	{
		this.relationshipQueue.push({'nodeA': nodeA, 'nodeB': nodeB});
	}

	this['dequeueNode'] = function()
	{
		var node = this.nodeQueue.shift();

		if (node)
		{
			this.addParticle(node);
			return true;
		}

		return false;
	}

	this['dequeueRelationship'] = function()
	{
		var edge = this.relationshipQueue.shift();
		if (edge)
			this.addSimilarity(.05, edge.nodeA, edge.nodeB);
	}

	this.update = function()
	{
		var nodes = this.dequeueNode();
		if (!nodes)
			this.dequeueRelationship();
	}

	this['addParticle'] = function(dataGraphNode)
	{
		particle = this.particleModel.makeParticle(dataGraphNode.mass, 0, 0);
		dataGraphNode.particle = particle;

		if (dataGraphNode.fixed)
			particle.fixed = true;

		var rx = Math.random() * 2 - 1;
		var ry = Math.random() * 2 - 1;
		particle.positionX = rx - 50 / this.view.skew;
		particle.positionY = ry;

		for (var j = 0, l = this.particleModel.particles.length; j &lt; l; j++)
		{
			if (this.particleModel.particles[j] != particle)
				this.particleModel.makeMagnet(particle, this.particleModel.particles[j], -30000, 64);
		}

		var particleParent = false;

		for (var c in dataGraphNode.edges)
		{
			if (!particleParent)
			{
				particleParent = true;
				particle.positionX = dataGraphNode.edges[c].particle.positionX + rx;
				particle.positionY = dataGraphNode.edges[c].particle.positionY + ry;
			}

			this.addSimilarity(.2, dataGraphNode, dataGraphNode.edges[c]);
		}

		var keyword = dataGraphNode.keyword;
		if (!keyword) {keyword = &quot;&quot;;}

		var n = document.createElement('div');
		n.style.position = &quot;absolute&quot;;
		n.className = &quot;keyword&quot;;
		n.innerHTML = '&lt;div onclick=&quot;'
        + &quot;hatenaKeywords.search('&quot; + keyword.replace(/&quot;/g,'&quot;') + &quot;')&quot;
        + &quot;\&quot;&gt;&lt;a onmouseover=\&quot;this.style.textDecoration = 'underline'\&quot; onmouseout=\&quot;this.style.textDecoration = 'none'\&quot;&gt;&quot;
        + keyword + '&lt;/a&gt;&lt;/div&gt;';
		n.onmousedown = new EventHandler(control, control.handleMouseDownEvent, particle.id)
		dataGraphNode.viewNode = this.view.addNode(particle, n, 25, isIE ? 10 : 30);

		//particle.width = dataGraphNode.viewNode.offsetWidth;
		//particle.height = dataGraphNode.viewNode.offsetHeight;

		return dataGraphNode;
	},

	this['addSimilarity'] = function(springConstant, nodeA, nodeB)
	{
		particleModel.makeSpring(nodeA.particle, nodeB.particle, springConstant, .2, 80);

		var props = document.implementation.hasFeature(&quot;org.w3c.dom.svg&quot;, '1.1') ?
		{
				'stroke': &quot;#bbbbbb&quot;,
				'stroke-width': '2px',
				'stroke-dasharray': '2, 8'
		}
		:
		{
				'pixelColor': &quot;#aaaaaa&quot;,
				'pixelWidth': '2px',
				'pixelHeight': '2px',
				'pixels': 15
		};

		this.view.addEdge(nodeA.particle, nodeB.particle, props);
	}
}

//--&gt;

&lt;/script&gt;

&lt;style type=&quot;text/css&quot;&gt;

body
{
	margin: 0;
	padding: 0;
	overflow: hidden;
}

p
{
	text-decoration: none;
	font-size: 13px;
	font-weight: normal;
	font-family: Verdana, Geneva, san-serif;
	line-height: 150%;
	margin-top: 0px;
	margin-bottom: 1em;
	padding-left: 8px;
	padding-right: 8px;
}

form
{
	margin: 0;
	padding: 0;
}

div.keyword
{
	font-family: Verdana, Geneva, san-serif;
	font-weight: bold;
	font-size: 14px;
	text-align: left;
	background-repeat: no-repeat;
	cursor: hand;
}

#keywordResults
{
	display: none;
	color: #000000;
	border-top: 0px;
}

&lt;/style&gt;
&lt;/head&gt;

&lt;body onload=&quot;setup();&quot;&gt;
&lt;div id=&quot;searchResults&quot;&gt;&lt;/div&gt;
&lt;div id=&quot;keywordResults&quot;&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

HEREDOC

	$response-&gt;content($out);

	return RC_OK;
}
</pre>
<p>「はてなキーワード」で検索を行うためのAJAXコールバックを定義する。<a href="http://www.kylescholz.com/blog/projects/jsviz/" target="_blank">JSViz</a>はこれを呼び出して見つかった関連単語を次々と検索し、単語のグラフに新しいノードを付け加えていく。</p>
<pre class="brush: perl;">

sub handlerSearch
{
	my ($request, $response) = @_;
	$response-&gt;code(RC_OK);

	if ($request-&gt;method =~ /get/i)
	{
		return RC_DENY;
	}

	$response-&gt;header('Content-Type' =&gt; 'text/xml');
	my $out = &quot;&lt;?xml version=\&quot;1.0\&quot; encoding=\&quot;UTF-8\&quot; standalone=\&quot;yes\&quot;?&gt;\n&lt;r&gt;\n&quot;;

	my $w = $request-&gt;content;
	utf8::decode($w);
	$w =~ tr/Ａ-Ｚａ-ｚ０-９/A-Za-z0-9/;
	$w = URI::Escape::uri_escape_utf8($w);

	my $content = get(&quot;http://search.hatena.ne.jp/keyword?word=$w&amp;amp;mode=rss&amp;amp;ie=utf8&amp;amp;page=1&quot;);
	if ($content)
	{
		my $rss = new XML::RSS;

		utf8::decode($content);

		$rss-&gt;parse($content);

		for my $item (@{$rss-&gt;{items}})
		{
			$out .= &quot;&lt;i a='&quot;;
			$out .= $item-&gt;{link};
			$out .= &quot;'&gt;&quot;;

			if (defined($item-&gt;{title}))
			{
				$out .= &quot;&lt;t&gt;&lt;![CDATA[&quot;;

				my $t = $item-&gt;{title};
				utf8::decode($t);
				$t =~ s/]]&gt;/]]&gt;/g;
				$out .= $t;

				$out .= &quot;]]&gt;&lt;/t&gt;\n&quot;;
			}

			if (defined($item-&gt;{description}))
			{
				$out .= &quot;&lt;d&gt;&lt;![CDATA[&quot;;

				my $d = $item-&gt;{description};
				utf8::decode($d);
				$d =~ s/]]&gt;/]]&gt;/g;
				$out .= $d;

				$out .= &quot;]]&gt;&lt;/d&gt;&quot;;
			}

			$out .= &quot;&lt;/i&gt;\n&quot;;
		}
	}

	$out .= &quot;&lt;/r&gt;&quot;;

	$response-&gt;content($out);

	return RC_OK;
}
</pre>
<p>関連単語を「はてな」の関連キーワードAPIを用いて探すためのハンドラ。「はてな」のコードサンプルで推奨されているようにXMLRPC::Liteモ ジュールを使って検索するが、私が試した限りでは「ディスク」など一部単語で問題が起こるようで、いささか実用性に欠ける。</p>
<pre class="brush: perl;">

sub handlerAssoc
{
	my ($request, $response) = @_;
	$response-&gt;code(RC_OK);

	if ($request-&gt;method =~ /get/i)
	{
		return RC_DENY;
	}

	$response-&gt;header('Content-Type' =&gt; 'text/xml');
	my $out = &quot;&lt;?xml version=\&quot;1.0\&quot; encoding=\&quot;UTF-8\&quot; standalone=\&quot;yes\&quot;?&gt;\n&lt;r&gt;\n&quot;;

	my $w = $request-&gt;content;
	utf8::decode($w);
	$w =~ tr/Ａ-Ｚａ-ｚ０-９/A-Za-z0-9/;
	utf8::encode($w);

	# XML::Parser employed by XMLRPC::Lite has troubles for some words such as &quot;ディスク&quot;.
	# Probably should avoid XMLRPC::Lite and use another module instead in future.
	eval
	{
		my $res = XMLRPC::Lite-&gt;new-&gt;proxy('http://d.hatena.ne.jp/xmlrpc')-&gt;call(
		        'hatena.getSimilarWord', {wordlist =&gt; [$w]}
		);

		unless ($res-&gt;fault)
		{
			foreach (@{$res-&gt;result-&gt;{wordlist}})
			{
				if (defined($_-&gt;{word}))
				{
					$out .= &quot;&lt;related w='&quot;;
					$out .= $_-&gt;{word};
					$out .= &quot;'/&gt;\n&quot;;
				}
			}
		}
	};
	warn $@ if $@;

	$out .= &quot;&lt;/r&gt;&quot;;

	$response-&gt;content($out);

	return RC_OK;
}
</pre>
<p>クイズの答えが正しいかどうか判定し、正解/不正解を返すAJAXコールバックURLのハンドラ。</p>
<pre class="brush: perl;">

sub handlerAnswer
{
	my ($request, $response) = @_;
	$response-&gt;code(RC_OK);

	if ($request-&gt;method =~ /get/i)
	{
		return RC_DENY;
	}

	$response-&gt;header('Content-Type' =&gt; 'text/xml');
	my $out = &quot;&lt;?xml version=\&quot;1.0\&quot; encoding=\&quot;UTF-8\&quot; standalone=\&quot;yes\&quot;?&gt;\n&lt;r&gt;\n&quot;;

	if (exists($quiz_answer{$request-&gt;{connection}-&gt;{remote_ip}}))
	{
		my $user_input = $request-&gt;content;
		utf8::decode($user_input);

		if ($quiz_answer{$request-&gt;{connection}-&gt;{remote_ip}} eq $user_input)
		{
			$out .= &quot;&lt;a r=\&quot;true\&quot;/&gt;&quot;;
		}
		else
		{
			my $answer = $quiz_answer{$request-&gt;{connection}-&gt;{remote_ip}};
			$answer =~ s/]]//g;
			$out .= &quot;&lt;a r=\&quot;false\&quot;&gt;&lt;![CDATA[&quot; . $answer . &quot;]]&gt;&lt;/a&gt;&quot;;
		}
	}

	$out .= &quot;&lt;/r&gt;&quot;;

	$response-&gt;content($out);

	return RC_OK;
}

POE::Kernel-&gt;run;
exit;
</pre>
<p>最後に<code>POE::Kernel</code>を呼び出し、サーバを起動する。</p>
<p>2つを書いてみての感想は、モジュール周りの扱いがRubyの方が簡潔で、完成された印象を持った。Perlの方はかなり入り組んでいてモジュールのインストールだけで小一時間かかってしまう(ほとんどがPOEに起因しているが、XML関連モジュールの層も相当ファットである印象を受ける)。Perlの方だけUTF-8を扱う必要がある点も、各モジュールの問題が噴出し、Perlにとって不利な結果となった。もちろんRubyも Mongrelの完成度が低いといった問題はあるものの、Webアプリケーションそのものの問題ではないし、それを言えばPOEは完全にMongrelに劣るので、Webアプリケーションテスト環境を含めた評価としては、やはりRubyの方が洗練されている。今回は順当にRubyに軍配が上がる結果となった。これでRubyそのもののパフォーマンスが向上すれば鬼に金棒といえるだろう。</p>
]]></content:encoded>
			<wfw:commentRss>http://zzz.zggg.com/2007/01/11/%e3%81%af%e3%81%a6%e3%81%aaweb%e3%82%b5%e3%83%bc%e3%83%93%e3%82%b9api%e3%82%92%e7%94%a8%e3%81%84%e3%81%9fperlruby-web%e3%82%a2%e3%83%97%e3%83%aa%e3%82%b1%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b32%e9%a1%8c/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Minuano</title>
		<link>http://zzz.zggg.com/2006/12/12/minuano/</link>
		<comments>http://zzz.zggg.com/2006/12/12/minuano/#comments</comments>
		<pubDate>Mon, 11 Dec 2006 22:57:25 +0000</pubDate>
		<dc:creator>RyuK</dc:creator>
				<category><![CDATA[sup]]></category>

		<guid isPermaLink="false">http://zzz.zggg.com/?p=133</guid>
		<description><![CDATA[2006年は更新1回などと前回書きながらまたこのページを更新する。今回はつまらない作業のメモ。
MongrelというRubyのモジュールがあって、これはWebサーバの機能を持っている。似たようなモジュールとしてWEBri [...]]]></description>
			<content:encoded><![CDATA[<p>2006年は更新1回などと前回書きながらまたこのページを更新する。今回はつまらない作業のメモ。</p>
<p><a href="http://mongrel.rubyforge.org/" target="_blank">Mongrel</a>というRubyのモジュールがあって、これはWebサーバの機能を持っている。似たようなモジュールとして<a href="http://www.webrick.org/" target="_blank">WEBrick</a>というのがあるけれども<a href="http://mongrel.rubyforge.org/" target="_blank">Mongrel</a>の方が新しくまたパフォーマンスも高いらしいというので昨日拾って中身を見てみるとmongrel(雑種)という名前に何か関係があるのかHTTPパーザの部分だけC拡張として実装してある。ソケット周りの部分をもCにしない理由は不明だが、大量のデータを処理するホットスポットをCにするという方針自体は 正しい。<br />
<span id="more-133"></span> だが、こうしたCを使っているモジュールの常か、クロスプラットフォーム対応はおざなりなようでWin32版のバイナリの提供が若干遅 れている。とあるテストをWin32版で行っていて不具合らしき現象に突き当たったので自分で新しい物をビルドしようと思いRubyForgeのSVNから未リリースのバージョンを取ってきた。どうやらメジャーバージョンアップを控えているようで色々中身が増えている。C拡張ソースコードがある<code>ext\http11</code>フォルダの<code>extconf.rb</code>が<code>mkmf</code>ライブラリによるMakefile生成スクリプトなのでこれをRubyで実行する。(ちなみにRuby本体は<a href="http://www.garbagecollect.jp/ruby/mswin32/ja/">Ruby-mswin32</a> のビルドを使用している。)         ところがエラーが出てMakefileを作ってくれない。<code>mkmf.log</code>を見ると<code>c.lib</code>と いうのがおかしいようだ。とどのつまりUnixのCライブラリが前提になっていてWindowsでのビルドは全く無視されていると思われる。ドキュメントはビルドについて何も触れていないので、オフィシャルのWin32版がどうやってビルドされたのかすらわからない。仕方ないので試行錯誤して<code>extconf.rb</code>内の<code>have_library</code>の第一引数を<code>libcpmt</code>(VC++のマルチスレッドリリース版Cライブラリ)にしてやるとコンフリクトも無くどうにかMakefile         を作ってくれる。nmakeによるビルドの際はRuby本体の<code>lib\ruby\1.8\i386-mswin32</code>フォルダにある<code>config.h</code> 先頭のコンパイラバージョンチェックにひっかかるので、これを編集してコメントアウトする必要がある。Makefile自体もまだ不完全で、ライブラリがエクスポートする関数を示すdefファイルをRubyで生成する手順になっているのが、Rubyインタプリタへのパスに空白と         スラッシュが含まれているためにうまく動作せず、そのまま実行するとライブラリのバイナリ(<code>http11.so</code>)はできるものの何も関数が入っていないという状態になってしまう。そこで二重引用符でRubyインタプリタのパスを囲みスラッシュを\に変えなければならない。これでようやくまともな<code>http11.so</code>が出来上がるのでこれを<a href="http://mongrel.rubyforge.org/" target="_blank">Mongrel</a>のlibフォルダへ移動する。</p>
<p>最新版でチェックしても問題は解決しなかった。その問題とは、Firefoxではページが表示されるのにIE7だととにかく「ページを 表示できない」というDNSエラーの表示が出てどうにもならないというものである。タイムアウトはしていないのでソケット接続はしているようである。ま た、Windowsファイアウォールも適切に設定してあり、それ以前にファイアウォールの内側でテストしているのだから関係があるはずもない。冒頭に述べ たように幸いソケット部分はRubyなので簡単にデバッグできそうかな等と思い始めた頃にはたと思い当たり<code>Mongrel::Configurator</code>で指定しているリクエストチェインから<code>Mongrel::DeflateFilter</code>を抜いてみる。これでやっとIE7でページを開くことが出来た。どうやらFirefoxと異なり圧縮モードへのIE7の対応に問題があったらしい。このライブラリが金を払う必要のある製品でないことだけが不幸中の幸いである。だが失った時間は帰ってこない。</p>
]]></content:encoded>
			<wfw:commentRss>http://zzz.zggg.com/2006/12/12/minuano/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Shonuff</title>
		<link>http://zzz.zggg.com/2006/11/30/shonuff/</link>
		<comments>http://zzz.zggg.com/2006/11/30/shonuff/#comments</comments>
		<pubDate>Wed, 29 Nov 2006 22:56:13 +0000</pubDate>
		<dc:creator>RyuK</dc:creator>
				<category><![CDATA[DICE]]></category>
		<category><![CDATA[sup]]></category>

		<guid isPermaLink="false">http://zzz.zggg.com/?p=130</guid>
		<description><![CDATA[去年は1回しかこのサイトを更新できなかったと述べているうちにすでに2006年も末で今年もこのDICE v0.86リリースに合わせての       年1回の更新となる。書きためていた文章もいくつかアップロードした。サイトの [...]]]></description>
			<content:encoded><![CDATA[<p>去年は1回しかこのサイトを更新できなかったと述べているうちにすでに2006年も末で今年もこのDICE v0.86リリースに合わせての       年1回の更新となる。書きためていた文章もいくつかアップロードした。サイトの全体的な改装なども行いたいけれどなかなか難しい。リンク切れの修正程度が精一杯だ。Webサイトを公開し始めて7年、このサーバに移ってきてから4年経つ。DICEも4年が経ち、かなり大規模なアプリケーションになった。4種類のサーバが1つになっている上にGUIクライアントやWebアプリケーションのスクリプトなどが入って、一人でメインテナンスするには正直言ってかなり厳しいサイズになっている。ドキュメント類の更新だけでも一仕事だ。アプリケーションのライセンスであるとか、ソフトウェアにまつわる形式的な物事に対して自分で決定し細部まで凝るのが以前は楽しみだった。プログラ       ミングそのものについても一貫していて、DICEのデザインについてみると、自分がかつて書いた<a href="http://zzz.zggg.com/category/code/dice/">2本の記録</a>を読み返してみても構築的な欲求や形式へのこだわりを強く感じる。ネットワークセキュリティにも強い興味があり、DICEの設計も偏執狂的にセキュリティを追及していた。IRCサーバというのは、他のサーバに比べ、一つの独立した世界を創造するという       側面が非常に強い。OSからハードウェアの抽象化という機能を抜いたような物だと考えれば案外近いかも知れない。セキュリティは、作り出した世界を確固としたものとするのに絶対に必須である。また、DICEを作り始めた2001年頃には、デザインパターンやeXtreme         Programming、RUP、アスペクト指向などのポストオブジェクト指向とでもいうべきソフトウェア工学のトレンドが旺盛に議論されていて、その頃は私もその関係の本やドキュメントを一生懸命追いかけていた。その時の自分のスタンスはどちらかというと保守的なもので、eXtreme         Programmingのような教えは米国のソフトウェアコンサルタントの新しい飯の種にすぎない、自己啓発セミナーのようで胡散臭い、といった         見方だった。また、色々なプログラミング言語を勉強したり、積極的に新しいプログラミングのトレンドを追いかけたりもした。C++のテンプレートなどもその一つで、自分の<a href="http://zzz.zggg.com/2003/08/16/template-metaprogramming-for-dummies/">3年前の文章</a>を見るに、当時はかなり興味を覚えていたようだ。余所余所しい書き方をするのは、関心が他に移っていったからに他ならない。その         徴候はこのサイトの2004年4月28日の項に現れている。<br />
<span id="more-130"></span><br />
実は、DICEを作りながら、3年以上前から別のネットワークアプリケーションを作ろうと考え色々と準備してきた。DICEには TCP/IP       ネットワークアプリケーションの全てが入っている。ファイルの送受信、通信の暗号化、多数のクライアントセッションのホスト、       クライアントセッションへのグループ(チャンネル)毎へのメッセージブロードキャスト、HTTPによるファイアウォール越え、データ       レコードの検索、データベースとの接続、.NETコンポーネントのホスト、GUIクライアント、Web          GUI等、要はDICEというのは私にとってのWindowsネットワークアプリケーションC++ライブラリのサンプルアプリケーションだ。DICEの中に入っている物を組み替えればどんなTCP/IPアプリケーションでも高パフォーマンスを維持しつつ作れる。ツールは全て揃っている。アマチュアゲーム製作の際にやってはいけないことの筆頭として「自分用のライブラリを製作すること」というのがあるそうで、それは「いつまで経っても目的の ゲームが完成しないから」だそうだ。DICEの場合、作り始めて4年で、自分用のライブラリ、ツールキットとしてはほとんど完成したのではないかというと ころまで、やっと到達できた。</p>
<p>新しいアプリケーションはもちろんDICEの部品を積極的に活用する。今度はクライアントとしての側面を強調したアプリケーションにな る予定だ。開発スタイルも、DICEのようにドキュメントも含めて完成したパッケージとするのではなくて、もっとカジュアルでインクリメンタルなものにしたい。それこそbloggerがblogを毎日記すように、頻繁に少しずつ更新したプログラムをアップロードしたい。セットアップもzipアーカイブの中身を取り出すだけの簡単な物が良いかもしれない。DICEでは一旦更新するとなるとドキュ        メントやパッケージの更新も必要で大がかりになり、アプリケーションそのもののスピーディな更新ができなかった。気がついたら、自分が軽んじていたアジャ イルソフトウェア開発のようなスタイルに惹かれていたのは何とも皮肉だ。こだわりやプライドを捨てることのなんと大変なことか。歳を取ってわかることはあると実感する。ツールの研究は十分やった。だから、こだわりを捨てて謙虚に、より多くの人に必要とされるようなソフトウェアを書きたい。応用(application)というのがキーワードだ。</p>
<p>しかしながら、より良いツールに対する興味も完全に失われたわけではなくて、どうやればクライアント向けの使いやすいユーザインター フェイスを作れるかについて、もうかなりの時間悩んでいる。最初に設定したユーザインターフェイスの要件は、1.         ユーザが自由に改変できるものであること 2. 短期間で要素技術が陳腐化しないこと である。この要件に合致するには何を選べばよいか、悩み続けている。ユーザによる改変可能性については、ユーザインターフェイスはユーザが常に触れる物なのでユーザが好き勝手に改変できるのが最良だろうという価値判断による。改変可能性の程度としては、スキン変更といった見栄えのみの変更に留まらず、ユーザとアプリ ケーションコア間でのインターフェイスとしてのデータストリームを全てユーザが制御できるようにしたい。ある意味Unixシェルのリダイレクトやパイプの概念と同じである。プログラム全体をオープンソースにすれば原理的にこの問題は解決するが、それをせずに、アプリケーションの一定部分のみプログラマブル にしたいというわけだ。パフォーマンス上の観点からも、パフォーマンスの要る部分はネイティブコンパイル済みで、それ以外はユーザが自由に変更できるとい う構成は理にかなっている。何よりそんなアプリケーションがあったら私自身が欲しい。</p>
<p>DICEを最初にリリースしたときから管理用GUIクライアントとして添付しているDICEAdminShellはMFCで作られているC++アプリケーションである。当然ユーザによる改変は出来ないし、MFCというライブラリは今ではほとんど進歩の望めない陳腐化したテクノロジになっ てしまっている。これではいかにも面白くない。そこで、次に候補になるのは.NETだ。2004年にはDICEが.NETコンポーネントを       ホストできるようにした。これはWebサーバ向けの機能として入れた物でWebアプリケーション実行が主用途だが、GUIを持ちユーザの       デスクトップとインタラクトするアプリケーションも実行できる。現在のMS          SQLサーバはストアドプロシージャをC#などで記述でき、同じようにDICEもCLRをホストしている。CodeDomを用いてC#などのソース          コードをコンパイルしてアセンブリにした上でロードし実行する機能も入れてあるので、ユーザによるコード改変も容易である。問題は、GUIのコードを Windows          Formsで開発するのが良いことなのかということだ。あと1年もすればWPF/XAMLがWindowsアプリケーションの標準ユーザインターフェイス定義言語になることは目に見えている。もう一つの可能性として、C#やC++/COMでIEコンポーネントを使う、あるいはGecko/XULでアプリケーションを構築するという道がある。IEコンポーネントの方は私自身が日常的にIEを使っていない関係上IEウィジェット         の見栄えや挙動に縛られるのは個人的に嫌だし、Mozillaの方はお世辞にも開発サポートが豊富とは言い難くまたランタイムの         配布サイズも大きくなってしまいそうだ。</p>
<p>そこで、最後に検討しているのはWeb UIである。DICEにはIEとFirefoxの双方で使える<a href="http://zzz.zggg.com/2006/11/12/server-push-today-marginal-ajax/">Web           IRCクライアント</a>を作って入れた。これはJavaScriptでタブ切り替えのUIを構成し、AJAXのHTTPリクエストではなくFlashでサーバ本体との通信を行っている。画面遷移も勿論無い。DICEにはWebサーバ機能があるので、リモート用途でなくともWeb           UIを簡単に付けられるというわけだ。もちろん、通信を暗号化すればインターネットを経由したリモートアクセスのためのUIと           しても使える。Web UIの短所は、右クリックコンテクストメニューがフックできない場合があること、クリップボードやサウンド再生が使えないことである。クリップボードやサウンド再生は、リモートからアクセスするのではなくローカルで動かす場合なら本体の方で処理すればよいのでそれなりに対処できるが、右クリックコンテクストメニューはブラウザ自身のメニューとの           兼ね合いで問題がある。その点のみ無視すれば、基本的に、フレームバッファの内容に直接演算を適用しないと得られない特殊な視覚効果を除き、普通のデスクトップアプリケーション並みのリッチなUIをWeb           UIでも実現できる。残る課題はFlashとサーバとの通信の暗号化くらいだけれども、それも目処が付いている。そういうわけで、今考えているデザインは、UIはWebブラウザを使って、それでカバーできない部分は上記の.NET           CLRホスティングでC#ソースコードを公開しユーザが改変できるようにしたいと思っている。アプリケーションのコアは今まで           通りC++で、パフォーマンスの要らない部分は可能な限り.NETの方へ回す。このページの下にある私が書いた2004年の文章では           「C++が格下の存在になりつつある」などと書いているが、実態は全然そんなことはなかったようで、Windows           Vistaですら.NETは一定の範囲に留められている。DICEに<a href="http://zzz.zggg.com/2006/11/15/perl-ruby-multithreading-embedding/">Perlも組み           込んだ</a>し、要は臨機応変というわけだ。フットワークの軽さなら自信がある。</p>
<p>以上が今後公開しようと思っているアプリケーションの話。このサイトで他にやっているのはプログラミング関係の雑文の公開だが、一番反響があるのは意外なことに<a href="http://zzz.zggg.com/2005/05/05/how-to-programmatically-create-self-signed-cert-key-pair-windows-sspi/">SSLの証明書を証明書ストアに入れる方法の記事</a>だっ た。資料が無くて困っているのは皆同じのようである。開発者が感謝のメールをくれたりする。中には記事の範囲を超えてあれこれと相談をし てくる人もいた。自分でビジネスをやっている人間は自分で問題解決して欲しいところだ。とは言っても私もFirefoxのBugzillaではオープンソースプロジェクトなのを良いことにかなり好き勝手な注文を付けたりしているので人のことは言えない。他人のリポートしたバグのプロパティを変更する権限を私は持っているので、他人のバグには介入するわで余計始末が悪い。</p>
<p>最近は諸々の理由で全くPCゲームをやっていない。一方で、ゲーム機のハードウェアへの興味が約2年前から非常に高まった。メインストリームPCがようやく並列プログラミングに入ろうかという時期に、ゲーム機は既に3コアとか9コアといった並列性の高いアーキテクチャをCPUに採用している。GPUの並列性はさらに高い。また、パフォーマンスがとかく軽視されがちなWebなどの       プログラミングと異なり、パフォーマンスが何時の時代も追及されるのはゲームの分野である。ネットワークプログラミングも、MMOGやオンラインFPSなど一つのワールドイメージを維持しようとした場合の負荷は非常に高い。というわけでここ2年は<a href="http://forum.beyond3d.com/forumdisplay.php?f=37" target="_blank">Beyond3DのConsole           Forum</a>に入り浸っている。ここはゲームコンソール関連のフォーラムの中では世界で一番参加者の平均年齢並びに精神年齢が 高いのではないかと思われるフォーラムで、PS3やXbox  360の開発者もよく投稿しており、メモリのレイテンシにまで気を配るハードウェアに密着したプログラミングの話題は非常に勉強になる。ゲーム機の話題は技術的な物も含め日本発のものも多く、私はよく<a href="http://www.gamasutra.com/php-bin/news_index.php?story=9642" target="_blank">日本語記事の英訳を提供している</a>のでそれが各方面から喜ばれているようだ。<a href="http://www.beyond3d.com/" target="_blank">Beyond3D</a>自体はリアルタイム3Dグラフィックスハードウェア専門サイトで、業界人御用達のような場所である。</p>
<p>IRCも約2年前からあまりやっていない。今でも海外のネット上の情報の一番濃い部分がIRCにあるという点は変わらないが、それでもWebのフォーラムが爆発的に普及してきているという印象がある。6、7年前は、日本の方が掲示板は進歩していたと思う。       ところがそれ以降は海外の掲示板がCMSの進歩やPHPの台頭と相俟ってWebサイトの必需品となり、発展してきた。       <a href="http://imageshack.us/" target="_blank">imageshack</a>とか<a href="http://www.imagevenue.com/" target="_blank">imagevenue</a> のようなイメージホスティングサービスも掲示板貼り付けを前提としてPHP掲示板用のBBコードを用意してくれる。海外の掲示板カルチャーは確固たるメンバー管理システムと組み合わさっていて、運用にはデータベースが必須である。CMSと親和性が高いのも当たり前だ。日本に見られる2chのような一様な匿名空間やSNSと称した出会い系サービスの作る空間、blogの散漫な空間とは一線を画した、社会的でかつ開かれた議論を行うための空間がそこにはある。こうなってくると、TCP/IPに乗せるのはHTTP だけで良く、IRC等は要らないのではないかという疑念も生じるかも知れない。しかし、Webフォーラム        とIRCの微妙な差異は依然として残っており、通信のオーバーヘッドの多寡を除けば、その差異とは、IRCではチャンネルのメンバーリストがリアルタイム に更新されメンバーの出入りがリアルタイムに宣言されるという点である。IRCはメンバーのプレゼンスをも模している。この参加者の居るライブ感という差異が残る限り、IRCが滅びることはない。一方で、<a href="http://www.secondlife.com/" target="_blank">Second           Life</a>や<a href="http://www.worldofwarcraft.com/" target="_blank">World of Warcraft</a>のような3Dの専用           クライアントによるリアルタイムコミュニケーションも、10年前には失敗したが回線品質が向上した現在はクリティカルマス           に達しようかというところまで来ているようだ。それでも、無駄な部分を省いたコミュニケーションの需要は残るはずであり、むしろ参入障壁が十分に低ければシンプルなものがリッチなものを出し抜く可能性が十分にある。<a href="http://zzz.zggg.com/2006/11/12/server-push-today-marginal-ajax/">Web           IRCクライアント</a>にはそのような展望も込めている。</p>
]]></content:encoded>
			<wfw:commentRss>http://zzz.zggg.com/2006/11/30/shonuff/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Perl, Ruby, Multithreading, Embedding</title>
		<link>http://zzz.zggg.com/2006/11/15/perl-ruby-multithreading-embedding/</link>
		<comments>http://zzz.zggg.com/2006/11/15/perl-ruby-multithreading-embedding/#comments</comments>
		<pubDate>Tue, 14 Nov 2006 21:55:46 +0000</pubDate>
		<dc:creator>RyuK</dc:creator>
				<category><![CDATA[code]]></category>

		<guid isPermaLink="false">http://zzz.zggg.com/?p=77</guid>
		<description><![CDATA[For the first half of this article the main topic is multithreading in the 2 scripting languages, Perl and Ruby. By writing a multithreaded download manager application in Perl and then porting it to Ruby, it&#8217;ll show you how to write a multithread application in the both languages and show you the difference of these [...]]]></description>
			<content:encoded><![CDATA[<p>For the first half of this article the main topic is multithreading in the 2 scripting languages,         Perl and Ruby. By writing a multithreaded download manager application in Perl and then porting         it to Ruby, it&#8217;ll show you how to write a multithread application in the both languages and         show you the difference of these 2 languages in this area. This section should be fairly easy and doesn&#8217;t          require much knowledge about the scripting languages, but it&#8217;s expected that you have basic         grasp of multithread programming.</p>
<p>The second half is for a bit more advanced programming topic; it&#8217;s about how to write a C++         application with an embedded Perl or Ruby interpreter. Simply embedding them is not rocket science,         but using them in an effective manner is not a very easy task right now because of the         implementations of these languages. If you are familiar with .NET you might know it&#8217;s embedded-friendly         with AppDomain and COM interfaces. On the other hand you have only raw C interfaces for these         scripting languages, let alone scarce documents. As for Perl embedding, the sample code is based         on the version I actually implemented in the web server of the <a href="http://zzz.zggg.com/dice/" target="_blank">DICE</a>.         Since it&#8217;s realized by the mixture of  C++ code and Perl hack, it requires some knowledge         of C/C++, advanced Perl programming, and Perl internals. But don&#8217;t be scared, I&#8217;ll annotate most         lines in the code to make it useful for as many people as possible because it&#8217;s the very purpose         of this article! Last but not least, the platform for those experiments is Microsoft Windows         XP and Visual C++ 7.1. But due to the platform-neutral nature of these scripting languages most         things should be applicable to any platforms.<br />
<span id="more-77"></span><br />
Though Microsoft Windows is my primary desktop, I still use the <a href="http://www.perl.com/" target="_blank">Perl</a> scripting         language to make a code snippet for a trivial text manipulation task such as list generation         in the shell or some network test. I know the <a href="http://www.ruby-lang.org/" target="_blank">Ruby</a> object-oriented         scripting language has become one of the hot topics among tech-savvy people, but I&#8217;d prefered         Perl just because I was familiar with it for longer time through developing Perl CGI applications in the earlier part of my programming history. My most preferred programming         language is by far C++, but it&#8217;s not exactly handy to write a tiny application. I like C# and         .NET too, but it&#8217;s still overkill. Dynamic languages have certain drawbacks and I would like to criticise them, but it&#8217;s true dynamic languages are sweet.</p>
<p>There are many scripting languages, Perl, Ruby, Python, PHP&#8230;  First Python is out, I don&#8217;t like significant whitespaces because I do  lots of indents for an aesthetic reason only. Next I drop PHP, its  focus seems to be the companionship with Apache and nothing  else. Ruby is cool, but I didn&#8217;t like the begin&#8230;end block. Braces can be used for blocks, but not in other places. Basically I&#8217;m too fond  of the C-style (or Algol-style) syntax. Perl 5 is a  clumsy procedural scripting language built on the C-like syntax, and the  only point I applaud in Perl is that it recommends the C-like syntax   (albeit with a little difference here and there) if you try to be  consistent. If I could use Ruby with C/Java like syntax I&#8217;d completely  abandon Perl anyday. But it didn&#8217;t happen.</p>
<p>Besides, object-orientedness in Ruby doesn&#8217;t matter in a small  code snippet, since humans are not that dumb and can use  non-object-oriented, non-intuitive expressions. Well it&#8217;s not totally  useless, but it makes more sense in the other situations. When you are  building a huge software stack or a huge loosely-coupled software  network, capsulation is very important. There are other goodies in Ruby,  but I couldn&#8217;t care less about esoteric syntax sugars. So is Perl the  perfect language for writing a code snippet? It has to be tested how perfect it is anyway.</p>
<p>One day I noticed my favorite download manager application for  Windows didn&#8217;t support chunked HTTP transfer properly. The downloader /  web site grabber application, Irvine, is an excellent Japanese software  but the development halted a few years ago. Its document said it would  support it in future, but it&#8217;s apparent that it wouldn&#8217;t arrive for a  while. If I have enough time I want to develop an HTTP download manager  just for myself in C# or something, but it&#8217;s not possible then. What I  needed at that time was downloading many files off a web site that is  configured to send all files in chunked transfer. Therefore I began  evaluation of a network library of Perl to see if it can do chunked  transfer. If it can, I can just write a short Perl script to get the  result I want. But it doesn&#8217;t end there, as I had to download hundreds  of files it&#8217;s preferable that it downloads multiple files at the same  time just like the aforementioned Windows application. I knew Perl had  implemented multithreading years ago, but still had the impression that  it might be awkward. So this is a nice occasion to do an experiment for  these 2 points: HTTP networking and multithreading.</p>
<p>Thinking about them, it came to my mind that I&#8217;d read somewhere  that Ruby supports them out of the box. Ruby supports multithreading  even in DOS by its own non-native threads. Also Ruby&#8217;s library can  handle networking with ease, as far as I knew. I&#8217;d known how Ruby works  and what kind of things are available for it for years but didn&#8217;t write a  Ruby code as Perl was sufficient for my use. On the other hand I knew  the transition to Perl 6 was not exactly smooth and I&#8217;d waited years for  it to come but eventually the whole Perl scene got out of my interest  when I was into other things. So this is a good occasion to test Ruby  for myself and see how my prejudice against it which I wrote above about  its non-C-like appearance can stand and if I can move to Ruby for my  code snippets.</p>
<p>So let&#8217;s write a multithreaded download manager for Perl.  First, we must choose how to download files. Since my purpose is to get  an application that can handle chunked transfer properly and just that,  it&#8217;s obvious I can&#8217;t bother to write a complicated network negotiation  with basic Perl Socket classes! libwww-perl (LWP) is a set of Perl  modules which provides API to write a web client. I tested if it can  download a file from the web site from which the Windows downloader  application failed to download a file and fortunately it worked. I  forget if the standard Perl distribution for Windows available from <a href="http://www.activestate.com/" target="_blank">ActiveState</a> has LWP, if it&#8217;s not installed you have to download it from the <a href="http://search.cpan.org/%7Egaas/" target="_blank">author&#8217;s page</a> at the CPAN and build it with the nmake of Visual C++ (probably freely  available from Microsoft) from the command line. With libwww-perl you  can control a web client object to surf the web.</p>
<p>Another thing to look at is how Perl&#8217;s multithreading works. <a href="http://perldoc.perl.org/" target="_blank">Perldoc</a> has a tutorial for Perl multithreading, <a href="http://perldoc.perl.org/perlthrtut.html" target="_blank">perlthrtut</a>.  This document has most of  the info you need to program a multithread  application. If there&#8217;s only one point which is specific to the Perl  thread, it&#8217;s that memory space is not shared by threads unlike usual  thread implementations. All usual premitives that are used in  multithreaded programming is available in there. For more info about  thread-related modules in Perl such as threads::shared, see their  documents from the <a href="http://search.cpan.org/%7Ejdhedden/" target="_blank">author&#8217;s page</a> at CPAN.</p>
<p>By the way I first tried to make my downloader application in  this way &#8211; it spawns multiple worker threads from the main thread and  suspends the main thread by putting a blocking semaphore (by setting the  counter value 0) in it, then a worker thread downloads a file in the  download queue, and it spawns another thread at the end of a thread,  finally when the queue becomes empty the semaphore in the main thread is  lifted, then all threads are joined and the main thread ends. The  number of worker threads is kept at the same number throughout  execution. But it didn&#8217;t work, as the Perl interpreter crashes at the  end of execution everytime I run it though it can download all the files  in the list. For writing a native multithread  application for  Microsoft Windows this should work, but in Perl&#8217;s case you have to join  all threads without spawning a new thread from a running thread. I first  thought it&#8217;s caused by a bug in the thread module and browsed the <a href="http://www.cpanforum.com/dist/threads" target="_blank">forum</a> for the module, and what I found was there are non-thread-safe modules  which can&#8217;t be used in Perl threads. libwww-perl looks like that and if I  removed it the program ended without an error. Seeking the solution I  downloaded the latest version of the threads module from the <a href="http://search.cpan.org/%7Ejdhedden/" target="_blank">author&#8217;s page</a> and built it. I tested threads-1.42 which was the latest at the time I  wrote the Perl script. It had a small problem when compiling it with  VC++, it couldn&#8217;t detect the existence of a C compiler even though I  used the command console provided in the Microsoft Platform SDK. The  solution is to edit Makefile.pl and skip the have_cc subroutine. Then  you can compile and install it. But it had still the same crash. Without  a choice I abandoned this design and decided to use the more  traditional, pthread-esque design.</p>
<p>In this actual design of the script, it spawns worker threads  and they get in infinite loops that pick up a job from a job queue one  by one. The main thread blocks by calling the <code>join</code> function  on these worker threads. This looks fairly simple even from the  description of it and may look better than what I described above, but I  prefer a more asynchoronous style if it works. I wrote it and this time  it worked flawlessly without a crash and resource leaks. The following  is the code. It&#8217;s tested with the Win32 version of Perl 5.8.8 available  from <a href="http://www.activestate.com/" target="_blank">ActiveState</a>, threads-1.42 module, and libwww-perl-5.805 module.</p>
<p>The simple user configuration section comes at the top.</p>
<pre class="brush: cpp;">
# user configuration begin ################################

# You have to list the URLs for the files to be downloaded in a file named
# &quot;download_files.txt&quot; and put it in the same directory as this script.

my $username = &quot;&quot;;
my $password = &quot;&quot;;

my $number_of_threads = 3;
my $download_interval_sec = 3;

my $download_list_filename = &quot;download_files.txt&quot;;

my $storage_directory = 'E:\program\src\downloader\store'; # set &quot;&quot; for the current directory

my $user_agent_string = &quot;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)&quot;;

# user configuration end ##################################
 </pre>
<p><code>$username</code> and <code>$password</code> are for basic authentication at password-protected pages. <code>$number_of_threads</code> tells how much worker threads do the downloading work simultaneously.  You can set how many seconds it waits before making a new download  connection in <code>$download_interval</code>.</p>
<p>After user configuration options, you have to load necessary modules and other tools useful throughout this application.</p>
<pre class="brush: cpp;">
use strict;
use warnings;

use threads;
use threads::shared;
#use Thread::Queue;
use Thread::Semaphore;

use LWP::UserAgent;
use Cwd;

my $current_directory = Cwd::getcwd();

if (!$storage_directory)
{
	$storage_directory = $current_directory;
}

$| = 1;

my @download_queue : shared; # used for optimization instead of Thread::Queue

my $sem_download_queue = new Thread::Semaphore;
my $sem_stdout = new Thread::Semaphore;

my $last_download_time : shared = 0;
	  </pre>
<p>The <code>strict</code> and <code>warnings</code> modules are  in action here to ban obscure expressions which are often seen in Perl  hacks. While this is a relatively short script it&#8217;s always nice that minor things don&#8217;t bother users. <code>$| = 1</code> expression is what you  always see in Perl scripts that process realtime I/O. It prevents  buffering and forces Perl to issue more I/O calls. The  synchoronized download queue where all jobs are put and fetched from in the  serial manner is <code>@download_queue</code>. To make it accessible from multiple threads, you have to mark it explicitly with the <code>shared</code> keyword and this is Perl-specific way as I wrote above. Actually Perl already has a useful tool for a situation like this as the <code>Thread::Queue</code> module, it&#8217;s absent as I control the scope of synchonization  explicitly to gain a bit better performance. So it requires a semaphore  object as a synchronization primitive, <code>$sem_download_queue</code>. <code>$sem_stdout</code> is the semaphore to synchronize the standard output, since multiple threads try to print text reports simultaneously. <code>$last_download_time</code> is the variable used to force threads to put an interval between downloads as explained in the configuration options.</p>
<p>The next part is the main loop of the script.</p>
<pre class="brush: cpp;">
print &quot;Perl threads version: &quot; . $threads::VERSION . &quot;\n&quot;;

open(IN, $current_directory . &quot;\\&quot; . $download_list_filename) || die(&quot;Can't open &quot; . $download_list_filename);

while (&lt;IN&gt;)
{
	chomp;
	if ($_ =~ /^http:/i)
	{
		push @download_queue, $_;
	}
}

close(IN);

print @download_queue . &quot; URIs have been loaded from the download list\n&quot;;

for (my $i = 0; $i &lt; $number_of_threads; ++$i)
{
	new threads(\&amp;amp;download_thread_func);
}

foreach my $thr (threads-&gt;list)
{
	if ($thr-&gt;tid)
	{
		$thr-&gt;join;
	}
}

print &quot;Download completed\n&quot;;

################################################################
	 </pre>
<p>Very short, don&#8217;t you think? It&#8217;s cooler that there are less  things to tweak than to be error-prone. It reads the content of the URL  list and puts all of them in the download job queue. Then spawns worker  threads, and waits them by calling <code>join</code>. As the <code>join</code> function blocks, the main thread can stay in memory waiting all  worker threads to return from the work. If it didn&#8217;t block, the main  thread would just exit and the whole program would halt leaking thread resources.</p>
<p>A spawned worker thread calls the function referenced as <code>download_thread_func</code>. Let&#8217;s see the part that does the heavy work.</p>
<pre class="brush: cpp;">
sub print_ts
{
	$sem_stdout-&gt;down;
	my $tid = threads-&gt;self-&gt;tid();
	print ($tid . &quot;: &quot; . shift @_);
	$sem_stdout-&gt;up;
}

sub download_thread_func
{
	for (;;)
	{
		my $u = &quot;&quot;;
		my $sleep_time = 0;

		$sem_download_queue-&gt;down;

		if (@download_queue == 0)
		{
			$sem_download_queue-&gt;up;
			print_ts &quot;The download queue is empty\n&quot;;
			return;
		}
		else
		{
			$u = shift @download_queue;

			# $last_download_time is protected by $sem_download_queue
			my $t = time();
			if ($t &lt; $last_download_time)
			{
				$sleep_time = $last_download_time - $t + $download_interval_sec;
			}
			else
			{
				if ($t - $last_download_time &lt; $download_interval_sec)
				{
					$sleep_time = $download_interval_sec - ($t - $last_download_time);
				}
				$last_download_time = $t + $sleep_time;
			}
		}

		$sem_download_queue-&gt;up;

		if ($sleep_time)
		{
			print_ts &quot;Sleeping for &quot; . $sleep_time .&quot; seconds\n&quot;;
			sleep($sleep_time);
		}

		download_uri($u);
	}
}
	</pre>
<p><code>print_ts</code> is just a debug output function mainly used to show HTTP headers. It&#8217;s a print functon synchronized  by a semaphore. The <code>download_thread_func</code> subroutine fetches a job from the job queue then consumes it. It continues to do it  until the queue gets empty. The part that tests and modifies the  download queue is synchronized by a single semaphore. The actual  download is handled by the <code>download_uri</code> subroutine desctibed below.</p>
<pre class="brush: cpp;">
sub download_uri
{
	my $uri = shift @_;

	print_ts &quot;Downloading $uri\n&quot;;

	my $ua = new LWP::UserAgent;
	$ua-&gt;cookie_jar({});

	my $req = new HTTP::Request(GET =&gt; $uri);
	$req-&gt;header(
		&quot;User-Agent&quot; =&gt; $user_agent_string,
		&quot;Accept&quot; =&gt; 'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*',
		&quot;Accept-Charset&quot; =&gt; 'iso-8859-1,*,utf-8',
		&quot;Accept-Language&quot; =&gt; 'en-US'
	);

	if ($username)
	{
		$req-&gt;authorization_basic($username, $password);
	}

	my $response = $ua-&gt;request($req);

	if ($response-&gt;is_success)
	{
		print_ts &quot;header begin-----------------------------------------\n&quot;
			. $response-&gt;headers_as_string
			. &quot;header end-------------------------------------------\n&quot;;

		my $is_chunked = (
			($response-&gt;header('Transfer-Encoding') &amp;amp;&amp;amp; $response-&gt;header('Transfer-Encoding') =~ /Chunked/i)
				|| ($response-&gt;header('Client-Transfer-Encoding') &amp;amp;&amp;amp; $response-&gt;header('Client-Transfer-Encoding') =~ /Chunked/i)
			)
					? 1 : 0;

		if ($response-&gt;header('Content-Length') &amp;amp;&amp;amp; $response-&gt;header('Content-Length') == 0 &amp;amp;&amp;amp; !$is_chunked)
		{
			print_ts &quot;Content-Length is zero\n&quot;;
		}
		else
		{
			my $image_name = &quot;tmp.bin&quot;;
			if ($uri =~ /\/([^\/]+)$/)
			{
				$image_name = $1;
			}

			if ($response-&gt;header('Content-Disposition'))
			{
				if ($response-&gt;header('Content-Disposition') =~ /filename=(.+)/i)
				{
					$image_name = $1;
					$image_name =~ s/&quot;//g;
				}
			}

			if (!open(OUT, &quot;&gt;$storage_directory\\$image_name&quot;))
			{
				print_ts &quot;open error : $storage_directory\\$image_name\n&quot;;
				exit();
			}
			else
			{
				binmode(OUT);
				#my $fsize = $response-&gt;header('Content-Length');
				if (!defined(syswrite OUT, $response-&gt;content, length($response-&gt;content)))
				{
					print_ts &quot;syswrite error : $storage_directory\\$image_name\n&quot;;
				}
				syswrite OUT, $response-&gt;content, length($response-&gt;content);
				close(OUT);

				print_ts &quot;Downloaded $image_name\n&quot;;
			}
		}
	}
	else
	{
		my $st = $response-&gt;status_line;
		print_ts &quot;Error: $uri : $st\n&quot;;
	}
}
	  </pre>
<p>It instantiates an object of the <code>LWP::UserAgent</code> class to do HTTP negotiation and sets necessary HTTP headers for it.  After a request is sent, the object receives an HTTP response header  and body. As the purpose of this script is to download a file off of a  setver that does chunked transfer, it does check if it founds an HTTP  header line that indicates it, <code>Transfer-Encoding</code> or <code>Client-Transfer-Encoding</code>. In a chunked transfer, a real file name is communicated by a <code>Content-Disposition</code> header, so it scans this header too. When all necessary info are  available the rest thing to do is to save received data into a file. If it&#8217;s a binary file, you have to call <code>binmode</code> to set the file handle in the binary mode on Windows. The data length can be obtained by a <code>Content-Length</code> header for a nomal HTTP response but the chunked transfer is the method to send a file without sending a <code>Content-Length</code> header. Conveniently the data size is already known by the length of <code>$response-&gt;content</code> data.</p>
<p>That&#8217;s all for the Perl downloader script. It&#8217;s about 200 lines  and it works, no big deal. So let&#8217;s move onto the next task, porting it  to Ruby.</p>
<p>Before writing the code, as Japanese is my 1st language I went to the Japanese side of the Ruby HQ, <a href="http://www.ruby-lang.org/" target="_blank">ruby-lang.org</a> for the Ruby language/library refererence. It&#8217;s known that Ruby was  invented in Japan and some non-Japanese users often complain the help documents of Ruby are rather weak compared to Perl and Python in a language  comparison war. But the reality is, the Japanese documents for Ruby are downright terrible. At least the official manual is very sparse and  unorganized. Probably mailing lists and code samples in Japanese may carry beefier contents than English equivalents, but the Japanese  documents at <a href="http://www.ruby-lang.org/" target="_blank">ruby-lang.org</a> are not something you yearn for and I actually found English documents linked from the <a href="http://www.ruby-lang.org/en/documentation/" target="_blank">English side</a> are more useful.</p>
<p>Writing a Ruby code immediately after writing a Perl code is a bit puzzling experience. In Ruby, the <code>@</code> prefix means an instance variable (or an object property, in a more  usual object-oriented lingo) unlike the array expression in Perl. In  Ruby it&#8217;s about a variable scope and in Perl it&#8217;s about a data type.  I like Ruby&#8217;s scope which is  more object-oriented and cleaner than  Perl&#8217;s one which is  heavily dependent on its symbol table and often the  source of dirty hacks. But the main problem is besides that. It&#8217;s about braces.  As I wrote at the beginning of this article I like braces and would like  to use it as much as I can do. But Ruby only allows it at blocks and I  often trip on it by using braces where it accepts only <code>do...end</code>.  Another thing is, Ruby doesn&#8217;t require you to end a line with a  semicolon, but when you put an unwanted carriage-return it just emits  syntax errors and stops. So you can&#8217;t hit the Enter key just to tidy up  the layout. It is especially problematic when you declare  a block  parameter for a block which is one of the main selling points of Ruby. So my  advice for writing a Ruby code is: don&#8217;t use braces and don&#8217;t hit the  Enter key too often.</p>
<p>Let&#8217;s take a look at the Ruby version. The user configuration  part is at the beginning and it won&#8217;t need much explanation as it&#8217;s  almost identical to the Perl version. Some variables are declared as  instance variables of the main function as they are used not before declaration in some methods. In the Ruby version, HTTP negotiation is  handled by the standard <code>net/http</code> library which, fortunately again, can download chunked transfer files. <code>thread</code> is Ruby&#8217;s intrinsic class for multithread programming. <code>uri</code> is a small utility class to handle a URI. <code>Net::HTTP.version_1_2</code> is the pragma to instruct <code>net/http</code> to use the newer implementation. <code>if $0 == __FILE__ line</code> is an include guard but it means nothing in this demo. This script is tested with Ruby 1.8.5.</p>
<pre class="brush: cpp;">

# user configuration begin ################################

@username = &quot;&quot;
@password = &quot;&quot;

number_of_threads = 3
@download_interval_sec = 3

download_list_filename = &quot;download_files.txt&quot;

@storage_directory = 'E:\program\src\downloader\store' # set &quot;&quot; for the current directory

@user_agent_string = &quot;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)&quot;

# user configuration end ##################################

require 'net/http'
require 'thread'
require 'uri'

Net::HTTP.version_1_2

if $0 == __FILE__
	  </pre>
<p>The following section contains the Ruby versions of the functions  with the same name as in the Perl version. The only difference is it  uses synchronization primitives (mutex) more intuitively.</p>
<pre class="brush: cpp;">
def puts_ts(x)
	@mutex_pt.synchronize do
		puts Thread.current.object_id.to_s + &quot;: &quot; + x
	end
end

def download_thread_func
	while true do
		u = &quot;&quot;
		sleep_time = 0

		@mutex_dq.synchronize do
			if @download_queue.empty?
				puts_ts &quot;The download queue is empty&quot;
				return
			else
				u = @download_queue.shift
				# @last_download_time is protected by @mutex_dq
				t = Time.now.gmtime.to_i
				if t &lt; @last_download_time
					sleep_time = @last_download_time - t + @download_interval_sec
				else
					if t - @last_download_time &lt; @download_interval_sec
						sleep_time = @download_interval_sec - (t - @last_download_time)
					end
					@last_download_time = t + sleep_time
				end
			end
		end

		if sleep_time != 0
			puts_ts &quot;Sleeping for &quot; + sleep_time.to_s + &quot; seconds&quot;
			sleep sleep_time
		end

		download_uri u
	end
end
	  </pre>
<p>The next section is again the function with the same name as the Perl version, but its content is a little different.</p>
<pre class="brush: cpp;">
def download_uri(uri)

	puts_ts &quot;Downloading #{uri}&quot;

	req = Net::HTTP::Get.new(URI.parse(uri).path)

	req[&quot;User-Agent&quot;] = @user_agent_string
	req[&quot;Accept&quot;] = 'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*'
	req[&quot;Accept-Charset&quot;] = 'iso-8859-1,*,utf-8'
	req[&quot;Accept-Language&quot;] = 'en-US'

	req.basic_auth @username, @password unless @username.empty?

	begin
		Net::HTTP.start(URI.parse(uri).host, 80) do |http|
			http.request(req) do |res|
				h = &quot;#{res.code} #{res.message}\nheader begin-----------------------------------------\n&quot;

				is_chunked = false
				filename = &quot;&quot;
				res.canonical_each do |n, v|
					h += (n + &quot;: &quot; + v + &quot;\n&quot;)
					if n =~ /Transfer-Encoding/i &amp;amp;&amp;amp; v =~ /Chunked/i
						is_chunked = true
					end
					if n =~ /Content-Disposition/i &amp;amp;&amp;amp; v =~ /filename=(.+)/i
						filename = $1
						filename.gsub(/^&quot;|&quot;$/, '')
					end
				end
				h += &quot;header end-------------------------------------------&quot;
				puts_ts h

				unless res.code == &quot;200&quot;
					return
				end

				if filename == &quot;&quot; &amp;amp;&amp;amp; uri =~ /\/([^\/]+)$/
					filename = @storage_directory + &quot;\\&quot; + $1
				end

				open(filename, &quot;wb&quot;) do |file|
					res.read_body do |str|
						file.write str
					end
				end
			end
		end
	rescue Exception =&gt; e
		puts_ts e.to_s
	end

end
	  </pre>
<p>By processing a code block the Net::HTTP class can process an  incoming HTTP response-body stream in multiple small chunks as the <code>read_body</code> function of the <code>Net::HTTPResponse</code> class returns. It means you don&#8217;t have to store the whole received file  in memory before dumping it onto a file unlike the Perl version. This  can be an advantage when a downloaded file is large.</p>
<p>The rest covers the main function. I think you can see the pattern here and it needs no further explanation. <code>&lt;&lt;</code> is a useful overloaded operator to push an element to the end of an array just like stack manipulation.</p>
<pre class="brush: cpp;">
# main begin #############################################################
@mutex_pt = Mutex::new

if @storage_directory.empty?
	@storage_directory = Dir.pwd
end

@download_queue = []
@mutex_dq = Mutex::new
@last_download_time = 0;

File.foreach(download_list_filename) {|line|
	@download_queue &lt;&lt; line.chomp if line.match(/^http:/i)
}

threads = []

for i in 1..number_of_threads
	threads &lt;&lt; Thread::start do download_thread_func end
end

for i in 1..number_of_threads
	threads[i - 1].join
end

# main end ###############################################################
end
	  </pre>
<p>That&#8217;s all for the Ruby version.</p>
<p>Now how do they compare? The first thing to be noticed is the  script length. The Perl version is over 200 lines and 4,890 bytes. The  Ruby version is under 150 lines and 3,383 bytes. The second point is the performance as they are multithreaded to squeeze extra  performance in the first place. I used them to download dozens of files  and the Perl version used about 15MB RAM and the Ruby version occupied  only 5MB. This is probably due to the difference of downloading methods  between 2 network libraries (whole download vs partial), but also due to  the implementation of interpreter threads in these languages, since 15MB  RAM usage is a bit too high for downloading files around 100KB. The  speed seemed a tie, but in some cases the Perl version was faster and  the Ruby version stuttered. Probably it can be attributed to file I/O  and some  context-switching awkwardness in the Ruby version.</p>
<p>My final verdict on this subject is, Ruby is the winner,  contrary to some of the possible doubts I suggested above. Of course I  still don&#8217;t like <code>begin...end</code>, but if you like the way Java  programs are written I guess you like Ruby too. Ruby is even nicer  than Java. Basically there&#8217;s nothing Ruby can&#8217;t do except  for Perl-specific symbolic hacks. Why not just jump ship and switch to  Ruby for good?</p>
<p>But this discourse doesn&#8217;t end there. Ruby is certainly a new  kid on the block (well not very new actually but leave it at that now)  that presents great opportunities. Even then, there may be something  useful in an older and proven tool. My server application, <a href="http://zzz.zggg.com/dice/">DICE</a>, ended up with an embedded Perl interpreter instead of Ruby. <a href="http://zzz.zggg.com/dice/">DICE</a> has already had a Common Language Runtime (CLR) embedded since years  ago, but for the upcoming new version I planned to add something new to  make a bullet point for the update list as it took over a year to prepare a new version. Though at first I wanted to embed PHP which is  fairly popular for a web application because of the affinity with the  Apache HTTPd, I abandoned that idea for now as the information about PHP  embedding seemed scarce on the web. (BTW I downloaded the source code of  the PHP5 and browsed it, it&#8217;s not much bigger than the source code of  the <a href="http://zzz.zggg.com/dice/">DICE</a> in size which was a  surprise for me.) So the candidates are, as you expect, Ruby and Perl.  What I&#8217;d like to accomplish is embedding an interpreter in the <a href="http://zzz.zggg.com/dice/">DICE</a> and making it process a web application, but not executing it for every request ala CGI. However, the merit of including such an interpreter is that it can support existing applications available without writing a new code.  The <a href="http://zzz.zggg.com/dice/">DICE</a> has a CLR embedded in  it to execute a web application, but it requires a user to write a stub  code, or an entire application. This time I want existing CGI  applications to run on the <a href="http://zzz.zggg.com/dice/">DICE</a>. Therefore the goal is to add <a href="http://perl.apache.org/" target="_blank">mod_perl</a> or <a href="http://www.modruby.net/" target="_blank">mod_ruby</a> like capability to it. The requirements are</p>
<p><a href="../../dice/"> </a>1. The interpreter is persistent.<br />
2. All input from the standard input and all output to the standard output must be hooked by the <a href="http://zzz.zggg.com/dice/">DICE</a> because stdin/stdout are the points where CGI scripts interact with an internet user through a web server.</p>
<p>This section of this article begins with Ruby. Before writing a  code for embedding Ruby, you have to learn the license of Ruby. It  adopts the dual license by the GPL and the other BSD-like license. It  seems you can do whatever with Ruby if you don&#8217;t choose GPL, but in  reality it&#8217;s not that simple. The current Ruby implementation (1.8)  contains the GNU regular expression library and it forces you to make  your application GPLed if you just embed a Ruby interpreter as is.  Actually there&#8217;s a workaround and it&#8217;s very simple. The current  development version of Ruby (1.9) has the <a href="http://www.geocities.jp/kosako3/oniguruma/" target="_blank">Oniguruma</a> regular expressions library which is under the BSD license to avoid  this license issue. If you choose to stick to 1.8, the source code of Oniguruma version 2.x is available for download, it can be integrated it onto Ruby 1.8  source codes (the instruction is in the Onigruma document). After  building the whole Ruby, you can open the binary (msvcr71-ruby19.dll if  you use VC++ 7.1 and Ruby 1.9) with <a href="http://www.dependencywalker.com/" target="_blank">Dependency Walker</a> to see if it has Onigruma symbols (prefixed with Onig) and not GNU regex ones.</p>
<p>The first thing you have to do is to download the Ruby source  code. Because of the reason I wrote above I recommend the 1.9  development nightly snapshot (snapshot.tar.gz) found in the <a href="http://www.ruby-lang.org/en/downloads/" target="_blank">download page</a> of the <a href="http://www.ruby-lang.org/" target="_blank">ruby-lang.org</a>.  Then obviously  you have to learn how to embed Ruby somewhere for  embedding Ruby. The particular document I referred to when I wrote my  sample code was magazine articles written by Shugo Maeda, the author of <a href="http://www.modruby.net/" target="_blank">mod_ruby</a>. They are found around <a href="http://shugo.net/article/cmagazine/3rd/" target="_blank">here</a> and <a href="http://shugo.net/article/cmagazine/4th/" target="_blank">here</a> at Mr. Maeda&#8217;s web site, and his VIM patch is <a href="http://shugo.net/archive/vim-ruby/" target="_blank">here</a>.  They discuss how he embedded Ruby into the vi-clone  editor VIM.  Unfortunately they are in Japanese though they have some code samples.  And this is the real horror story, I couldn&#8217;t find other detailed  Japanese documents for embedding Ruby. Actually the English <a href="http://wiki.rubygarden.org/Ruby/page/show/EmbedRuby" target="_blank">EmbedRuby</a> article at the Ruby Garden is probably the second best source of info  on this matter. After you read the tutorial,  you can download the C  source code of <a href="http://www.modruby.net/" target="_blank">mod_ruby</a> to learn how a real-world application hosts a Ruby interpreter though  it may be hard for those who are not familiar with how an Apache module  works.</p>
<p>In addition to the basics of embedding Ruby, Maeda&#8217;s article  offers how to interrupt the standard output of Ruby and trap it to feed  to VIM instead of the console screen. Ruby&#8217;s outputs are all assigned in  the <code>$&gt;</code> special variable and its real body in the C implementation of Ruby is the object named <code>rb_defout</code>. By hooking the <code>write</code> method of this object you can trap all Ruby outputs to the standard output. But how? You can do that by the <code>rb_define_singleton_method</code> function defined in the class.c of the Ruby source code. It defines a  &#8220;singleton method&#8221; which is a special method for a special object, which  is <code>rb_defout</code> in this case. Also you can redefine each output method in Ruby if you will by the <code>rb_define_global_function</code> function. It&#8217;s useful to preserve the way each output method behaves differently. If you read the source code of <a href="http://www.modruby.net/" target="_blank">mod_ruby</a> you&#8217;ll notice it actually redefines all related methods in Ruby with respective hook functions. <code>VALUE</code> is a Ruby type which can hold any Ruby object which is something like the <code>VARIANT</code> type in COM, or the void* type in C.</p>
<pre class="brush: cpp;">
-- class.c --
void
rb_define_singleton_method(VALUE obj, const char *name, VALUE (*func)(ANYARGS), int argc)
{
    rb_define_method(rb_singleton_class(obj), name, func, argc);
}

void
rb_define_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc)
{
    rb_add_method(klass, rb_intern(name), NEW_CFUNC(func, argc), NOEX_PUBLIC);
}

void
rb_define_global_function(const char *name, VALUE (*func)(ANYARGS), int argc)
{
    rb_define_module_function(rb_mKernel, name, func, argc);
}
-- eval.c --

void
rb_add_method(VALUE klass, ID mid, NODE *node, int noex)
{

}
	  </pre>
<p>To build a C++ code with a Ruby interpreter embedded, you need  Ruby header files, the Ruby static library and the Ruby dynamic link  library. The header file, <code>ruby.h</code>, is found in the root  directory of the Ruby source code archive. The static/dynamic libraries  can be obtained by building the Ruby source code. To build it on  Windows, open your VC++/Platform SDK build environment console and  run  the <code>configure.bat</code> in the <code>win32</code> directory then type <code>nmake</code>. You get <code>msvcr71-ruby19.lib</code> for the static library and <code>msvcr71-ruby19.dll</code> for the dll. Note that it doesn&#8217;t produce a static library for static  linking. These are release builds that refer to the release version of  the MS C-runtime library. I tried to build a debug version by editing  the makefile by changing the compiler option /MD to /MDd but the build  stopped in building one of .exe. It seemed to have created libraries at  least, but I couldn&#8217;t link it with an embedding C++ code in the Debug  mode nor in the Release mode.</p>
<p>Once the Ruby library is ready you have to set up your build  environment for your Ruby-embedded application. Set the root directory  of the Ruby source code in your include path and set the library path in  your LIB directory (In VC++ it&#8217;s in Tool | Options | Projects | VC++  Directories | Library files). As the resulted executable requires <code>msvcr71-ruby19.dll</code> to run you should copy it in the working directory by yourself. For the  C-runtime library, you have to choose the multi-threaded DLL or the  multi-threaded debug DLL since Ruby&#8217;s memory manager is built with the  multithread version of the library. Unfortunately the library built by  the default makefile has conflicts with other library in the Debug mode  with the multi-threaded debug DLL (/MDd). You have to address it by  putting <code>libcmtd.lib</code> in Linker | Input | Ignore Specific  Library in the VC++ project options for the Debug mode. In the Release  mode it&#8217;s not required, but it still issues linker warnings LNK4049. I  tried to create a project file by myself but failed to do that as the  Ruby I built couldn&#8217;t get the current directory as it can&#8217;t access some  APIs. I hope these issues with Windows are resolved in the future.</p>
<p>The environment is ready, let&#8217;s see the actual C++ code. This  sample code is written for a Ruby 1.9 development snapshot and doesn&#8217;t  work with Ruby 1.8 as there are significant changes in the Ruby source  code in 1.9. What this code does is fairly simple, initializes a Ruby  interpreter, loads and execute a Ruby script, then destroys the  interpreter. It includes necessary files at the beginning. For the sake  of simpleness I included the library file by the pragma, but it may be  safe to do in in the project setting if your project contains many files  that can conflict with it. (Actually for Perl embedding it didn&#8217;t work  if I did this.) In addition to the <code>ruby.h</code>, standard C++ headers are included for utility functions.</p>
<pre class="brush: cpp;">
#pragma comment(lib, &quot;E:\\program\\lib\\rubylib\\ruby_win32\\usr\\lib\\msvcr71-ruby19-static.lib&quot;)

extern &quot;C&quot; {

#include &lt;ruby.h&gt;

}

#include &lt;iostream&gt;
#include &lt;string&gt;

using namespace std;
	  </pre>
<p>First, output hook functions are defined.</p>
<pre class="brush: cpp;">
VALUE ruby_write_hook(VALUE self, VALUE str)
{
	str = rb_obj_as_string(str);
	cout &lt;&lt; string(RSTRING_PTR(str), RSTRING_LEN(str));

	return Qnil;
}

VALUE ruby_p_hook(int argc, VALUE *argv, VALUE self)
{
	VALUE str = rb_str_new(&quot;&quot;, 0);

	for (int i = 0; i &lt; argc; i++)
	{
		if (i &gt; 0)
			rb_str_cat(str, &quot;, &quot;, 2);

		rb_str_concat(str, rb_inspect(argv[i]));
	}

	cout &lt;&lt; RSTRING_PTR(str);

	return Qnil;
}
</pre>
<p><code>ruby_write_hook</code> is  a function to hook the singleton method of <code>rb_defout</code> as explained already. It extracts a <code>char*</code> pointer and its length from a Ruby string that is assigned to this special object, and outputs it by itself with <code>cout</code>. <code>Qnil</code> is the null value in the Ruby C implementation. <code>ruby_p_hook</code> is defined to redefine the kernel method <code>p</code> that is used to print the state of an object in a human readable form like <code>ToString</code> method in C#/Java. It create a new Ruby string by <code>rb_str_new</code> and enumerates its elements by adding them by <code>rb_str_concat</code>.</p>
<p><code>init_ruby</code> is the function that initializes the Ruby interpreter. <code>ruby_init_loadpath</code> initializes the library path of Ruby. <code>show_error_pos</code> and <code>show_exception_info</code> are the functions that shows detailed error information just like what  you get when you feed an erroneous Ruby script to the Ruby interpreter.</p>
<pre class="brush: cpp;">
void init_ruby()
{
	ruby_init();
	ruby_init_loadpath();
}

void show_error_pos()
{
	ID this_func = rb_frame_this_func();

	if (ruby_sourcefile)
	{
		if (this_func)
		{
			cout &lt;&lt; ruby_sourcefile &lt;&lt; &quot;:&quot; &lt;&lt; ruby_sourceline &lt;&lt; &quot;:in&quot; &lt;&lt; rb_id2name(this_func) &lt;&lt; endl;
		}
		else
		{
			cout &lt;&lt; ruby_sourcefile &lt;&lt; &quot;:&quot; &lt;&lt; ruby_sourceline &lt;&lt; endl;
		}
	}
}

void show_exception_info()
{
	if (NIL_P(ruby_errinfo))
		return;

	VALUE errat = rb_funcall(ruby_errinfo, rb_intern(&quot;backtrace&quot;), 0);
	if (!NIL_P(errat))
	{
		VALUE mesg = (RARRAY_PTR(errat))[0];

		if (NIL_P(mesg))
		{
			show_error_pos();
		}
		else
		{
			cout &lt;&lt; string(RSTRING_PTR(mesg), RSTRING_LEN(mesg));
		}
	}

	VALUE eclass = CLASS_OF(ruby_errinfo);

	char* einfo;
	int elen;
	int state;
	VALUE estr = rb_protect(rb_obj_as_string, ruby_errinfo, &amp;amp;state);
	if (state)
	{
		einfo = &quot;&quot;;
		elen = 0;
	}
	else
	{
		einfo = RSTRING_PTR(estr);
		elen = RSTRING_LEN(estr);
	}

	if (eclass == rb_eRuntimeError &amp;amp;&amp;amp; elen == 0)
	{
		cout &lt;&lt; &quot;: unhandled exception&quot; &lt;&lt; endl;
	}
	else
	{
		VALUE epath;

		epath = rb_class_path(eclass);
		if (elen == 0)
		{
			cout &lt;&lt; &quot;: &quot; &lt;&lt; string(RSTRING_PTR(epath), RSTRING_LEN(epath)) &lt;&lt; endl;
		}
		else
		{
			char* tail  = 0;
			int len = elen;

			if ((RSTRING_PTR(epath))[0] == '#')
				epath = 0;

			if (tail = strchr(einfo, '\n'))
			{
				len = tail - einfo;
				tail++;
			}

			cout &lt;&lt; &quot;: &quot; &lt;&lt; string(einfo, len);
			if (epath)
			{
				cout &lt;&lt; &quot; (&quot; &lt;&lt; string(RSTRING_PTR(epath), RSTRING_LEN(epath)) &lt;&lt; endl;
			}

			if (tail)
			{
				cout &lt;&lt; string(tail, elen - len - 1) &lt;&lt; endl;
			}
		}
	}

	if (!NIL_P(errat))
	{
		const int TRACE_HEAD = 8;
		const int TRACE_TAIL = 5;
		const int TRACE_MAX = TRACE_HEAD + TRACE_TAIL + 5;

		RArray* ep = RARRAY(errat);

		long len =  RARRAY_LEN(errat);
		for (int i = 1; i &lt; len; ++i)
		{
			if (TYPE((RARRAY_PTR(errat))[i]) == T_STRING)
			{
				cout &lt;&lt; &quot;  from &quot; &lt;&lt; string(RSTRING_PTR((RARRAY_PTR(errat))[i]), RSTRING_LEN((RARRAY_PTR(errat))[i])) &lt;&lt; endl;
			}

			if (i == TRACE_HEAD &amp;amp;&amp;amp; len &gt; TRACE_MAX)
			{
				cout &lt;&lt; &quot;   ... &quot; &lt;&lt; len - TRACE_HEAD - TRACE_TAIL &lt;&lt; &quot;ld levels...&quot; &lt;&lt; endl;
				i = len - TRACE_TAIL;
			}
		}
	}
}
</pre>
<p>The last part is the function that loads and executes a Ruby script (<code>execute_ruby</code>) and the main function. It registers output hook functions in the Ruby interpreter by <code>rb_define_singleton_method</code> and <code>rb_define_global_function</code>.</p>
<pre class="brush: cpp;">
void execute_ruby(const char* pScriptName)
{
	int state = 0;

	extern VALUE rb_defout;

	typedef VALUE (*rubyfunc)(...);

	rb_defout = rb_obj_alloc(rb_cObject);
	rb_define_singleton_method(rb_defout, &quot;write&quot;, (rubyfunc)ruby_write_hook, 1);
	rb_define_global_function(&quot;p&quot;, (rubyfunc)ruby_p_hook, -1);

	// Always need a full path
	rb_load_protect(rb_str_new2(pScriptName), 0, &amp;amp;state);
	if (state)
	{
		switch (state)
		{
		case 0x1: // TAG_RETURN
			cout &lt;&lt; &quot;unexpected return&quot; &lt;&lt; endl;
			show_error_pos();
			break;
		case 0x2: // TAG_BREAK
			cout &lt;&lt; &quot;unexpected break&quot; &lt;&lt; endl;
			show_error_pos();
			break;
		case 0x3: // TAG_NEXT
			cout &lt;&lt; &quot;unexpected next&quot; &lt;&lt; endl;
			show_error_pos();
			break;
		case 0x4: // TAG_RETRY
			cout &lt;&lt; &quot;retry outside of rescue clause&quot; &lt;&lt; endl;
			show_error_pos();
			break;
		case 0x5: // TAG_REDO
			cout &lt;&lt; &quot;unexpected redo&quot; &lt;&lt; endl;
			show_error_pos();
			break;
		case 0x6: // TAG_RAISE
		case 0x8: // TAG_FATAL
			show_exception_info();
			break;
		default:
			cout &lt;&lt; &quot;unknown longjmp status &quot; &lt;&lt; state &lt;&lt; endl;
			break;
		}
	}

	rb_gc();
}

int _tmain(int argc, _TCHAR* argv[])
{
	init_ruby();

	execute_ruby(&quot;test.rb&quot;);

	ruby_finalize();

	return 0;
}
</pre>
<p>The <code>rb_load_protect</code> function is the protected version of the <code>rb_load</code> function and it doesn&#8217;t cause a segmentation fault even when loading  fails. Unless there&#8217;s a special reason you should always use the  protected version of Ruby functions. After evaluation of a script is  done, it shows error information if any. <code>rb_gc</code> is called at the end to invoke the garbage collector.</p>
<p>When this program is executed it loads the &#8220;test.rb&#8221; Ruby  script and executes it. I tested it with a simple script that does print  some strings and apparently it could output characters through the  redefined hook functions. I thought that my experiment was a success and  I&#8217;d move onto writing the real code for embedding Ruby. But I did one  more test. It was to feed the Ruby downloader script which I described  above to this test code. To my surprise, it halted with the &#8220;memory  error&#8221; in the Ruby interpreter. I have no idea what&#8217;s wrong, but  apparently the culprit was the <code>net/http</code> library. As the library path is in the <code>$LOAD_PATH</code> variable it&#8217;s not a loading error. At this point I had no idea what  happned and wanted to debug it, but the Ruby library is compiled without  debug symbols as explained at the beginning of this section. I was  really disappointed in this result as I thought the experiment was  almost a success. Eventually I abandoned the idea of embedding Ruby into  my application. It is known that the Ruby author himself admits that  the weakness of the current Ruby implementation is in embedding because  it can&#8217;t have multiple interpreters side by side, but I didn&#8217;t know  there was such a basic problem.  It&#8217;s expected the next version of the  Ruby VM addresses some of the issues in embedding, but apparently it&#8217;s  far off in 2008. There may be some workaround to this if I look more  carefully into the <a href="http://www.modruby.net/" target="_blank">mod_ruby</a> source code, but I lost my energy for further research for the time  being. If you know a solution please email me, thanks in advance.</p>
<p>The last hope is naturally on Perl. First you need the source code of Perl. I used a Perl  development snapshot available at <a href="http://mirrors.develooper.com/perl/APC/perl-current-snap/" target="_blank">here</a> but snapshots of the development trunk in this directory often fail to  build or pass the test. After looking at the latest version number of  the stable release  at <a href="http://www.perl.com/download.csp" target="_blank">Perl.com </a>, you may pick up the latest stable snapshot in <a href="http://mirrors.develooper.com/perl/APC/" target="_blank">another directory</a>. You can find exactly which snapshot is broken at the <a href="http://www.nntp.perl.org/group/perl.perl5.porters/">perl.perl5.porters</a> newsgroup.</p>
<p>To build it on Windows, edit the <code>Makefile</code> in the  win32 direcrtory (in most cases just editing the install path and the  compiler selection are enough). Then open a VC++/Platform SDK build  environment console and type <code>nmake</code> to stard the building process. After it&#8217;s done <code>nmake test </code>to test it and type <code>nmake install</code> to build and install all Perl libraries and the Perl interpreter into the install path you specified in the <code>Makefile</code>.</p>
<p>The overall information on how to embed a Perl interpreter into your C/C++ application is available in the <a href="http://perldoc.perl.org/perlembed.html" target="_blank">perlembed</a> document. However, I recommend you to read <a href="http://perldoc.perl.org/perlguts.html" target="_blank">perlgut</a> and <a href="http://perldoc.perl.org/perlcall.html" target="_blank">perlcall</a> before going to <a href="http://perldoc.perl.org/perlembed.html" target="_blank">perlembed</a>. Also <a href="http://perldoc.perl.org/perlapi.html" target="_blank">perlapi</a> may be useful for a reference. <a href="http://perldoc.perl.org/perlguts.html" target="_blank">perlgut</a> explains how the C implementation of Perl represents Perl&#8217;s data types and subroutines in C. <a href="http://perldoc.perl.org/perlcall.html" target="_blank">perlcall</a> explains how to call Perl subroutines from C. If you still have  questions after reading these documents, you can always grep the Perl  source code for functions, function macros and their notes.</p>
<p>For an actual embedding code sample, <a href="http://perl.apache.org/" target="_blank">mod_perl</a> is probably the most complete. If you read <code>mod_perl.c</code> in the src\modules\perl directory of mod_perl 2, you see the <code>modperl_response_handler_cgi</code> function is the main functon that executes a Perl CGI script. The basic flow of this function is</p>
<p>1. Setup environmental variables to pass to Perl<br />
2. Override Perl&#8217;s standard input and standard output<br />
3. Process an HTTP request to a Perl CGI<br />
4. Restore everything back</p>
<p>Basically it&#8217;s in line with what I wrote as what I wanted to do  earlier in this article. Apache does most of these things by directly  manipulating Perl by a C code. But I don&#8217;t need as much security as the  Apache module does. Right now the <a href="../../dice/">DICE</a> is a server to host its owner&#8217;s code and nothing else and don&#8217;t have to  watch naughty users. Also I don&#8217;t intend to run multiple interpreters  for now as the context switching method by <code>PERL_SET_CONTEXT</code> in the current Perl implementation sucks. It&#8217;s based on heavy use of  macro and it forces me to use some nasty macro hacks. Even with a single  interpreter, it&#8217;s not a fun to integrate a C++ code that has a Perl  interpreter in a larger project. The Perl source code predetermines the  name of the interpreter object as &#8220;my_perl&#8221; and predefined macros can&#8217;t  take other names, so you have to assign your  a Perl interpreter pointer  to a local variable <code>PerlInterpreter* my_perl </code>everytime you  want to do something with it. Also the perl header file redefines many  symbols for its own API, which means if your C++ class has a member  function named <code>write</code> it conflicts with Perl. So my advice is, don&#8217;t include <code>perl.h</code> in a header file that is included by many other files in your project.  Include it at the beginning of a .cpp source code file and put all  Perl-related things in it. But how can you define a class that has a  pointer to a Perl interpreter as its property? You can put a <code>char*</code> or something equivalent in the place of the pointer to an interpreter in your class declaration and cast it to <code>PerlInterpreter*</code> everytime you use it in the .cpp source file.</p>
<p>I decided to offload most of the sandbox work to Perl to keep the C++ part simple. The basic design is like this:</p>
<p>1. The Perl interpreter in the <a href="../../dice/">DICE</a> loads a setup Perl script which does <code>STDIN</code>/<code>STDOUT</code> hooks and has other necessary utilities for  sandboxing a Perl script<br />
2. Whenever a CGI script is called, if it&#8217;s the first time the  Perl interpreter evaluates it as a subroutine in a unique package by  using <code>eval</code> in Perl and caches the compiled code. If it is an already compiled code in the cache, it just returns a reference to it.<br />
3. The Perl interpreter executes the compiled Perl code by  dereferencing the subroutine reference and the C++ part gets the output  by reading a Perl variable that has all Perl output. The C++ part is  protected and synchronized by a critical section and you don&#8217;t have to  worry about this Perl variable is destroyed by others (though there&#8217;s  certain performance penalty)</p>
<p>Let&#8217;s take a look at the Perl setup script (<code>EmbeddedPerlSandbox.pl</code>). This script is the one actually used in the <a href="../../dice/">DICE</a> but  a newer version may be included in actual package, to see how it works you should download the <a href="../../dice/">DICE</a> and run its web server by yourself. First, it defines the package <code>EmbeddedPerlSandboxOut</code> that is used to intercept and overrides the standard out (<code>STDOUT</code> in Perl). The required Perl version is specified as 5.8.0, but I&#8217;m not  sure if this script works for a lower version of Perl or not.</p>
<pre class="brush: cpp;">
# EmbeddedPerlSandbox for DICE
# (c) RyuK 2006 All Rights Reserved
#
# klassphere[at.mark]gmail.com
# http://aiueo.da.ru/
# http://zzz.zggg.com/
#
# This file is not redistributable.
#

use 5.8.0;

package EmbeddedPerlSandboxOut;

sub TIEHANDLE
{
	my $classname = shift;
	my $buffer = &quot;&quot;;
	bless \$buffer, $classname;
}

sub PRINT
{
	my $buffer = shift;
	my $s = shift;
	$$buffer .= $s;
}

sub PRINTF
{
	my $buffer = shift;
	my $s = shift;
	$$buffer .= sprintf($s, @_);
}

sub WRITE
{
	my ($buffer, $data, $len, $offset) = @_;

	if (!defined($len))
	{
		$len = length($data);
	}

	if (!defined($offset))
	{
		$offset = 0;
	}

	$$buffer .= substr($data, $offset, $len);
}

sub READLINE
{
	my $buffer = shift;
	return $$buffer;
}

sub BINMODE
{
	# does nothing
	return 1;
}

sub CLOSE
{
	my $buffer = shift;
	undef $buffer;
}

sub DESTROY
{
	my $buffer = shift;
	undef $buffer;
}
	  </pre>
<p>When a Perl script is executed, this class is associated with <code>STDOUT</code> by the <code>tie</code> Perl function which calls the <code>TIEHANDLE</code> subroutine. It creates an object which is just a scalar value that  works as a buffer to hold the content of the standard output. After  that, if <code>print</code> tries to write something onto <code>STDOUT</code> the <code>PRINT</code> function in this class is called. <code>READLINE</code> is called by <code>&lt;&gt;</code>.</p>
<p>The next section defines <code>EmbeddedPerlSandboxIn</code> to override the standard input in the same way.</p>
<pre class="brush: cpp;">
package EmbeddedPerlSandboxIn;

sub TIEHANDLE
{
	my $classname = shift;
	my $buffer = shift;
	bless \$buffer, $classname;
}

sub READLINE
{
	my $buffer = shift;

	# substr EXPR,OFFSET,LENGTH,REPLACEMENT
	return (length($$buffer) ? substr(
		$$buffer,
		0,
		(defined($/) ? index($$buffer, &quot;$/&quot;): length($$buffer) - 1) + 1,
		&quot;&quot;
	)
		: undef);
}

sub READ
{
	my ($buffer, $len, $offset) = ($_[0], $_[2], $_[3]);

	if (!defined($offset))
	{
		$offset = 0;
	}

	if ($len &gt; length($$buffer))
	{
		$len = length($$buffer);
	}

	# You can use the substr() function as an lvalue
	substr($_[1], $offset, $len) = substr($$buffer, 0, $len, &quot;&quot;);

	return $len;
}

sub GETC
{
	my $buffer = shift;
	return (length($$buffer) ? substr(
		$$buffer,
		0,
		1,
		&quot;&quot;
	)
		: undef);
}

sub BINMODE
{
	# does nothing
	return 1;
}

sub CLOSE
{
	my $buffer = shift;
	undef $buffer;
}

sub DESTROY
{
	my $buffer = shift;
	undef $buffer;
}
</pre>
<p>The <code>TIEHANDLE</code> function receives a scalar value as the content of the standard input when it&#8217;s associated with <code>STDIN</code> with <code>tie</code> and holds it. For <code>READ</code>, the second parameter (<code>$_[1]</code>)  is the reference (not a &#8220;reference&#8221; in the Perl, but in the broader  computer science terminology) to the receiving variable as Perl  subroutines are call-by-reference.</p>
<p>From here on the main class EmbeddedPerlSandbox is defined.</p>
<pre class="brush: cpp;">
package EmbeddedPerlSandbox;

$SANDBOX_OUTPUT = &quot;&quot;;

%CODE_STORE = {};

BEGIN
{
	push @INC, Win32::GetCwd() . &quot;\\perl_lib&quot;;

	*CORE::GLOBAL::exit = \&amp;amp;EmbeddedPerlSandbox::exit;
	*CORE::GLOBAL::flock = \&amp;amp;EmbeddedPerlSandbox::flock;
}
</pre>
<p><code>$sandbox_output</code> is the global package variable  that holds the standard output. The C++ part retrieves the output of a  Perl user script by referencing this variable later. <code>%CODE_STORE</code> is a cache that holds compiled Perl scripts with its unique package name as a key. The <code>BEGIN</code> block evaluated when this setup script is loaded by the C++ code. <code>@INC</code> is the array of library paths it adds the &#8220;perl_lib&#8221; directory under  the current directory to it. To obtain the current directory it uses <code>Win32::GetCwd</code> which is a function in a builtin class in the Windows version of Perl without loading the <code>Cwd</code> module. Also it overrides the <code>exit</code> and the <code>flock</code> builtin functions by assigning subroutine references to their type globs. The <code>exit</code> function ends the Perl interpreter by calling <code>exit()</code> in C, which means not only the Perl interpreter but also the entire  application just ends if it&#8217;s called. Such a situation must be avoided  for an obvious reason. The <code>flock</code> function should work in  theory even in the Windows version, but when I tested it it stopped  execution there by an unknown reason. As I explained already the C++  section is synchronized by a critical section, therefore the Perl  section is synchronized too. I decided to trap <code>flock</code> and substitute it with a functon that does nothing.</p>
<p>The next subroutine is necessary to make a user script behave  like a Perl CGI. If a persistent interpreter just executes the same  compiled code 2 times, all global variables persist in the second  execution. For example,</p>
<pre class="brush: cpp;">
if (!defined($var))
{
	$var = 1;
}

print &quot;$var\n&quot;;

$var++;
</pre>
<p>if  this code is executed repeatedly  in a persistent  interpreter the displayed number continues to increase. This behavior is  OK for a Java servlet and other persistent web applications, but not  desirable for emulating a Perl CGI. To stop it, it has to clear global  package variables previously defined in the package before executing a  user code. This <code>cleanupSymbolTable</code> subroutine does it by  scanning the symbol table hash (also called &#8220;stash&#8221; in the Perl jargon)  of the user code package. In the Perl language, the reflection system is  very easily available in the form of the symbol table and it&#8217;s almost  too exposed if you ask me. Though I don&#8217;t like it as it can be a source  of nasty Perl hacks, the reflection itself is certainly a necessary  feature when creating a plugin system or a self-contained virtual world  (Matrix, anyone?).</p>
<pre class="brush: cpp;">
sub cleanupSymbolTable
{
	my $id = shift;

	while (my ($name, $glob_entry) = each %{$id . &quot;::&quot;})
	{
		local *v = $glob_entry;
		if (defined($v))
		{
			undef $v;
		}

		if (defined(@v))
		{
			undef @v;
		}

		if (defined(%v))
		{
			undef %v;
		}
	}
}
</pre>
<p><code>$id</code> is the name of the user code package. It  enumerates all symbols in that package and receives a typeglob for its  symbol (See the &#8220;Symbol Tables&#8221; in <a href="http://perldoc.perl.org/perlmod.html" target="_blank">perlmod</a>).  Then if there is a variable (scalar/array/hash) defined within that  typeglob it&#8217;s undefined. Note that it tests all value types as there may  be a typeglob that has variables of  all the 3 types defined.</p>
<p>The next subroutine compiles a user code as the name suggests.</p>
<pre class="brush: cpp;">
sub compile
{
	my ($id, $content, $current_dir) = @_;

	if (!length($content))
	{
		return $CODE_STORE{$id};
	}

	local $SIG{__WARN__} = \&amp;amp;warn;

	# the part undefining global variables must come before $content,
	# otherwise $content can't contain 'use strict'
	my $sandbox =&lt;&lt; &quot;SBOX&quot;;
package $id;
sub __wrapper
{
	EmbeddedPerlSandbox::cleanupSymbolTable(&quot;$id&quot;);
	\@ARGV = \@_;
	$content ;
}
SBOX

	my @tmp = @INC;
	push @INC, $current_dir;
	eval $sandbox;
	@INC = @tmp;

	if ($@)
	{
		my $e = $@;
		if ($e !~ /via package/ &amp;amp;&amp;amp; $e =~ /line (\d+)/)
		{
			my $n = $1;
			my $m = $n - 6;
			$e =~ s/line $n/line $m/g;
		}

		$e =~ s/\(eval \d+\) //g;

		$SANDBOX_OUTPUT = &quot;Content-Type: text/plain\n\nPerl Compilation Error: &quot; . $e . &quot;\n&quot;;
	}

	return $CODE_STORE{$id} = *{$id . &quot;::__wrapper&quot;}{CODE}; # CODE reference - see perlsub
}
</pre>
<p><code>$id</code> is the unique package name for this script. It  has to be a unique name not to conflict with other scripts compiled  later in this interpreter instance. <code>$content</code> is the actual  Perl CGI script content. If it&#8217;s empty it just returns the already  compiled code for this unique package name. In the actual implementation  in the <a href="../../dice/">DICE</a> the C++ part checks  the file time of a script to see if it has to be reloaded or not, but  you can implement it in Perl too if you want. Though it may not be  necessary the <code>__WARN__</code> signal is trapped with the <code>warn</code> subroutine to suppress a potential  interrupt. The <code>$sandbox</code> here document is the code that wraps the usercode into a unique package  that has only a wrapper subroutine code in it. The user code is  embedded in this wrapper function and placed after the symbol table  cleaner function that is explained above. In this &#8220;here document&#8221; you  have to add &#8220;<code>\</code>&#8221; to a variable you don&#8217;t want to be evaluated at the time of the definition of <code>$sandbox</code>. <code>$current_dir</code> has the current directory for this script. Since the <code>use</code> function in Perl is evaluated in the compile time you have to add the current directory particular to this script to <code>@INC</code> here and restores after the evaluation of the user code by <code>eval</code>. If the script has an error, <code>$@</code> has the error information. When the evaluation is done, the reference  to the compiled code can be obtained by getting the &#8220;CODE&#8221; entry in the  subroutine&#8217;s typeglob.</p>
<p>These subroutines are just utility functions. <code>clearCodeStore</code> is a function that clears the code store as the name suggests. When you  run a persistent interpreter you can call this function to reclaim  unused resources. <code>addRuntimeError</code> is an error reporting function in executing a user code.</p>
<pre class="brush: cpp;">
sub clearCodeStore
{
	%CODE_STORE = ();
}

sub addRuntimeError
{
	my $e = shift;
	if (length($SANDBOX_OUTPUT))
	{
		if ($SANDBOX_OUTPUT !~ /Perl Runtime Error/)
		{
			$SANDBOX_OUTPUT .= &quot;Perl Runtime Error: &quot; . $e . &quot;\n&quot;;
		}
	}
	else
	{
		$SANDBOX_OUTPUT = &quot;Content-Type: text/plain\n\nPerl Runtime Error: &quot; . $e . &quot;\n&quot;;
	}
}
	  </pre>
<p>The <code>execute</code> subroutine executes a compiled user code.</p>
<pre class="brush: cpp;">
sub execute
{
	my ($id, $compiled, $stdin, $env, $current_dir, $clear_codestore) = @_;

	local $SIG{__WARN__} = \&amp;amp;warn;

	if (!chdir($current_dir))
	{
		addRuntimeError(&quot;chdir: &quot; . $current_dir);
		return;
	}

	my $oh = tie(*STDOUT, &quot;EmbeddedPerlSandboxOut&quot;);
	my $ih = tie(*STDIN, &quot;EmbeddedPerlSandboxIn&quot;, $stdin);

	my %env_vars = ();
	foreach my $kv (split(/\n/, $env))
	{
		if ($kv =~ /([^=]+)=(.+)/)
		{
			$env_vars{$1} = $2;
		}
	}

	%main::ENV = %env_vars;

	my @tmp = @INC;
	push @INC, $current_dir;
	eval {$compiled-&gt;();}; # you need this semicolon...
	@INC = @tmp;

	if ($@)
	{
		unless ($@ =~ /EmbeddedPerlSandbox::exit/)
		{
			my $e = $@;
			if ($e !~ /via package/ &amp;amp;&amp;amp; $e =~ /line (\d+)/)
			{
				my $n = $1;
				my $m = $n - 6;
				$e =~ s/line $n/line $m/g;
			}

			$e =~ s/\(eval \d+\) //g;

			addRuntimeError($e);
		}
	}

	undef $ih;
	untie *STDIN;

	if (!$@)
	{
		$SANDBOX_OUTPUT = &lt;STDOUT&gt;;
	}

	undef $oh;
	untie *STDOUT;

	if ($clear_codestore)
	{
		clearCodeStore();
	}
}
	  </pre>
<p>First it changes the current directory to the directory of the user script by calling <code>chdir</code>.  I tried to move the current directory in the C++ side by the <code>SetCurrentDirectory</code> Windows API and the <code>PerlDir_chdir</code> Perl C API, however for some reason the Perl side was not affected by  them. Then it overrides the standard input and the standard output by  using <code>tie</code> and the <code>EmbeddedPerlSandboxIn</code> and <code>EmbeddedPerlSandboxOut</code> classes  as explained already. Unfortunately it seems not to be able to override the implicit <code>STDIN</code> expression in <code>&lt;&gt;</code> so it&#8217;s required to specify  in a user script. <code>$stdin</code> passes the content of the standard input which is the input by a web user in the case of a web server. <code>$env</code> has the content of environmental variables for CGI. As it&#8217;s passed in a  scalar value delimited by &#8216;\n&#8217; and &#8216;=&#8217; it&#8217;s parsed and stored in the <code>%main::ENV</code> hash to which a CGI code refers. <code>$compiled</code> is the code reference and it&#8217;s invoked by <code>$compiled-&gt;()</code> subroutine expression in the <code>eval</code> block. After it&#8217;s done the content of the standard output is saved in <code>$SANDBOX_OUTPUT</code> then the standard input and the standard output are restored.</p>
<p>The last section is for the hook functions for these dangerous global functions.</p>
<pre class="brush: cpp;">
sub exit
{
	$SANDBOX_OUTPUT = &lt;STDOUT&gt;;
	die(&quot;EmbeddedPerlSandbox::exit&quot;);
}

sub flock
{
	# does nothing
	return 1;
}

sub warn
{
	die(&quot;Perl Warning: &quot; . $_[0] . &quot;\n&quot;);
}

1;
</pre>
<p>The <code>exit</code> function  saves the content of the standard output in <code>$SANDBOX_OUTPUT</code> and stops execution by the <code>die</code> function. As the <code>die</code> function can be trapped by <code>eval</code>, the <code>exit</code> function can be emulated this way. <code>flock</code> does nothing and warn is almost the same with <code>exit</code>. A Perl module has to return 1 at the end.</p>
<p>That&#8217;s all for the Perl side (<code>EmbeddedPerlSandbox.pl</code>).  Let&#8217;s check out the C++ sample code that drives a Perl interpreter. To  build it you have to add the Perl source code directory to the header  include directories of your project and have to add the <code>perl59.lib</code> static library to your linker input. The resultant executable requires <code>perl59.dll</code> to run.</p>
<pre class="brush: cpp;">
#include &lt;string&gt;
#include &lt;cstdio&gt;

using namespace std;

#include &lt;EXTERN.h&gt;
#include &lt;perl.h&gt;

EXTERN_C void xs_init (pTHX);
EXTERN_C void boot_DynaLoader (pTHX_ CV* cv);

EXTERN_C void xs_init(pTHX)
{
	char *file = __FILE__;
	dXSUB_SYS;

	/* DynaLoader is a special case */
	newXS(&quot;DynaLoader::boot_DynaLoader&quot;, boot_DynaLoader, file);
}
	  </pre>
<p>The <code>xs_init</code> glue code is just what you get by the <code>ExtUtils::Embed</code> tool (See &#8220;Using Perl modules, which themselves use C libraries, from your C program&#8221; in <a href="http://perldoc.perl.org/perlembed.html" target="_blank">perlembed</a>).</p>
<pre class="brush: cpp;">
int _tmain(int argc, _TCHAR* argv[])
{
	int argc_perl = 0;
	char* embedding[] = {&quot;&quot;, &quot;E:\\program\\src\\perlembedtest2\\EmbeddedPerlSandbox.pl&quot;};

	PERL_SYS_INIT(&amp;amp;argc_perl, (char***)embedding);

	PerlInterpreter* my_perl = perl_alloc();
	perl_construct(my_perl);

	PL_exit_flags |= PERL_EXIT_DESTRUCT_END;

	perl_parse(my_perl, xs_init, 2, embedding, NULL);
	perl_run(my_perl);
</pre>
<p>The <code>embedding</code> char pointer array is the argument list passed to the Perl interpreter. In the second parameter it set the path of the <code>EmbeddedPerlSandbox.pl</code>. Then it creates a Perl interpreter and runs it.</p>
<p>The next part calls the <code>EmbeddedPerlSandbox::compile</code> subroutine in the Perl side to compile a Perl script. The unique package name for this script is in <code>id</code> and the script content is in <code>content</code> for the sake of this sample code.</p>
<pre class="brush: cpp;">
	const char* id = &quot;test&quot;;
	string content(
		&quot;my $data = ''; read(STDIN, $data, 2); print \&quot;$data\n\&quot;; print getc(STDIN) . \&quot;\n\&quot;;&quot;
		&quot;while (&lt;STDIN&gt;) {print ($i++ . ': ' . $_);}&quot;
		&quot;open FH, '&gt;log.txt';print FH 'test'; close(FH);&quot;
	);

	dSP;

	ENTER;
	SAVETMPS;
	PUSHMARK(SP);

	XPUSHs(sv_2mortal(newSVpv(id, 0)));
	// don't use 0 as the second argument since Perl uses strlen for 0
	XPUSHs(sv_2mortal(newSVpv(content.c_str(), content.size())));

	PUTBACK;

	call_pv(&quot;EmbeddedPerlSandbox::compile&quot;, G_EVAL);

	SPAGAIN;

	SV* sandbox = 0;
	if (SvTRUE(ERRSV))
	{
		POPs; // see perlcall for G_EVAL

		printf(&quot;ERRSV\n&quot;);
		printf(SvPVX(ERRSV));
		return 0;
	}
	else
	{
		sandbox = newSVsv(POPs);
	}

	PUTBACK;
	FREETMPS;
	LEAVE;
	  </pre>
<p>It pushs two strings <code>id</code> and <code>content</code> in a locally copied Perl stack. The <code>newSVpv</code> function creates a new Perl scalar  value for a string (pv: pointer value). If you set 0 for the second parameter it uses C <code>strlen()</code> to calculate the length of the string pointer by a <code>char*</code> pointer. Since a Perl script can contain a null character (though  unlikely) this sample gives the length of the script explicitly by using  C++ <code>basic_string</code> for <code>content</code>. When the parameters are ready, it puts the stack back to Perl by the <code>PUTBACK</code> macro and  calls the subroutine by <code>call_pv</code>. See the details for Perl stack manipulation in <a href="http://perldoc.perl.org/perlcall.html" target="_blank">perlcall</a>. If <code>call_pv</code> is successful it pops the return value from the stack and assigns it to a new Perl scalar value <code>sandbox</code> that holds a compiled Perl code reference.</p>
<p>In this section it actually execute the compiled Perl code. <code>in</code> is a C++ <code>basic_string</code> object that has an emulated standard input. <code>env</code> is supposed to have environmental variables, but this sample code omits it. <code>new_dir</code> is the current directory for this Perl code. As the sample Perl script  fiddles with a file, it is created in this directory. Though the <code>EmbeddedPerlSandbox::execute</code> in Perl takes 6 parameters, the 6th parameter is omitted again for this sample. It calls <code>EmbeddedPerlSandbox::execute</code> with 5 arguments with no return value expected (<code>G_VOID</code>).</p>
<pre class="brush: cpp;">
	string in(&quot;abcHello world\nI think\nTherefore I am\n&quot;);

	string env;

	string new_dir(&quot;E:\\program\\src\\perlembedtest2\\Debug&quot;);

	ENTER;
	SAVETMPS;
	PUSHMARK(SP);

	XPUSHs(sv_2mortal(newSVpv(id, 0)));
	XPUSHs(sandbox);
	XPUSHs(sv_2mortal(newSVpv(in.c_str(), in.size())));
	XPUSHs(sv_2mortal(newSVpv(env.c_str(), env.size())));
	XPUSHs(sv_2mortal(newSVpv(new_dir.c_str(), new_dir.size())));

	PUTBACK;

	call_pv(&quot;EmbeddedPerlSandbox::execute&quot;, G_VOID);

	SPAGAIN;

	STRLEN n_a;
	const char* output = SvPV(get_sv(&quot;EmbeddedPerlSandbox::SANDBOX_OUTPUT&quot;, FALSE), n_a);
	int len = (int)n_a;
	printf(&quot;returned: %d bytes\n&quot;, len);

	// it can contain a null character
	for (int i = 0; i &lt; len; ++i)
	{
		if (i == 0)
			printf(&quot;[%c&quot;, output[i]);
		else
			printf(&quot;%c&quot;, output[i]);
	}
	if (len != 0)
		printf(&quot;]\n&quot;);

	PUTBACK;
	FREETMPS;
	LEAVE;
	  	  </pre>
<p>After <code>EmbeddedPerlSandbox::execute</code> is executed, it retrieves the Perl scalar value of <code>EmbeddedPerlSandbox::SANDBOX_OUTPUT</code> by calling <code>get_sv</code> and converting it into a <code>char*</code> by <code>SvPV</code>. <code>output</code> is the string that holds the standard output of the Perl script.</p>
<pre class="brush: cpp;">
	PL_perl_destruct_level = 0;

	perl_destruct(my_perl);
	perl_free(my_perl);
	PERL_SYS_TERM();

	return 0;
}
</pre>
<p>When the work is done it frees the Perl interpreter. It&#8217;s the  end of the C++ sample code. This works flawlessly including using Perl  modules, and I implemented it in the <a href="../../dice/">DICE</a> with some additional attention for a persistent interpreter.</p>
<p>The winner for this round is Perl. But it&#8217;s not free from a  problem, for example its header file redefines common API names with C  macros and are not include-friendly. Also it&#8217;s not designed to host  multiple interpreters from the beginning and plagued with excessive C  macro use. I hope these issues can be solved in <a href="http://dev.perl.org/perl6/" target="_blank">Perl 6</a> with a new VM (<a href="http://www.parrotcode.org/" target="_blank">Parrot</a>), just like Ruby&#8217;s new VM (<a href="http://www.atdot.net/yarv/" target="_blank">YARV</a>) plans. If <a href="http://dev.perl.org/perl6/" target="_blank">Perl 6</a> becomes the mainstream this article has to be rewritten since the  embedding method should be fairly different from the current one, though  I somehow doubt the likelihood that Perl 6 becomes too popular. One  thing for sure is I&#8217;m satisfied with the relative robustness of Perl for  now.</p>
]]></content:encoded>
			<wfw:commentRss>http://zzz.zggg.com/2006/11/15/perl-ruby-multithreading-embedding/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>サーバプッシュの現在 &#8211; AJAXの辺縁</title>
		<link>http://zzz.zggg.com/2006/11/12/server-push-today-marginal-ajax/</link>
		<comments>http://zzz.zggg.com/2006/11/12/server-push-today-marginal-ajax/#comments</comments>
		<pubDate>Sat, 11 Nov 2006 23:17:11 +0000</pubDate>
		<dc:creator>RyuK</dc:creator>
				<category><![CDATA[code]]></category>

		<guid isPermaLink="false">http://zzz.zggg.com/?p=151</guid>
		<description><![CDATA[プッシュテクノロジと呼ばれる、web通信をデータ放送に見立てた一連のWWW技術が1990年代後半に注目され、webブラウザによる実装も試み られたことがあった。通常のHTTP接続はwebブラウザがリクエストをサーバに送り [...]]]></description>
			<content:encoded><![CDATA[<p>プッシュテクノロジと呼ばれる、web通信をデータ放送に見立てた一連のWWW技術が1990年代後半に注目され、webブラウザによる実装も試み られたことがあった。通常のHTTP接続はwebブラウザがリクエストをサーバに送りつけるとレスポンスを1つしか返さないのに対し、Content- type:  multipart/x-mixed-replaceを利用したHTTP上でのサーバプッシュでは、クライアントのリクエスト無しで第二第三のメッセージ が連続してサーバから送られてくるように見える。これを利用するとユーザの介入無く動的なページの表示が可能だった。ところがこの方法はNetscape ブラウザしか対応しておらず、Internet  Explorerの台頭と共に完全に死滅することになった。webのデファクトスタンダードを握るMicrosoftの影響力はやはり強く、何年もアップ デートされないIEがwebの進化を停滞させているという批判の一方で、画面遷移無しでwebページの内容を更新する手法としてのAJAXの基盤である XMLHTTPRequestはすんなりと世間に受け入れられた(ただしGoogleがその成果を非Microsoft化してしまった)。しかし、 XMLHTTPRequestはクライアントがリクエストを一々発行するクライアントプル技術であり、サーバプッシュの真の代用にはなり得ない。</p>
<p>そもそもHTMLやHTTPというwebの標準がここまで貧しい設計でなければ特定ブラウザで動作するしないといった些細な事柄につい て無意味な議論を重ねる必要は生じなかったはずだ。HTMLベース技術の大半がプラットフォーム間の互換性の維持に労力を費やしている状況はある観点から 見れば極めて寒々しい。例えばblogのデファクト標準であるトラックバックという設備は技術的なメリットではなくそれが標準として流布しているという政 治的状況によって生かされているのである。その現状を一旦忘れ、例えば全てのwebページが別個のソフトウェアアプリケーションだったらどうだろうか。 HTMLがチューリング完全なプログラミング言語で、全てのwebページが自己が利用できるサーバまたはクライアント上の資源を理解しつつ位置透過的・自 律的にサービスを提供していたら?<br />
<span id="more-151"></span><br />
マイクロソフトのActiveX(現在は.NET)やSunのJavaはネットワーク化されたソフトウェアコンポーネントの世界の端緒 を開くことを期待された技術だった。ところが様々な政治的要因によりそれらの技術がwebを席巻することは最早あり得ない状況となっている。ブラウザの備 えるソフトウェアスタックのサポートはECMAScriptとW3C  DOM/CSSのサポートに留まり、webページそのものはXMLに見られるようにますますデータ化の傾向を強めるかに見える。その傾向と一線を画するの がActiveXコンテナに収まってIE上で動作するAdobe Flashである。 2001年にリリースされたWindows  XPをインストールして最初に表示されるプロダクトツアーはMacromedia  Flashで駆動されていた。Flashオブジェクトを作成するためのActionScriptは現在3.0までバージョンアップし、Java並のプログ ラマビリティを備える。実装のパフォーマンスもJavaアプレットと比較して高く、webアプリケーションのユーザビリティを阻害するストレスがほとんど ない。セキュリティもこれまで見る限り申し分ない(ActiveXコンポーネントと違ってデコンパイルが容易な点を除けば)。Flex 2  SDKは無料化され、誰でも無料でFlashアプリケーションを開発できる。先日のMozilla FoundationへのActionScript  VM寄贈も記憶に新しい。その上サーバサイドActionScriptがMacromedia製品以外にも拡がることにでもなれば、Flashがwebの 覇者となる未来も決して遠くないだろう。あるいは現在既にそうなっているかもしれない。</p>
<p>しかしFlashとてwebの基幹技術のHTMLと融合したわけではなく、両者の間の溝は厳然として存在する。webブラウザのプラグ インとして実装されているFlashその他の技術は、HTMLベースの技術と異なり、webブラウザのUI上では異物感を感じさせる。ブラウザ自身の提供 するUIウィジェットの中でプラグインの表示領域のみが浮いて見えることを始め、表示フォントの違い、右クリック時のコンテクストメニューの違い、 Javaアプレットの場合は起動自体の遅さなど、枚挙にいとまがない。そもそもコンテンツホルダーの要求でテクストのコピーが禁止になっているなど、風通 しの悪さも感じさせられることがあるかもしれない。これも過渡期の現象で遠い将来にはFlashが全てのUIを覆い尽くすことになるのだろうか。 Flashがプロプライエタリな技術に留まる限りこの展望は全く荒唐無稽といえる。AJAXの定義をJavaScriptによるwebブラウザのUI操作 全般に拡張する動きに見られるように、ECMAScriptのスタックがツールも含めて十分に成長してくれば、webのUIにダイナミックで柔軟なプログ ラマビリティを与える道具としてのFlashは存在意義を失ってしまう。そして、歴史をひもとけばFlashのプログラミング言語 ActionScript自体がECMAScriptから派生している(ただし2.0でクラスライブラリ階層や強い型付けが導入されたため現在の ActionScriptはプロトタイプベースではない)。つまり、現時点では、Flashを擁するAdobe社が、Microsoftとその他の対立に 乗じていわば「漁夫の利」を得た状況となっているわけだ。そして、この不安定な均衡に満足しないAdobeは既に先手を打ち、将来にわたりFlashの支 配を保証するために、オープンな<a href="http://www.mozilla.org/projects/tamarin/" target="_blank">Tamarin Project</a>に打って出た。</p>
<p>この現状を認識した上で冒頭のサーバプッシュの話に戻ると、かつてのサーバプッシュに現在最も近い技術はweb上のストリーミング映像 サイトで目にすることが出来る。ただし、これらは実際はサーバプッシュではなくクライアントプルでプロトコル上実装されていることもあるのでストリーミン グ即サーバプッシュを意味するわけではない。ここでもプラットフォーム中立性やコンテンツホルダの要請により、QuicktimeやWindows  Media Playerオブジェクトのwebページへの埋め込みではなく、FlashによるFlash  Videoフォーマットのストリーミングが一般的になりつつある。HTTPではない(あるいはHTTPトンネリングされた)プロトコルを喋るストリーミン グサーバとwebブラウザの間をFlashが取り持っている。webブラウザがそれ自身でリッチなアニメーションや動画を処理する標準的方式が存在しない 以上、先に述べたUI上でのFlashの異物感もやむを得ないと言えるし、このストリーミングは基本的にFlash内で完結している。しかし、イメージ/ バイナリベースではない、テクストベースのサーバプッシュについてはどうか。</p>
<p><a href="../../dice/index_j.html">DICE</a>のwebサーバ 部分にIRCチャンネルのwebインターフェイス機能を画面遷移無しで持たせようと考えたとき、真っ先に頭をよぎったのはNetscapeブラウザが持っ ていたサーバプッシュ機能だった。もちろん、Internet Explorerでは対応していないこの方式は使用することはできない。また、<a href="../../dice/index_j.html">DICE</a>は 一つのサーバでIRCインターフェイスとwebインターフェイスの両方を別々のポートから同時に提供するのでJavaアプレットやFlashのセキュリ ティサンドボックスの制限をクリアできるとはいえ、それらプラグインでIRCクライアントのUIを作った場合のUIの異物感、不統一感は避けたい。逆に、 HTML寄りの方法としてAJAXを使う場合、新しいメッセージがチャンネルに存在していないか調べるためにサーバに対し一定時間毎に XMLHTTPRequestで接続を張ってテストするというポーリングを行わなければならない。これはスマートではないし、サーバへの負荷が高い。 AJAXの最悪の使用法だ。</p>
<p>あるいは、サーバプッシュを真似る古典的な手法として、HTTPの接続を切らずに隠しIFRAMEやXMLHTTPRequestによ る接続先にサーバからJavaScriptの文を一つ一つ送らせ、フレームやXMLHttpRequestのプロパティといったローカル受信バッファへの ポーリングを一定時間毎に行うことにより、サーバ自体へのポーリングを回避しつつ動的なページの更新をエミュレートするというものがある。AJAXに飽き 足らないユーザの中にはこの手法をCometあるいはHTTPストリーミングなどと呼んでAJAXのパターンに含めようという動きもあり、例えば<a href="http://www.pushlets.com/" target="_blank">Pushlet</a>は それを系統的にまとめ上げたツールキットである。しかしこの方法はいわゆるハックの部類に属し、ブラウザの読み込み表示が表示されたままになるという問題 の他、クライアント側のタイムアウト時間、バッファリングや同時接続数制限といった仕様に依存する。実装上も、CGIなどのwebアプリケーションとして 長時間スレッドをsleepさせるようなものはパフォーマンスの観点から論外である上に、<a href="../../dice/index_j.html">DICE</a>自体に実装するにも煩雑すぎる。</p>
<p>IRCチャンネルにwebインターフェイスを設ける際の注意点として、上記のようなクライアントやサーバ側の仕様上の問題に加え、IRCチャンネルのセキュリティの問題がある。従来は<a href="../../dice/index_j.html">DICE</a>の webインターフェイスからIRCチャンネルへ発言を投稿するには、サーバがJavaScriptとスタイルシートで文字列(機械的な読み取りが困難なよ うにノイズを加えてある)を描き、投稿者にそれを一々読ませて投稿時に記入させ、HTTPのPOSTで投稿させていた。故意の連続投稿やスパムを未然に防 止するためである。しかしweb掲示板ならともかくリアルタイム性が売りのIRCにこの形式はそぐわない。HTTPストリーミングと組み合わせた場合には 輪をかけて非効率である。当然実装も複雑になる。IRC側ではプロクシサーバのチェックまで行っているにも関わらず、web側のユーザの参加資格には何の 制約もないのも問題だ。また、IRCチャンネル内でwebのユーザをアクセス禁止にすることも容易ではない。</p>
<p>そこで、Flashをソケット通信のバックエンドのみに用いてブラウザをIRCサービスへ直接接続させ、ユーザインターフェイスは JavaScriptによるDOM操作で構築することを考えた。ページに不可視のFlashオブジェクトを埋め込み、FlashはソケットでIRCサービ スのポートへ接続し、通常のIRCクライアント同様に振る舞う。ただしFlashは出来る限りネットワークプロクシの役割に留め、IRCクライアントとし てのプログラミングはJavaScript側で行う。この方法ならばプラットフォーム間の差異を最小に留めつつIRCチャンネルのwebインターフェイス を提供でき、セキュリティや実装上のオーバーヘッドも低い。テクストベースのwebインターフェイスにFlashプラグインを接ぎ木することによるUI上 の拒絶反応も当然無い。サーバにIRCクライアントとして接続する以前に取得する必要のある動的な情報(例えばIRCのポート)のみAJAXで取得すれば よい。UIはスクリプト言語のJavaScriptで制御されているのでFlashと異なりコンパイル無しでサーバ管理者が自由に編集でき、アプリケー ションの配布には都合がよいという利点もある。</p>
<p>早速コードを見てみよう。以下、<a href="../../dice/index_j.html">DICE</a>に 添付しているIRCクライアントwebアプリケーションのAdobe Flash         9向けFlashオブジェクトProxyIRCのActionScript  3.0ソースコードProxyIRC.asを解説していく。最初IRCProxyという名称で作成していたが何故かエラーが発生したのでProxyIRC という名称に変更した。ビルドには、最近無償配布が開始されたコマンドライン開発ツール<a href="http://www.adobe.com/jp/products/flex/sdk/" target="_blank">Adobe         Flex SDK</a>を使用する。 コンパイル時のコマンドラインオプションは <code>mxmlc -default-size 1 1 -default-frame-rate=30         -default-background-color=0xFFFFFF ProxyIRC.as</code> とでもすればよいだろう。</p>
<pre class="brush: cpp;">
package
{
	import flash.display.*;
	import flash.external.*;
	import flash.net.*;
	import flash.events.*;
	import flash.utils.*;

	// class name may conflict with the JavaScript engine for IE
	// when ExternalInterface is in use, be careful to test different names
	public class ProxyIRC extends Sprite
	{
		private var socket:Socket;
		private var receivedMessageBuffer:String;
		private var isJIS:Boolean;

		public function ProxyIRC()
		{
			socket = new Socket();
			configureListeners(socket);
			isJIS = false;

			receivedMessageBuffer = new String();

			// It seems the JS engine for IE has a serious problem with the functionName argument.
			// Don't use an existing JS function name.
			ExternalInterface.addCallback(&quot;testLoopback&quot;, loopbackToJavaScript);
			ExternalInterface.addCallback(&quot;connectIRC&quot;, connect);
			ExternalInterface.addCallback(&quot;sendIRC&quot;, send);
			ExternalInterface.addCallback(&quot;setJIS&quot;, setCharacterEncoding);
			ExternalInterface.addCallback(&quot;quitIRC&quot;, disconnect);
		}
	 </pre>
<p>ActionScript  3.0は前述のようにJavaScript風からJava風に進化したような言語なのでJavaを知っているならばほとんど言語仕様を見なくてもライブラ リリファレンスから必要な道具を拾うだけで直ちにプログラミングを開始できるだろう。型が変数名の後に付くとか、XMLをソースコード内に直接記入できる とかというような程度の違いしかない。このクラス<code>ProxyIRC</code>はFlashのディスプレイリストの最も単純な基本クラス<code>Sprite</code>を継承し、3つのプロパティを保持している。<code>socket</code>は文字通り接続対象に接続するためのTCP/IPソケットであり、<code>receivedMessageBuffer</code>は送られてきたデータを解析するために溜めておくバッファである。<code>isJIS</code>は、 日本語で行われるIRCのエンコーディングは通常JISのため、文字列は全てutf-8で内部的に扱われるJavaScriptとやりとりするときに日本 語向けの相互変換を行うか否かのユーザ設定を保持するためのフラグである。コンストラクタ内での一連の ExternalInterface.addCallbackの呼び出しは、JavaScriptの関数名とFlashのメソッドとの関連付けを登録し、 JavaScriptからFlashの関数名を呼び出せるようにするための準備である。</p>
<pre class="brush: cpp;">
		public function loopbackToJavaScript():void
		{
			ExternalInterface.call(&quot;loopbackFromFlash&quot;);
		}

		public function setCharacterEncoding(jis:Boolean):void
		{
			isJIS = jis;
		}

		public function connect(host:String, port:uint):void
		{
			if (socket.connected)
			{
				socket.writeUTFBytes(&quot;QUIT\n&quot;);
				socket.flush();
				socket.close();
			}

			socket.connect(host, port);
		}

		public function disconnect():void
		{
			if (!socket.connected)
				return;

			socket.writeUTFBytes(&quot;QUIT\n&quot;);
			socket.flush();
			socket.close();
		}
</pre>
<p><code>loopbackToJavaScript</code>はFlashが正常に動作しているかJavaScript側から調べるためのメソッドである。JavaScript側から呼び出されると逆にJavaScript側の<code>loopbackFromFlash</code>関数を実行する。<code>connect</code>は サーバへの接続を実行するための関数だが、IRCクライアントのUIの実装上、接続中には接続開始のボタンが切断ボタンになっているので、接続中に呼び出 されると接続を直ちに切断するようにしてある。この部分はJavaScriptのUIのことを考慮せず分離すべきだったかも知れない。<code>disconnect</code>メソッドは単純にサーバから切断するためのメソッドである。ちなみにIRCはテクストベースのプロトコルで一つ一つのメッセージは&#8217;\n&#8217;で区切られる。QUITは最も単純なメッセージである。プロトコルの詳細はRFC 2812を参照して頂きたい。</p>
<pre class="brush: cpp;">
	  	private function configureListeners(dispatcher:IEventDispatcher):void
		{
			dispatcher.addEventListener(Event.CLOSE, closeHandler);
			dispatcher.addEventListener(Event.CONNECT, connectHandler);
			dispatcher.addEventListener(ProgressEvent.SOCKET_DATA, dataHandler);
			dispatcher.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
			dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler);
			dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
		}

		public function send(utf8string:String):void
		{
			if (isJIS)
			{
				var b:ByteArray = new ByteArray();
				b.writeMultiByte(utf8string + &quot;\n&quot;, &quot;iso-2022-jp&quot;);
				socket.writeBytes(b, b.bytesAvailable);
			}
			else
				socket.writeUTFBytes(utf8string + &quot;\n&quot;);
			socket.flush();
		}

		private function closeHandler(event:Event):void
		{
			ExternalInterface.call(&quot;onClose&quot;);
		}

		private function connectHandler(event:Event):void
		{
			ExternalInterface.call(&quot;onConnect&quot;);
		}
</pre>
<p><code>configureListeners</code>メソッドは、引数で指定されたオブジェクトに起こったイベントを監視するためのイベントリスナをまとめて登録するための初期化メソッドで、既に見たように<code>ProxyIRC</code>のコンストラクタで呼ばれる。引数はソケットオブジェクトなので、ソケットが切断される、ソケットがデータを受信する等のイベントに対してそれぞれの処理メソッドの参照を関連付けている。<code>send</code>はサーバに対して文字列を送信するためのメソッドで、JavaScript側からutf-8で送られてきた文字列を、JISエンコーディングが有効になっている場合は<code>ByteArray</code>クラスの<code>writeMultibyte</code>メソッドを利用してJIS(iso-2022-jp)エンコーディングに変換してから送信する。それが必要ない場合はそのままutf-8文字列として送信している。<code>closeHandler</code>、<code>connectHandler</code>はJavaScript側にソケットの切断・接続を通知するための処理メソッドである。</p>
<pre class="brush: cpp;">
		private function dataHandler(event:ProgressEvent):void
		{
			var buffer:String = new String();
			if (isJIS)
				buffer = socket.readMultiByte(event.bytesLoaded, &quot;iso-2022-jp&quot;);
			else
				buffer = socket.readUTFBytes(event.bytesLoaded);

			var c:String = new String();
			for (var i:uint = 0; i &lt; buffer.length; ++i)
			{
				c = buffer.charAt(i);
				if (c == &quot;\n&quot; || c == &quot;\r&quot;)
				{
					if (receivedMessageBuffer.length != 0)
					{
						if (receivedMessageBuffer.length &gt;= 4
							&amp;amp;&amp;amp; receivedMessageBuffer.substring(0, 4).toUpperCase().localeCompare(&quot;PING&quot;) == 0)
							send(&quot;PONG&quot;);
						else
							ExternalInterface.call(&quot;output&quot;, receivedMessageBuffer);
					}
					receivedMessageBuffer = &quot;&quot;;
				}
				else
					receivedMessageBuffer += c;
			}
		}

		private function progressHandler(event:ProgressEvent):void
		{
			//  trace(&quot;progressHandler loaded:&quot; + event.bytesLoaded + &quot; total: &quot; + event.bytesTotal);
		}

		private function ioErrorHandler(event:IOErrorEvent):void
		{
			ExternalInterface.call(&quot;reportError&quot;, &quot;I/O Error: &quot; + event);
		}

     		private function securityErrorHandler(event:SecurityErrorEvent):void
		{
			ExternalInterface.call(&quot;reportError&quot;, &quot;Security Error: &quot; + event);
		}
	}
}
		 </pre>
<p><code>dataHandler</code>メソッドはやや複雑だが、サーバからデータを受信したときの処理を記述したメソッド である。JISエンコーディングが有効なときはJISとしてソケットからバッファへ読み込み、そうでなければutf-8文字列として読み込む。ループ処理 はバッファから個々のIRCメッセージを切り出すためのもので、メッセージ区切りの改行文字を見つけると一つメッセージを受け取ったと判断して JavaScript側の処理関数<code>output</code>を呼び出している。ただし例外として、<code>PING</code>というIRCメッセージを受け取ると、JavaScript側に通知せず<code>PONG</code>と いうメッセージを送り返す。これはIRCの接続維持メカニズムで、一定時間活動がないと回線が接続されているかどうかのポーリングが行われるというもので ある。JavaScript側へ渡すことも出来るが、無駄なオーバーヘッドとなるのでここではFlash側で適切に処理している。<code>ioErrorHandler</code>はI/Oエラー時の処理メソッド、<code>securityErrorHandler</code>は Flashのセキュリティ違反が起こったときに呼ばれるメソッドである。Javaアプレットと同じく、Flashのセキュリティサンドボックスはソケット の接続をアプレットがホストされているサーバと同じドメインのサーバに制限しているので、それに違反するとソケットにセキュリティエラーが起こる。ただ し、Flashの場合は、接続先外部サーバがルートにXMLのクロスドメインポリシーファイルを持っていればこの制限は回避できるため、外部サーバに接続 するよう指示された場合Flashはまずクロスドメインポリシーファイルを探索し、見つからないとセキュリティエラーを発生させる。</p>
<p>ソケット通信と日本語JISエンコーディング変換を行うバックエンドのFlashコードは以上である。今度は、JavaScriptで構築された ユーザインターフェイスを見ていこう。基本的な動作イメージは、よくあるGUIのIRCクライアントのように、チャンネルの内容を表示するバッファとチャ ンネルのメンバーを表示するコラムがあり、さらに発言を入力したりするための操作パネルが下部にある。さらにチャンネルやその他のタブ画面の切り替えを行 うために、Windowsのタスクバーのようにタブを列挙したバーが操作パネルの下部に付属している。最初から存在する消去できない特殊なタブとして、ヘ ルプファイル表示タブ(help)、生の通信ログを書き出すためのタブ(raw)、IRCサーバーが発したメッセージを表示するためのタブ (status)、チャンネルリスト表示用タブ(channels)がある。これらの要素を、JavaScriptによるDOM操作で、Internet  Explorer 6またはFirefox 2以上を対象としたIRCクライアントwebアプリケーションとして駆動する。</p>
<p>IRCクライアント本体のコードを見る前に、AJAXやDOM関係の簡易ユーティリティメソッドをまとめたJavaScriptファイルdice.jsをまず提示しておく。IRCクライアントの方ではここに登場するメソッドを使用している。</p>
<pre class="brush: cpp;">

var isMozilla = navigator.userAgent.indexOf('Gecko') != -1;
var isIE = window.ActiveXObject;

function createHttpRequest()
{
	if (isIE)
	{
		try
		{ // CLSID_XMLHTTP
			// v 3.0
			return new ActiveXObject(&quot;Msxml2.XMLHTTP&quot;);
		}
		catch (e)
		{
			try
			{// v 2.x
				return new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;);
			}
			catch (e2)
			{
				return null;
			}
		}
	}
	else if (window.XMLHttpRequest) // non-IE
	{
		var hr = new XMLHttpRequest();
		if (isMozilla)
			hr.overrideMimeType('text/xml');
		return hr;
	}
	else
	{
		return null;
	}
}

function getInnerHTML(id)
{
//	if (isIE)
//	{
		return document.getElementById(id).innerHTML;
//	}

/* For old Mozilla compatibility
	var html = &quot;&quot;;
	node = document.getElementById(id);
	for (var i = 0; i &lt; node.childNodes.length; ++i)
		html += getOuterHTML(node.childNodes.item(i));

	return html;*/
}

/* For old Mozilla compatibility
function getOuterHTML(node)
{
	var s = &quot;&quot;;

	switch (node.nodeType)
	{
		case 1: // ELEMENT_NODE
			s += &quot;&lt;&quot; + node.nodeName;
			for (var i = 0; i &lt; node.attributes.length; ++i)
			{
				if (node.attributes.item(i).nodeValue != null)
				{
					s += &quot; &quot;
					s += node.attributes.item(i).nodeName;
					s += &quot;=\&quot;&quot;;
					s += node.attributes.item(i).nodeValue;
					s += &quot;\&quot;&quot;;
				}
			}

			if (node.childNodes.length == 0
				&amp;amp;&amp;amp; (node.nodeName == &quot;IMG&quot;
					|| node.nodeName == &quot;HR&quot;
					|| node.nodeName == &quot;BR&quot;
					|| node.nodeName == &quot;INPUT&quot;
			))
				s += &quot;&gt;&quot;;
			else
			{
				s += &quot;&gt;&quot;;
				s += getInnerHTML(node);
				s += &quot;&lt;&quot; + node.nodeName + &quot;&gt;&quot;
			}
			break;
		case 3:	//TEXT_NODE
			s += node.nodeValue;
			break;
		case 4: // CDATA_SECTION_NODE
			s += &quot;&lt;![CDATA[&quot; + node.nodeValue + &quot;]]&gt;&quot;;
			break;
		case 5: // ENTITY_REFERENCE_NODE
			s += &quot;&amp;amp;&quot; + node.nodeName + &quot;;&quot;
			break;
		case 8: // COMMENT_NODE
			s += &quot;&lt;!--&quot; + node.nodeValue + &quot;--&gt;&quot;
			break;
	}

	return s;
}
*/

function replaceElement(id, html)
{
//	if (isIE)
//	{
		document.getElementById(id).innerHTML = html;
//	}
/* For old Mozilla compatibility
	else
	{
		var e = document.createElement('span');
		e.setAttribute('id', 'temp');

		var range = document.createRange();
		range.selectNodeContents(document.body);
		range.collapse(true);
		e.appendChild(range.createContextualFragment(html));

		var outer = document.getElementById(id);
		while (outer.firstChild)
			outer.removeChild(outer.firstChild);
		outer.appendChild(e);
	}*/
}

function appendElement(id, html)
{
//	if (isIE)
//	{
		document.getElementById(id).innerHTML += html;
//	}
/* For old Mozilla compatibility
	else
	{
		var e = document.createElement('span');
		e.setAttribute('id', 'temp');
		var range = document.createRange();
		range.selectNodeContents(document.body);
		range.collapse(true);
		e.appendChild(range.createContextualFragment(html));
		document.getElementById(id).appendChild(e);
	}*/
}

function sendHTTP(data, method, fileName, callback, async)
{
	var hr = createHttpRequest();
	hr.open(method, fileName, async);
	hr.setRequestHeader(&quot;If-Modified-Since&quot;, &quot;Thu, 01 Jun 1970 00:00:00 GMT&quot;);
	hr.onreadystatechange = function()
	{
		if (hr.readyState == 4)
		{
			callback(hr);
		}
	}

	hr.send(data)
}

// replace an element in an HTML document with a text node in an AJAX XML response
// that has the same tag name as the ID of the aforementioned HTML element
function replaceElementByAjaxTagName(hr, tagname)
{
	var nodelist = hr.responseXML.getElementsByTagName(tagname);
	if (nodelist != null)
	{
		var x = nodelist.length;
		for (var i = 0; i &lt; x; ++i)
		{
			var n = nodelist.item(i);
			var nn = n.firstChild;
			// NODE_TEXT == 3 || NODE_CDATA_SECTION == 4
			while (nn != null &amp;amp;&amp;amp; (nn.nodeType == 3 || nn.nodeType == 4))
			{
				replaceElement(tagname, nn.nodeValue);
				nn = nn.nextSibling;
			}
		}
	}
}

function getAjaxTextNodeByTagName(hr, tagname)
{
	var nodelist = hr.responseXML.getElementsByTagName(tagname);
	if (nodelist == null)
		return null;

	return nodelist.item(0).firstChild.nodeValue;

	//return nodelist.item(0).textContent; // IE fails
}

function unixtime2localdate(t)
{
	var d = new Date;
	d.setTime(t * 1000);

	return d.toLocaleString();

	//var h = String(d.getHours());
	//if (h.length == 1)
	//	h = &quot;0&quot; + h;

	//var m = String(d.getMinutes());
	//if (m.length == 1)
	//	m = &quot;0&quot; + h;

	//var s = String(d.getSeconds());
	//if (s.length == 1)
	//	s = &quot;0&quot; + h;

	//return String(d.getYear() % 1900 + 1900) + &quot;/&quot;
	//	+ String(d.getMonth() + 1) + &quot;/&quot;
	//	+ d.getDate() + &quot; &quot;
	//	+ h + &quot;:&quot;
    	//	+ m + &quot;:&quot;
	//	+ s;
}
	  </pre>
<p>御覧のように、かなりの部分がコメント化されて消されているのは、Mozillaが<code>innerHTML</code>をサポートしていなかった時代から使っていた互換性維持のためのコードを省略したことによる。<code>innerHTML</code>自体はあくまで非標準だが、今回はあえて古いブラウザは切り捨てておそらく効率的であろう<code>innerHTML</code>を使用するという道を選んだ。</p>
<p>ここから、JavaScriptによるIRCクライアントのコードを含んだHTMLファイルirc_client.htmlを見ていく。</p>
<pre class="brush: cpp;">
&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Web IRC Client for KLassphere DICE&lt;/title&gt;

&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot;&gt;
&lt;meta HTTP-EQUIV=&quot;PRAGMA&quot; CONTENT=&quot;NO-CACHE&quot;&gt;
&lt;meta HTTP-EQUIV=&quot;Expires&quot; CONTENT=&quot;Mon, 04 Dec 1999 21:29:02 GMT&quot;&gt;

&lt;style type=&quot;text/css&quot;&gt;&lt;!--
td, form, input, button, #tabs
{
	font-size: 12px;
	font-family: Verdana, Geneva, Arial, sans-serif;
}
--&gt;
&lt;/style&gt;
&lt;/head&gt;
&lt;script language=&quot;JavaScript&quot; src=&quot;dice.js&quot;&gt;&lt;/script&gt;
&lt;script language=&quot;JavaScript&quot;&gt;
&lt;!--
/*

Flash/Javascript IRC Client for KLassphere DICE - (c) RyuK 2006 - 2007 All Rights Reserved

klassphere[at.mark]gmail.com

http://zzz.zggg.com/

http://aiueo.da.ru/

This file is not redistributable.

*/

var flashIsEnabled = false;
var isConnected = false;

var mapChannelBuffer = new Object();
var mapSpecialBuffer = new Object();
var mapPrivateMessageBuffer = new Object();

var helpBuffer = new ChannelBuffer;
var rawBuffer = new ChannelBuffer(&quot;(This is the buffer where raw server outputs are dumped in)&lt;br&gt;&quot;);
var statusBuffer = new ChannelBuffer(&quot;(This is the buffer where current status is logged)&lt;br&gt;&quot;);

var channelListHeader = (&quot;Channel List&lt;br&gt;&lt;br&gt;Push the button to load the list from the server after a connection is established.&lt;br&gt;Click a channel name in the list to join in.&lt;br&gt;&quot;
	+ &quot;&lt;button onClick=\&quot;loadChannelList();\&quot;&gt;Refresh the channel list&lt;/button&gt;&lt;br&gt;&lt;br&gt;&quot;);
var channelListBuffer = new ChannelBuffer(channelListHeader);

var myNick = &quot;&quot;;
var currentTab = &quot;&quot;;
var serverName = &quot;&quot;;
		  </pre>
<p>まず、グローバル変数の定義から行う。<code>mapChannelBuffer</code>は汎用のObjectとして生成さ れるが、実際にはを連想配列として使用され、キーはチャンネル名(IRCチャンネル名は全て&#8217;#'という接頭辞が付く &#8211;  従ってJavaScriptオブジェクトのプロパティ名としては不適当なので、チャンネル名に対応したオブジェクトのアクセスは.#testではなく ["#test"]のように角括弧で行う)、キーに対応するエントリはIRCチャンネルの表示ウィンドウを表すオブジェクトとする予定である。同様に、<code>mapSpecialBuffer</code>は先に述べた特殊なタブの表示ウィンドウを保持する。<code>mapPrivateMessageBuffer</code>は、他のIRCユーザと1対1のプライベートメッセージを交わすための表示画面を保持する、ユーザ名をキーとした連想配列となる。<code>helpBuffer</code>、<code>rawBuffer</code>、<code>statusBuffer</code>、<code>channelListBuffer</code>はそれぞれの特殊タブの実体で、後で定義される<code>ChannelBuffer</code>というクラスのオブジェクトを個々に保持する。<code>ChannelBuffer</code>は文字通りチャンネルの中身を保持するためのオブジェクトで、ここでは一々オブジェクトを特殊化せずにそのまま他の種類のタブにも流用している。<code>myNick</code>はIRCサーバ上での自分のニックネーム、<code>currentTab</code>は現在選択され表示されているタブの名称、<code>serverName</code>は接続先サーバ名である。</p>
<p>さて、ここで説明の便宜上、irc_client.htmlの最下部に置いてあるHTMLデザインの部分を先に出しておく。デザインの 概要は、先に基本動作イメージとして述べたとおりである。冒頭にFlashのProxyIRCが配置され、1ピクセルの大きさなのでユーザからは見えな い。(尚、フラッシュのファイル名についている&#8221;?1&#8243;はwebブラウザによるFlashオブジェクトのキャッシュを抑制するためのもので、 irc_client.htmlの冒頭のヘッダにもキャッシュを抑制するためのmetaタグが置いてある。開発中に頻繁にFlashを更新するためにブラ ウザのキャッシュを抑制すべく置いたが、実際に機能する場合としない場合があるようだ。)チャンネル表示ウィンドウとして主に利用される、IDが<code>currentBuffer</code>の 要素内に、helpタブの内容は既に記述されており、アプリケーションは開始時にこの要素のHTMLを読み込んでヘルプファイルとする。また、チャンネル 表示ウィンドウの右上にはXボタンがあり、これを押すとそのタブが消去されるのはWindowsなどのGUIの慣例に拠っている。例えばチャンネルのタブ の場合、そのチャンネルから離脱するという効果が得られる。</p>
<pre class="brush: cpp;">
&lt;body onLoad=&quot;setup();&quot; onkeydown=&quot;onKeyDown(event);&quot;&gt;

&lt;OBJECT classid=&quot;clsid:D27CDB6E-AE6D-11cf-96B8-444553540000&quot; id=&quot;ProxyIRC&quot; width=&quot;1&quot; height=&quot;1&quot;
codebase=&quot;http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0&quot;&gt;
	&lt;PARAM name=&quot;movie&quot; value=&quot;ProxyIRC.swf?1&quot;&gt;
	&lt;PARAM name=&quot;quality&quot; value=&quot;high&quot;&gt;
	&lt;PARAM name=&quot;bgcolor&quot; value=&quot;#ffffff&quot;&gt;
	&lt;PARAM name=&quot;allowScriptAccess&quot; value=&quot;sameDomain&quot;&gt;
	&lt;param name=&quot;src&quot; value=&quot;ProxyIRC.swf?1&quot;&gt;
	&lt;EMBED src=&quot;ProxyIRC.swf?1&quot; quality=&quot;high&quot; bgcolor=&quot;#ffffff&quot; width=&quot;1&quot; height=&quot;1&quot; name=&quot;ProxyIRC&quot; align=&quot;middle
&quot; play=&quot;true&quot; loop=&quot;false&quot; quality=&quot;high&quot; allowScriptAccess=&quot;sameDomain&quot; type=&quot;application/x-shockwave-flash&quot; pluginspage=&quot;http://www.macromedia.com/go/getflashplayer&quot;&gt;
&lt;/OBJECT&gt;

&lt;table width=&quot;100%&quot; height=&quot;85%&quot; border=&quot;1&quot; cellpadding=&quot;0&quot;&gt;
&lt;tr&gt;
    &lt;td width=&quot;100%&quot; height=&quot;1%&quot; colspan=&quot;2&quot; bgcolor=&quot;#0066FF&quot;&gt;
	&lt;table width=&quot;100%&quot; border=&quot;0&quot; cellspacing=&quot;0&quot;&gt;
	&lt;tr&gt;
		&lt;td bgcolor=&quot;#0066FF&quot;&gt;&lt;font color=&quot;#ffffff&quot;&gt;&lt;b&gt;&lt;span id=&quot;buffer_title&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/font&gt;&lt;/td&gt;
		&lt;td width=&quot;1%&quot;&gt;&lt;button onClick=&quot;onXButtonClick();&quot;&gt;X&lt;/button&gt;&lt;/td&gt;
   	&lt;/tr&gt;
	&lt;/table&gt;
	&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
    &lt;td width=&quot;85%&quot; height=&quot;460&quot; style=&quot;vertical-align: top;&quot;&gt;&lt;p&gt;&lt;span id=&quot;currentBuffer&quot;&gt;&lt;b&gt;Welcome to the Web IRC Client for the KLassphere DICE&lt;/b&gt;&lt;br&gt;
&lt;br&gt;&lt;font size=-1&gt;
Usage:&lt;br&gt;
&lt;br&gt;
* This client requires &lt;b&gt;Internet Explorer 6&lt;/b&gt; or &lt;b&gt;Firefox 1.5&lt;/b&gt; or higher, and &lt;b&gt;Adobe Flash 9&lt;/b&gt; plug-in to run. Also &lt;b&gt;JavaScript&lt;/b&gt;
 must be enabled in the web browser options.&lt;br&gt;&lt;br&gt;
* There are tabs at the bottom of this page, &quot;&lt;b&gt;help&lt;/b&gt;&quot;, &quot;&lt;b&gt;raw&lt;/b&gt;&quot;, &quot;&lt;b&gt;status&lt;/b&gt;&quot;, and &quot;&lt;b&gt;channels&lt;/b&gt;&quot;. Click them to open. The &quot;help&quot;
 tab is this page and has the user manual. &quot;raw&quot; is the buffer where raw server outputs are dumped in. &quot;status&quot; is where current status is logged,
 such as negotiation with an IRC server. &quot;channels&quot; is for the channel list for the IRC server.&lt;br&gt;&lt;br&gt;
* Enter your nick name for IRC in the box on the right of &quot;&lt;b&gt;Nick Name&lt;/b&gt;&quot;. Then press the &quot;&lt;b&gt;Connect&lt;/b&gt;&quot; button to connect to the IRC server.
 The boxes on the right are for IRC server name and port.&lt;br&gt;&lt;br&gt;
* After the connection is established, you are transferred to the &quot;status&quot; tab and can see server messages. Then you can retrieve it the channel
 list and join channels from the &quot;channels&quot; tab. Alternatively, or when you want to create a new channel, you can specify the channel name directly
 in the box on the right of the &quot;Join Channels&quot; button and join in it by pressing the button. A channel name has to start with &quot;#&quot;.&lt;br&gt;&lt;br&gt;
* When you've joined a channel, a new tab is added in the tab bar at the bottom of this page. You can switch channels by clicking tabs.&lt;br&gt;&lt;br&gt;
* To speak in a channel, write down your message in the &quot;&lt;b&gt;Message&lt;/b&gt;&quot; box and press the Enter key or push the &quot;Send&quot; button.&lt;br&gt;&lt;br&gt;
* To exchange private messages with a channel member, click a user name in the channel member list and type in a message.&lt;br&gt;&lt;br&gt;
* The &quot;Unicode (UTF-8)&quot; / &quot;Japanese Multibyte (JIS)&quot; radio buttons are used to change the message encoding. Set it to &quot;Unicode (UTF-8)&quot;
 if your channel's encoding is Unicode.&lt;br&gt;&lt;br&gt;
* When you check the &quot;Raw Message&quot; checkbox this client sends the text entered as is to the IRC server. This command is useful to enter
 an IRC command which is not supported by this client. Alternatively you can send a message with &quot;/quote &quot; prefix to gain the same effect.&lt;br&gt;&lt;br&gt;
* If you want to erase the content of the current tab, push the &quot;Clear This Buffer&quot; button.&lt;br&gt;&lt;br&gt;
* To part from a channel, click the &quot;X&quot; button on the upper right of the tab.&lt;br&gt;&lt;br&gt;
* If you are done with chat, push the &quot;Disconnect&quot; button to quit.&lt;br&gt;&lt;br&gt;
&lt;/font&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
    &lt;td width=&quot;15%&quot; style=&quot;vertical-align: bottom;&quot;&gt; &lt;p&gt;&lt;span id=&quot;members&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;form onSubmit=&quot;return false;&quot;&gt;&lt;input type=&quot;button&quot; onclick=&quot;connect();&quot; id=&quot;buttonConnect&quot; value=&quot;(initializing)&quot;&gt;
 &lt;input type=&quot;text&quot; id=&quot;IRCHost&quot; value=&quot;&quot;&gt; : &lt;input type=&quot;text&quot; id=&quot;IRCPort&quot; value=&quot;&quot; size=&quot;5&quot;&gt; Nick Name &lt;input type=&quot;text&quot; id=&quot;nick&quot; value=&quot;&quot; size=&quot;16&quot;&gt;
   &lt;input type=&quot;button&quot; onclick=&quot;joinChannel();&quot; id=&quot;buttonJoin&quot; value=&quot;Join Channel&quot;&gt;
 &lt;input type=&quot;text&quot; id=&quot;channelName&quot; size=&quot;32&quot; onFocus=&quot;this.value = '';&quot; value=&quot;(enter a channel name here)&quot;&gt;&lt;br&gt;
Message &lt;input type=&quot;text&quot; id=&quot;message&quot; size=&quot;80&quot;&gt;&lt;input type=&quot;button&quot; onclick=&quot;sendMessage();&quot; value=&quot;Send&quot;&gt;&lt;br&gt;
&lt;input type=&quot;radio&quot; name=&quot;charset&quot; value=&quot;utf-8&quot; onclick=&quot;setCharset(false)&quot;&gt; Unicode (UTF-8)
&lt;input type=&quot;radio&quot; name=&quot;charset&quot; value=&quot;JIS&quot; onclick=&quot;setCharset(true)&quot; checked&gt; Japanese Multibyte (JIS) &lt;input type=&quot;checkbox&quot; id=&quot;raw&quot;&gt; Raw Message
 &lt;input type=&quot;button&quot; onClick=&quot;onClearThisBuffer();&quot; id=&quot;buttonClearBuffer&quot; value=&quot;Clear this buffer&quot;&gt;&lt;/form&gt;
&lt;span id=&quot;tabs&quot;&gt;&lt;/span&gt;
&lt;/body&gt;&lt;/html&gt;
</pre>
<p>再びJavaScriptコードへ戻ろう。初期化関連の関数がまず登場する。</p>
<pre class="brush: cpp;">
function callProxyIRC()
{
	// IE6 doesn't need this method, but FF3 does
	if (isIE)
	{
		return window[&quot;ProxyIRC&quot;];
	}
	else
	{
		return document[&quot;ProxyIRC&quot;];
	}
}

function setup()
{
	mapSpecialBuffer[&quot;help&quot;] = helpBuffer;
	helpBuffer.buffer = getInnerHTML(&quot;currentBuffer&quot;);

	mapSpecialBuffer[&quot;raw&quot;] = rawBuffer;
	mapSpecialBuffer[&quot;status&quot;] = statusBuffer;
	mapSpecialBuffer[&quot;channels&quot;] = channelListBuffer;

	var date = new Date;
	document.getElementById(&quot;nick&quot;).value = (&quot;guest&quot; + date.getTime().toString().slice(-5));

	renderTabs();
	switchTab(&quot;help&quot;);

	sendHTTP( '' , 'GET', './irc_address', fillServerAddress, true);

	// For Firefox 2 and lower, the initialization of Flash is so slow
	// that the setup function must be delayed.
	setTimeout('setupFlash()', 1000);
}

function setupFlash()
{
	callProxyIRC().testLoopback();

	if (!flashIsEnabled)
		setTimeout('setupFlash()', 2000);
}

function loopbackFromFlash()
{
	flashIsEnabled = true;
	document.getElementById(&quot;buttonConnect&quot;).value = &quot;Connect&quot;;
}

function fillServerAddress(hr)
{
	document.getElementById(&quot;IRCHost&quot;).value = getAjaxTextNodeByTagName(hr, &quot;IRCHost&quot;);
	document.getElementById(&quot;IRCPort&quot;).value = getAjaxTextNodeByTagName(hr, &quot;IRCPort&quot;);
}
	   </pre>
<p><code>callProxyIRC</code>はFlashのProxyIRCオブジェクトをJavaScriptからの呼び出し対象として取得する関数で、ブラウザ間の扱いの違いを吸収するためのラッパーである。<code>isIE</code>はdice.js内で定義されているフラグで、Internet Explorerに対してtrueとなる。IEの場合は全てのドキュメントオブジェクトはwindowに格納されており、Firefoxの場合はdocumentになる。<code>setup</code>は このアプリケーションの初期化関数で、HTMLからonLoadで呼び出され、まずそれぞれのタブ群をまとめる連想配列に各々の表示ウィンドウのオブジェ クトを入れる。それから、ユーザのニックネーム欄には&#8221;guest&#8221;で始まるランダムな文字列が設定される。タブ群の準備が出来たところで<code>renderTabs</code>を呼び出し、タブを列挙したバーの描画を行い、helpタブへタブを切り替える。最後に、webサーバの<code>/irc_address</code>というファイルを取得するようAJAXで要求を送っている。<a href="../../dice/index_j.html">DICE</a>のwebサービスは<code>/irc_address</code>でIRCサービスのアドレスとポートをXMLファイルとして返すので、<code>fillServerAddress</code>関 数はこの情報を得てフォームの接続先ホスト・ポート欄に記入する。setup関数の最後には、Flash内のtestLoopback関数を呼び出すこと により、Flashがこのブラウザで有効になっているか調べている。Firefox  3やIE6/7ではsetTimeoutで実行を遅らせる必要はないが、Firefox  2だとFlashの初期化がbodyタグでのonloadのタイミングより若干遅れるため、2秒の実行待ち時間を入れている。</p>
<p>続いて、<code>ChannelBuffer</code>クラスの定義を行う。</p>
<pre class="brush: cpp;">

// ChannelMember class
function ChannelMember(px, n)
{
	var obj =
	{
		prefix : px,
		nick : n
	};
	return obj;
}

// ChannelBuffer class
function ChannelBuffer(buf)
{
	if (buf == null)
		buf = &quot;&quot;;

	var obj =
	{
		buffer : buf,
		members : new Array(),

		addMember : function(m) // m: ChannelMember object
		{
			this.members.push(m);
		},

		// returns the removed channel member object if it's successful
		removeMember : function(n) // n: nick
		{ // don't use splice for compatibility
			var ret = false;
			var c = new Array();
			for (var i in this.members)
			{
				if (ret)
					c.push(this.members[i]);
				else if (this.members[i].nick != n)
					c.push(this.members[i]);
				else
					ret = this.members[i];
			}

			if (ret)
				this.members = c;

			return ret;
		},

		sortMembers : function()
		{
			this.members.sort(this.compareMember);
		},

		compareMember : function(a, b)
		{
			if (a.prefix == &quot;@&quot; &amp;amp;&amp;amp; b.prefix != &quot;@&quot;)
				return -1;
			if (a.prefix == &quot;+&quot; &amp;amp;&amp;amp; b.prefix == &quot;&quot;)
				return -1;
			if (a.prefix == &quot;&quot; &amp;amp;&amp;amp; b.prefix != &quot;&quot;)
				return 1;

			if (b.prefix == &quot;@&quot; &amp;amp;&amp;amp; a.prefix != &quot;@&quot;)
				return 1;
			if (b.prefix == &quot;+&quot; &amp;amp;&amp;amp; a.prefix == &quot;&quot;)
				return 1;
			if (b.prefix == &quot;&quot; &amp;amp;&amp;amp; a.prefix != &quot;&quot;)
				return -1;

			//return (a.nick - b.nick); // this doesn't work in IE6

			if (b.nick &gt; a.nick)
				return -1;
			else if (b.nick == a.nick)
				return 0;
			else
				return 1;
		},

		setMemberMode : function(nick, mode)
		{
			for (var i in this.members)
			{
				if (this.members[i].nick == nick)
				{
					if (mode.charAt(0) == &quot;+&quot;)
					{
						switch (mode.charAt(1))
						{
						case &quot;o&quot;:
							this.members[i].prefix = &quot;@&quot;;
							break;
						case &quot;v&quot;:
							this.members[i].prefix = &quot;+&quot;;
							break;
						}
					}
					else
						this.members[i].prefix = &quot;&quot;;

					return;
				}
			}
		},

		getMemberPrefix : function(nick)
		{
			for (var i in this.members)
			{
				if (this.members[i].nick == nick)
				{
					return this.members[i].prefix;
				}
			}

			return &quot;&quot;;
		},

		// these rendering functions force update when the parameter is omitted.
		renderMembers : function(bufferName)
		{
			var ret = (this.members.length == 0 ? &quot;&quot; : &quot;[members]&lt;br&gt;&quot;);

			for (var i in this.members)
			{
				ret += &quot;&lt;a style=\&quot;text-decoration: none;\&quot; href=\&quot;#\&quot; onclick=\&quot;openPrivateMessage('&quot;;
				ret += (this.members[i].nick + &quot;@&quot; + serverName);
				ret += &quot;')\&quot;&gt;&quot;;
				ret += (this.members[i].nick == myNick
					? (&quot;&lt;font color=\&quot;red\&quot;&gt;&quot; + this.members[i].prefix + this.members[i].nick + &quot;&lt;/font&gt;&quot;)
						: (this.members[i].prefix + this.members[i].nick));
				ret += &quot;&lt;/a&gt;&quot;;
				ret += &quot;&lt;br&gt;&quot;;
			}

			if (!bufferName || currentTab == bufferName)
			{
				replaceElement(&quot;members&quot;, ret);
			}
		},

		renderBuffer : function(bufferName)
		{
			if (!bufferName || currentTab == bufferName)
			{
				replaceElement(&quot;currentBuffer&quot;, this.buffer);
			}
		},

		render : function(bufferName)
		{
			if (!bufferName || currentTab == bufferName)
			{
				replaceElement(&quot;currentBuffer&quot;, this.buffer);
				this.renderMembers();
			}
		},

		renderTab : function(i)
		{
			var t = &quot;&lt;span style=\&quot;border: 1px solid gray; padding: 0px 2px 4px 4px; margin: 3px 3px 3px 3px\&quot;&gt;&lt;a style=\&quot;text-decoration: none;\&quot; href=\&quot;#\&quot; onclick=\&quot;switchTab('&quot;;
			t += i;
			t += &quot;')\&quot;&gt;&quot;;
			t += i;
			t += &quot;&lt;/a&gt;&lt;/span&gt;&quot;;
			return t;
		}
	};
	return obj;
}
	  </pre>
<p>JavaScriptはプロトタイプベースの言語でクラスというものが存在せず、いつでもオブジェクトのプロパティやメソッド、また継 承に使用されるプロトタイプメソッドを付けたり外したりできるので、あるプロパティのセットを持つオブジェクトを返すという、クラスのコンストラクタ風の メソッドを定義することによってオブジェクト指向を模することになる。ここではJSONデータフォーマットの元となったオブジェクト記法でクラスのプロパ ティやメソッドオブジェクトを列挙している。<code>ChannelMember</code>クラスはチャンネル参加メンバーリストの個々のメンバーを表すオブジェクトで、<code>prefix</code>と<code>nick</code>の2つのプロパティを持つ。IRCのチャンネル参加者は階級毎に異なる接頭辞が付くのでその接頭辞とニックネームの双方を別々に保持するための2つのプロパティである。<code>ChannelBuffer</code>クラスは先に出てきたチャンネル表示ウインドウのデータを保持するためのクラスである。<code>buffer</code>はチャンネルのログなどが表示されるメインの領域のデータを保持するためのバッファで、<code>members</code>は<code>ChannelMember</code>の 配列を保持するチャンネル参加者リストである。チャンネルメンバー追加、メンバーリストのソートなどの一連のメンバーリスト操作メソッドの後に、メンバー リスト、メイン領域、タブ列挙バーの中の個々のタブの描画を担当するレンダリングメソッドが続く。これらレンダリングメソッドは、対象のタブが現在表示さ れているときのみdice.jsの<code>replaceElement</code>関数を使用してDOMを操作し表示領域を更新する。</p>
<pre class="brush: cpp;">
function getCurrentTimeString()
{
	var ret = &quot;&quot;;
	var d = new Date();
	var tmp = d.getHours();
	if (tmp &lt; 10)
		tmp = &quot;0&quot; + tmp;
	ret += tmp;
	ret += &quot;:&quot;;
	tmp = d.getMinutes();
	if (tmp &lt; 10)
		tmp = &quot;0&quot; + tmp;
	ret += tmp;
	ret += &quot;:&quot;;
	tmp = d.getSeconds();
	if (tmp &lt; 10)
		tmp = &quot;0&quot; + tmp;
	ret += tmp;
	return ret;
}

function refreshCurrentBuffer(bufferName, buffer)
{
	if (currentTab == bufferName)
		replaceElement(&quot;currentBuffer&quot;, buffer);
}

function passOutputStatusBuffer(s, raw)
{
	if (raw)
	{
		statusBuffer.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot; + s);
		refreshCurrentBuffer(&quot;status&quot;, statusBuffer.buffer);
		return;
	}

	var matched = s.match(/\S+\s+:*(.*)/);
	if (matched == null)
	{
		mapSpecialBuffer[&quot;status&quot;].buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot; + s);
		return;
	}

	statusBuffer.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot; + matched[1] + &quot;&lt;br&gt;&quot;);
	refreshCurrentBuffer(&quot;status&quot;, statusBuffer.buffer);
}

function passOutputRawBuffer(s)
{
	rawBuffer.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot; + s + &quot;&lt;br&gt;&quot;);
	refreshCurrentBuffer(&quot;raw&quot;, rawBuffer.buffer);
}

function escapeHTML(s)
{
    return s.split(&quot;&lt;&quot;).join(&quot;&amp;amp;lt;&quot;).split(&quot;&gt;&quot;).join(&quot;&amp;amp;gt;&quot;);
}
	  </pre>
<p>これらはユーティリティ関数群である。<code>getCurrentTimeString</code>はタイムスタンプ用文字列を生成し、<code>refreshCurrentBuffer</code>はタブが現在表示されているときのみ画面を更新する。<code>passOutputStatusBuffer</code>はstatusタブへ、<code>passOutputRawBuffer</code>はrawタブへそれぞれ文字列を出力する。escapeHTMLはIRCサーバから送られてくる文字列がブラウザによってHTMLとして解釈されないように全てサニタイズするためのフィルタ関数である。</p>
<pre class="brush: cpp;">
// this function is called from Flash
function output(s)
{
	s = escapeHTML(s);

	// matched[1] : prefix, matched[2] : command, matched[3] : parameter
	var matched = s.match(/^:(\S+)\s(\S+)\s*(.*)/);
	if (matched == null)
		return;

	if (serverName == &quot;&quot;)
		serverName = matched[1];

	passOutputRawBuffer(&quot;&lt;font color=red&gt;&quot; + matched[1]
			+ &quot;&lt;/font&gt; &lt;font color=blue&gt;&quot; + matched[2]
			+ &quot;&lt;/font&gt; &lt;font color=green&gt;&quot; + matched[3] + &quot;&lt;/font&gt;&quot;);

	switch (matched[2])
	{
	case &quot;JOIN&quot;:
		onJOIN(matched[1], matched[3]);
		break;
	case &quot;KICK&quot;:
		onKICK(matched[1], matched[3]);
		break;
	case &quot;MODE&quot;:
		onMODE(matched[1], matched[3]);
		break;
	case &quot;NICK&quot;:
		onNICK(matched[1], matched[3]);
		break;
	case &quot;NOTICE&quot;:
		onNOTICE(matched[1], matched[3]);
		break;
	case &quot;PART&quot;:
		onPART(matched[1], matched[3]);
		break;
	case &quot;PRIVMSG&quot;:
		onPRIVMSG(matched[1], matched[3]);
		break;
	case &quot;QUIT&quot;:
		onQUIT(matched[1], matched[3]);
		break;
	case &quot;332&quot;: // RPL_TOPIC
		onTOPIC(matched[3]);
		break;
	case &quot;353&quot;: // RPL_NAMREPLY
		onNAMREPLY(matched[3]);
		break;
	case &quot;001&quot;: // RPL_WELCOME
		onWELCOME(matched[1], matched[3]);
		break;
	case &quot;002&quot;: // RPL_YOURHOST
	case &quot;003&quot;: // RPL_CREATED
	case &quot;004&quot;: // RPL_MYINFO
	case &quot;005&quot;: // RPL_BOUNCE / RPL_ISUPPORT

	case &quot;251&quot;: // RPL_LUSERCLIENT
	case &quot;254&quot;: // RPL_LUSERCHANNELS
	case &quot;255&quot;: // RPL_LUSERME

	case &quot;375&quot;: // RPL_MOTDSTART
	case &quot;372&quot;: // RPL_MOTD
	case &quot;376&quot;: // RPL_MOTDSTART
		passOutputStatusBuffer(matched[3]);
		break;
	default:

		break;
	}
}

function reportError(s)
{
	statusBuffer.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot; + s + &quot;&lt;br&gt;&quot;);
	refreshCurrentBuffer(&quot;status&quot;, statusBuffer.buffer);
}
	  </pre>
<p><code>output</code>は、先にProxyIRCの解説で登場した、FlashがIRCサーバーからデータを受け取ったときに呼ばれる関数である。ここで正規表現を用いてIRCメッセージを解析し、得られたコマンドに基づいてそれぞれのコマンド処理関数へ処理を分岐させる。<code>reportError</code>はエラーをstatusタブに書き出す関数である。</p>
<pre class="brush: cpp;">
function renderTabs()
{
	var t = &quot;&quot;;

	for (var i in mapSpecialBuffer)
	{
		t += mapSpecialBuffer[i].renderTab(i);
	}

	for (var i in mapChannelBuffer)
	{
		t += mapChannelBuffer[i].renderTab(i);
	}

	for (var i in mapPrivateMessageBuffer)
	{
		t += mapPrivateMessageBuffer[i].renderTab(i);
	}

	replaceElement(&quot;tabs&quot;, t);
}

function setCurrentBuffer(name)
{
	currentTab = name;
	replaceElement(&quot;buffer_title&quot;, name);
}

function switchTab(name)
{
	for (var i in mapSpecialBuffer)
	{
		if (name == i)
		{
			setCurrentBuffer(name);
			mapSpecialBuffer[name].render();
			return;
		}
	}

	for (var i in mapChannelBuffer)
	{
		if (name == i)
		{
			setCurrentBuffer(name);
			mapChannelBuffer[name].render();
			return;
		}
	}

	for (var i in mapPrivateMessageBuffer)
	{
		if (name == i)
		{
			setCurrentBuffer(name);
			mapPrivateMessageBuffer[name].render();
			return;
		}
	}

	alert(&quot;Invalid tab&quot;);
}

function setCharset(jis)
{
	callProxyIRC().setJIS(jis);
}
	  	  </pre>
<p><code>renderTabs</code>は特殊タブ、チャンネルタブ、プライベートメッセージタブの順でタブ列挙バーの描画を行う。setCurrentBufferは現在のタブを設定するとともに表示ウィンドウのタイトルバーに表示されている文字列を現在のタブの名称に更新する。<code>switchTab</code>はユーザがタブを押したときに呼ばれる関数で、目的のタブを探して表示する。<code>setCharset</code>はFlashが日本語JISエンコーディングを文字列に適用するか否かを設定する。</p>
<pre class="brush: cpp;">
function connect()
{
	if (!flashIsEnabled)
	{
		callProxyIRC().testLoopback();

		if (!flashIsEnabled)
		{
			alert(&quot;This client requires Adobe Flash Player 9 and a JavaScript-enabled web browser!&quot;);
			return;
		}
	}

	document.getElementById(&quot;buttonConnect&quot;).blur();

	if (isConnected)
	{
		if (!confirm(&quot;Do you really want to disconnect?&quot;))
			return;

		callProxyIRC().quitIRC();
		onClose();
		return;
	}

	var n = document.getElementsByName(&quot;charset&quot;);
	for (var i = 0; i &lt; n.length; ++i)
	{
		if (n.item(i).checked)
			callProxyIRC().setJIS(n.item(i).value == &quot;JIS&quot; ? true : false);
	}

	var h = document.getElementById(&quot;IRCHost&quot;).value;
	if (!h || h == &quot;&quot;)
	{
		alert(&quot;Invalid host name&quot;);
		return;
	}

	var p = document.getElementById(&quot;IRCPort&quot;).value;
	if (!p || p == &quot;&quot;)
	{
		alert(&quot;Invalid port&quot;);
		return;
	}

	callProxyIRC().connectIRC(h, parseInt(p, 10));
}

function onConnect()
{
	isConnected = true;
	document.getElementById(&quot;buttonConnect&quot;).value = &quot;Disconnect&quot;;

	switchTab(&quot;status&quot;);

	myNick = document.getElementById(&quot;nick&quot;).value;

	send(&quot;USER &quot; + myNick + &quot; &quot; + myNick + &quot; &quot; + myNick + &quot;:&quot; + myNick);
	send(&quot;NICK &quot; + myNick);
}

function onClose()
{
	isConnected = false;
	document.getElementById(&quot;buttonConnect&quot;).value = &quot;Connect&quot;;

	mapChannelBuffer = new Object();
	renderTabs();

	switchTab(&quot;status&quot;);
	passOutputStatusBuffer(&quot;&lt;br&gt;* Disconnected&lt;br&gt;&lt;br&gt;&quot;, true);

	serverName = &quot;&quot;;
	myNick = &quot;&quot;;
}
	  </pre>
<p><code>connect</code>はユーザが接続ボタンを押したときに呼ばれる関数で、接続中に呼ばれると接続を切断するはたらきもある。フォームのホスト欄、ポート欄からそれぞれデータを取得し、Flash側のメソッドを呼び出してIRCサーバへの接続を開始する。<code>onConnect</code>、<code>onClose</code>はそれぞれサーバに接続したとき、切断されたときにFlash側から呼び出される関数で、前者はニックネームを設定するIRCコマンドをサーバへ送信し、後者はチャンネルリストをクリアしてタブ列挙バーを再描画し、画面に切断の表示を出して終了処理を行う。</p>
<pre class="brush: cpp;">
function joinChannel()
{
	document.getElementById(&quot;buttonJoin&quot;).blur();

	if (!isConnected)
	{
		alert(&quot;Not connected&quot;);
		return;
	}

	if (document.getElementById(&quot;channelName&quot;).value == &quot;&quot;)
	{
		alert(&quot;No channel specified&quot;);
		return;
	}

	if (document.getElementById(&quot;channelName&quot;).value.charAt(0) != '#')
	{
		alert(&quot;Invalid channel name - it must begin with '#'&quot;);
		return;
	}

	send(&quot;JOIN &quot; + document.getElementById(&quot;channelName&quot;).value);
}

function send(s)
{
	passOutputRawBuffer(&quot;(send) &quot; + s);

	callProxyIRC().sendIRC(s);
}

function sendMessage()
{
	var m = document.getElementById(&quot;message&quot;).value;
	if (m == &quot;&quot;)
		return;

	if (document.getElementById(&quot;raw&quot;).checked)
	{
		document.getElementById(&quot;message&quot;).value = &quot;&quot;;
		send(m);
		return;
	}

	var matched = m.match(/\/quote\s+(.*)/i);
	if (matched)
	{
		document.getElementById(&quot;message&quot;).value = &quot;&quot;;
		send(matched[1]);
		return;
	}

	for (var i in mapSpecialBuffer)
	{
		if (currentTab == i)
		{
			send(m);
			return;
		}
	}

	for (var i in mapChannelBuffer)
	{
		if (currentTab == i)
		{
			var c = mapChannelBuffer[currentTab];
			if (!c)
				return;

			c.buffer += &quot;[&quot;;
			c.buffer += getCurrentTimeString();
			c.buffer += &quot;] &lt;&quot;;
			c.buffer += myNick;
			c.buffer += &quot;&gt; &quot;;
			c.buffer += m;
			c.buffer += &quot;&lt;br&gt;&quot;;

			c.renderBuffer(currentTab);

			document.getElementById(&quot;message&quot;).value = &quot;&quot;;
			send(&quot;PRIVMSG &quot; + currentTab + &quot; :&quot; + m);
			return;
		}
	}

	for (var i in mapPrivateMessageBuffer)
	{
		if (currentTab == i)
		{
			var pm = mapPrivateMessageBuffer[currentTab];
			if (!pm)
				return;

			matched = currentTab.match(/([^@]+)@/);
			if (!matched)
				return;

			pm.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
			pm.buffer += &quot;&lt;&quot;;
			pm.buffer += myNick;
			pm.buffer += &quot;&gt; &quot;;
			pm.buffer += m;
			pm.buffer += &quot;&lt;br&gt;&quot;;

			pm.renderBuffer(currentTab);

			document.getElementById(&quot;message&quot;).value = &quot;&quot;;
			send(&quot;PRIVMSG &quot; + matched[1] + &quot; :&quot; + m);
			return;
		}
	}
}
	  </pre>
<p><code>joinChannel</code>はユーザが操作パネルから参加対象チャンネルを明示的に選んでチャンネルに参加することを選んだ場合に呼ばれる関数である。sendは裸の文字列をサーバへ送信する関数で、<code>sendMessage</code>は ユーザが操作パネルのメッセージ欄に文字列を記入しEnterキーを押すか送信ボタンを押したときに呼ばれる関数である。IRCサーバはユーザが送信して きたメッセージをそのユーザ自身へエコーしないので、クライアントが自分で自分の発したメッセージを画面に書き出してやる必要がある。</p>
<pre class="brush: cpp;">
function onJOIN(prefix, param)
{
	var matched = prefix.match(/([^!]+)!(\S+)/);
	if (matched == null)
		return;

	if (matched[1] == myNick)
	{
		var c = new ChannelBuffer(&quot;&quot;);

		c.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
		c.buffer += &quot;&lt;font color=\&quot;green\&quot;&gt;* Now talking in &quot;;
		c.buffer += param;
		c.buffer += &quot;&lt;/font&gt;&lt;br&gt;&quot;;

		mapChannelBuffer[param] = c;

		renderTabs();
		switchTab(param);
		send(&quot;MODE &quot; + param);
		//send(&quot;WHO &quot; + param);
	}
	else
	{
		var c = mapChannelBuffer[param];
		if (!c)
			return;

		c.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
		c.buffer += &quot;&lt;font color=\&quot;green\&quot;&gt;* Joins: &quot;;
		c.buffer += matched[1];
		c.buffer += &quot; (&quot;;
		c.buffer += matched[2];
		c.buffer += &quot;)&lt;/font&gt;&lt;br&gt;&quot;;

		c.addMember(ChannelMember(&quot;&quot;, matched[1]));
		c.sortMembers();

		c.render(param);
	}
}

function onPART(prefix, param)
{
	var matched = prefix.match(/([^!]+)!(\S+)/);
	if (matched == null)
		return;

	var c = mapChannelBuffer[param];
	if (!c)
		return;

	c.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
	c.buffer += &quot;&lt;font color=\&quot;green\&quot;&gt;* Parts: &quot;;
	c.buffer += matched[1];
	c.buffer += &quot; (&quot;;
	c.buffer += matched[2];
	c.buffer += &quot;)&lt;/font&gt;&lt;br&gt;&quot;;

	c.removeMember(matched[1]);
	c.sortMembers();

	c.render(param);
}

function onKICK(prefix, param)
{
	var matched = prefix.match(/([^!]+)!(\S+)/);
	if (matched == null)
		return;

	var nick = matched[1];

	matched = param.match(/(\S+)\s+(\S+)\s+:(.*)/);
	if (matched == null || matched[1] == null || matched[2] == null)
		return;

	if (matched[2] == myNick)
	{
		delete mapChannelBuffer[matched[1]];

		var s = c.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &lt;font color=\&quot;green\&quot;&gt;* You were kicked from &quot;);
		s += matched[1];
		s += &quot; by &quot;;
		s += nick;

		if (matched[3])
		{
			s += &quot; (&quot;;
			s += matched[3];
			s += &quot;)&quot;;
		}

		s += &quot;&lt;/font&gt;&lt;br&gt;&quot;;

		passOutputStatusBuffer(s, true);

		renderTabs();
		switchTab(&quot;status&quot;);

		return;
	}

	var c = mapChannelBuffer[matched[1]];
	if (!c)
		return;

	c.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
	c.buffer += &quot;&lt;font color=\&quot;green\&quot;&gt;* &quot;;
	c.buffer += matched[2];
	c.buffer += &quot; was kicked by &quot;;
	c.buffer += nick;

	if (matched[3])
	{
		c.buffer += &quot; (&quot;;
		c.buffer += matched[3];
		c.buffer += &quot;)&quot;;
	}

	c.buffer += &quot;&lt;/font&gt;&lt;br&gt;&quot;;

	c.removeMember(matched[2]);
	c.sortMembers();

	c.render(param);
}

function onQUIT(prefix, param)
{
	var matched = prefix.match(/([^!]+)!/);
	if (matched == null)
		return;

	var nick = matched[1];
	for (var i in mapChannelBuffer)
	{
		var c = mapChannelBuffer[i];
		if (c.removeMember(nick))
		{
			c.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
			c.buffer += &quot;&lt;font color=\&quot;blue\&quot;&gt;*** Quits: &quot;;
			c.buffer += matched[1];
			c.buffer += &quot; (&quot;;
			c.buffer += ((param.length != 0 &amp;amp;&amp;amp; param.charAt(0) == &quot;:&quot;) ? param.substr(1): param);
			c.buffer += &quot;)&lt;/font&gt;&lt;br&gt;&quot;;

			c.sortMembers();
			c.render(i);
		}
	}
}
	  </pre>
<p>これらはユーザの入退出に関連するメッセージを受け取ったときの処理関数である。<code>onJOIN</code>はユーザがチャンネルに参加してきたときに送られるメッセージで、自分がチャンネルに参加成功したときも送られてくるためそれぞれについて処理する必要がある。<code>onPART</code>はユーザがチャンネルから退出したというメッセージ、<code>onKICK</code>はチャンネルから強制的に出されたというメッセージを処理する。<code>onQUIT</code>はあるユーザがIRCサーバ自体から退出したというメッセージで、<code>onPART</code>と異なりそのユーザが参加していた全てのチャンネルについて影響を考慮してやる必要がある。</p>
<pre class="brush: cpp;">
function onMODE(prefix, param)
{
	var matched = prefix.match(/([^!]+)/);
	if (matched == null)
		return;

	var subject = matched[1];

	matched = param.match(/(\S+)\s+(\S+)\s+(\S*)/);
	if (matched == null)
		return;

	var c = mapChannelBuffer[matched[1]];
	if (!c)
		return;

	// channel mode
	if (!matched[3])
	{
		c.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
		c.buffer += &quot;&lt;font color=\&quot;green\&quot;&gt;* &quot;;
		c.buffer += subject;
		c.buffer += &quot; sets mode: &quot;;
		c.buffer += matched[2];
		c.buffer += &quot;&lt;/font&gt;&lt;br&gt;&quot;;

		c.renderBuffer(matched[1]);
		return;
	}

	// user mode

	c.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
	c.buffer += &quot;&lt;font color=\&quot;green\&quot;&gt;* &quot;;
	c.buffer += subject;
	c.buffer += &quot; sets mode: &quot;;
	c.buffer += matched[2];
	c.buffer += &quot; &quot;;
	c.buffer += matched[3];
	c.buffer += &quot;&lt;/font&gt;&lt;br&gt;&quot;;

	c.setMemberMode(matched[3], matched[2]);
	c.sortMembers();

	c.render(matched[1]);
}

function onNICK(prefix, param)
{
	var matched = prefix.match(/([^!]+)!/);
	if (matched == null)
		return;

	var subject = matched[1];

	matched = param.match(/:(\S+)/);
	if (matched == null)
		return;

	var newNick = matched[1];

	var m = (&quot;[&quot; + getCurrentTimeString() + &quot;] &lt;font color=\&quot;green\&quot;&gt;* &quot;);
	m += subject;
	m += &quot; is now known as &quot;;
	m += newNick;
	m += &quot;&lt;/font&gt;&lt;br&gt;&quot;;

	if (subject == myNick)
	{
		myNick = newNick;
		document.getElementById(&quot;nick&quot;).value = myNick;
	}

	for (var i in mapChannelBuffer)
	{
		var c = mapChannelBuffer[i];
		var removed = c.removeMember(subject);
		if (removed)
		{
			c.buffer += m;
			c.addMember(ChannelMember(removed.prefix, newNick));
			c.sortMembers();
			c.render(i);
		}
	}
}
	 </pre>
<p><code>onMODE</code>はチャンネルまたはユーザのモードが変化したとき、<code>onNICK</code>はユーザがニックネームを変更したときに送られるメッセージを処理する。ユーザのニックネーム変更は<code>onQUIT</code>同様に全ての関係チャンネルで処理しなければならない。</p>
<pre class="brush: cpp;">
function onNOTICE(prefix, param)
{
	var matched = prefix.match(/([^!]+)/);
	if (matched == null)
		return;

	var nick = matched[1];

	matched = param.match(/(\S+)\s+:(.*)/);
	if (matched == null)
	{
		passOutputStatusBuffer(&quot;-&quot; + serverName + &quot;- &quot; + param.substr(1) + &quot;&lt;br&gt;&quot;, true);
		return;
	}

	var c = mapChannelBuffer[matched[1]];
	if (!c)
	{
		delete mapChannelBuffer[matched[1]];
		passOutputStatusBuffer(&quot;-&quot; + nick + &quot;- &quot; + matched[2] + &quot;&lt;br&gt;&quot;, true);
		return;
	}

	c.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
	c.buffer += &quot;-&gt; (&quot;;
	c.buffer += nick;
	c.buffer += &quot;) &quot;;
	c.buffer += matched[2];
	c.buffer += &quot;&lt;br&gt;&quot;;

	c.renderBuffer(matched[1]);
}

function onPRIVMSG(prefix, param)
{
	var matched = prefix.match(/([^!]+)/);
	if (matched == null)
		return;

	var nick = matched[1];

	matched = param.match(/(\S+)\s+:(.*)/);
	if (matched == null)
		return;

	var c = mapChannelBuffer[matched[1]];
	if (!c)
	{
		if (matched[1] == myNick)
		{ // private message
			var pm = mapPrivateMessageBuffer[nick + &quot;@&quot; + serverName];
			if (pm)
			{
				pm.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
				pm.buffer += &quot;&lt;&quot;;
				pm.buffer += nick;
				pm.buffer += &quot;&gt; &quot;;
				pm.buffer += matched[2];
				pm.buffer += &quot;&lt;br&gt;&quot;;

				pm.renderBuffer(nick + &quot;@&quot; + serverName);
			}
			else
			{
				var pm = new ChannelBuffer(&quot;&quot;);
				pm.buffer += (&quot;[&quot; + getCurrentTimeString() + &quot;] &quot;);
				pm.buffer += &quot;&lt;&quot;;
				pm.buffer += nick;
				pm.buffer += &quot;&gt; &quot;;
				pm.buffer += matched[2];
				pm.buffer += &quot;&lt;br&gt;&quot;;

				mapPrivateMessageBuffer[nick + &quot;@&quot; + serverName] = pm;

				renderTabs();
			}
		}
		return;
	}

	c.buffer += &quot;[&quot;;
	c.buffer += getCurrentTimeString();
	c.buffer += &quot;] &lt;&quot;;
	c.buffer += c.getMemberPrefix(nick);
	c.buffer += nick;
	c.buffer += &quot;&gt; &quot;;
	c.buffer += matched[2];
	c.buffer += &quot;&lt;br&gt;&quot;;

	c.renderBuffer(matched[1]);
}

function openPrivateMessage(target)
{
	if (serverName == &quot;&quot;)
	{
		alert(&quot;Not connected&quot;);
		return;
	}

	var matched = target.match(/([^@]+)/);
	if (!matched)
		return;

	var pm = mapPrivateMessageBuffer[matched[1] + &quot;@&quot; + serverName];
	if (!pm)
	{
		var pm = new ChannelBuffer(&quot;&quot;);
		mapPrivateMessageBuffer[matched[1] + &quot;@&quot; + serverName] = pm;

		renderTabs();
	}

	switchTab(matched[1] + &quot;@&quot; + serverName);
}
	  </pre>
<p>IRCのチャンネル内で発言するにはPRIVMSGによるかNOTICEによるかのどちらかであり、<code>onNOTICE</code>はそのうちNOTICEメッセージを、<code>onPRIVMSG</code>は PRIVMSGメッセージを扱う。PRIVMSGメッセージは引数がユーザの時には1対1のプライベートメッセージのためのメッセージとしても使用され る。このプライベートメッセージを受け取ると、対象ユーザと会話するためのタブが存在しない場合は新たに1対1会話用のタブを生成して<code>mapPrivateMessageBuffer</code>へ追加する。<code>openPrivateMessage</code>はユーザがチャンネル参加者リストの中の1対1で会話したいメンバーをクリックしたときに新しくタブを生成する関数である。</p>
<pre class="brush: cpp;">
function onTOPIC(param)
{
	var matched = param.match(/\S+\s+(\S+)\s+:(.*)/);
	if (matched == null)
		return;

	var c = mapChannelBuffer[matched[1]];
	if (!c)
		return;

	c.buffer += &quot;[Topic] &quot;;
	c.buffer += matched[2];
	c.buffer += &quot;&lt;br&gt;&lt;br&gt;&quot;;

	c.render(matched[1]);
}

function onNAMREPLY(param)
{
	var matched = param.match(/=\s+(\S+)\s+:(.+)/);
	if (matched == null)
		return;

	var c = mapChannelBuffer[matched[1]];
	if (!c)
		return;

	var a = matched[2].split(&quot; &quot;);
	for (var i in a)
	{
		switch (a[i].charAt(0))
		{
		case &quot;@&quot;:
			c.addMember(ChannelMember(&quot;@&quot;, a[i].substr(1)));
			break;
		case &quot;+&quot;:
			c.addMember(ChannelMember(&quot;+&quot;, a[i].substr(1)));
			break;
		default:
			c.addMember(ChannelMember(&quot;&quot;, a[i]));
			break;
		}
	}

	c.sortMembers();
	c.renderMembers(matched[1]);
}

function onWELCOME(prefix, param)
{
	// it's not necessarily true that welcome message is the first one the server sents in
	if (serverName != prefix)
		serverName = prefix;

	var matched = param.match(/(\S+)\s+:*/);
	if (matched != null)
	{
		myNick = matched[1];
		document.getElementById(&quot;nick&quot;).value = myNick;
	}

	passOutputStatusBuffer(param);
}
	  </pre>
<p><code>onTOPIC</code>はチャンネルトピックを通知するメッセージを、<code>onNAMEREPLY</code>はチャンネル参加者を列挙するメッセージを処理する。双方ともチャンネル参加時に送られてくるメッセージである。それに対し、<code>onWELCOME</code>は申請したニックネームがサーバに登録されサーバに受容されたときに送られてくるもので、ユーザのニックネームを含んでおり、申請したニックネームを重複などの理由によりサーバが勝手に変更した場合にも対処できるようにしてある。</p>
<pre class="brush: cpp;">
function onXButtonClick()
{
	for (var i in mapSpecialBuffer)
	{
		if (i == currentTab)
		{
			alert(&quot;This tab can't be closed&quot;);
			return;
		}
	}

	for (var i in mapChannelBuffer)
	{
		if (i == currentTab)
		{
			send(&quot;PART &quot; + currentTab);

			delete mapChannelBuffer[currentTab];

			renderTabs();
			switchTab(&quot;status&quot;);
			return;
		}
	}

	for (var i in mapPrivateMessageBuffer)
	{
		if (i == currentTab)
		{
			delete mapPrivateMessageBuffer[currentTab];

			renderTabs();
			switchTab(&quot;status&quot;);
			return;
		}
	}

	alert(&quot;Invalid buffer&quot;);
}

function onClearThisBuffer()
{
	document.getElementById(&quot;buttonClearBuffer&quot;).blur();

	if (!confirm(&quot;Do you really want to clear the content of the current buffer?&quot;))
		return;

	for (var i in mapSpecialBuffer)
	{
		if (currentTab == i)
		{
			mapSpecialBuffer[currentTab].buffer = &quot;&quot;;
			mapSpecialBuffer[currentTab].renderBuffer(currentTab);
			return;
		}
	}

	for (var i in mapChannelBuffer)
	{
		if (currentTab == i)
		{
			mapChannelBuffer[currentTab].buffer = &quot;&quot;;
			mapChannelBuffer[currentTab].renderBuffer(currentTab);
			return;
		}
	}

	for (var i in mapPrivateMessageBuffer)
	{
		if (currentTab == i)
		{
			mapPrivateMessageBuffer[currentTab].buffer = &quot;&quot;;
			mapPrivateMessageBuffer[currentTab].renderBuffer(currentTab);
			return;
		}
	}

	alert(&quot;Invalid buffer&quot;);
}
	  </pre>
<p><code>onXButtonClick</code>は、上述のように、タブ右上のXボタンを押すとそのタブを閉じることが出来るというもので、同時にそのタブが表象していたチャンネルを離脱するようになっている。<code>onClearThisBuffer</code>は、操作パネルのタブの内容をクリアするボタンを押したときの動作を記述している。</p>
<pre class="brush: cpp;">
function loadChannelList()
{
	if (!isConnected)
	{
		alert(&quot;Not connected&quot;);
		return;
	}

	channelListBuffer.buffer += &quot;Now loading...&quot;;
	refreshCurrentBuffer(&quot;channels&quot;, channelListBuffer.buffer);

	sendHTTP('' , 'GET', './channels', showChannels, true);
}

function showChannels(hr)
{
	var nodelist = hr.responseXML.getElementsByTagName(&quot;Channel&quot;);
	if (!nodelist)
	{
		channelListBuffer.buffer = channelListHeader;
		channelListBuffer.buffer += &quot;Currently there are no channels on the server.&quot;;
		refreshCurrentBuffer(&quot;channels&quot;, channelListBuffer.buffer);

		return;
	}

	var output = nodelist.length.toString();
	output += (nodelist.length == 1 ? &quot; channel has been retrieved.&lt;br&gt;&lt;br&gt;&quot; : &quot; channels have been retrieved.&lt;br&gt;&lt;br&gt;&quot;);

	for (var i = 0; i &lt; nodelist.length; ++i)
	{
		var n = nodelist.item(i);
		if (n == null)
			continue;

		var users = n.getAttribute(&quot;Users&quot;);

		var nn = n.firstChild;
		var name = &quot;&quot;;
		var topic = &quot;&quot;;

		while (nn != null)
		{
			if (nn.nodeName == &quot;Name&quot;)
			{
				// nn.textContent == Mozilla only
				var a = nn.firstChild;
				// NODE_TEXT == 3 || NODE_CDATA_SECTION == 4
				while (a != null &amp;amp;&amp;amp; (a.nodeType == 3 || a.nodeType == 4))
				{
					name = a.nodeValue;
					a = a.nextSibling;
				}
			}
			else if (nn.nodeName == &quot;Topic&quot;)
			{
				var a = nn.firstChild;
				// NODE_TEXT == 3 || NODE_CDATA_SECTION == 4
				while (a != null &amp;amp;&amp;amp; (a.nodeType == 3 || a.nodeType == 4))
				{
					topic = a.nodeValue;
					a = a.nextSibling;
				}
			}

			nn = nn.nextSibling;
		}

		output += (i + 1).toString();
		output += &quot;. &quot;;
		output += &quot;&lt;a style=\&quot;text-decoration: none;\&quot; href=\&quot;#\&quot; onclick=\&quot;joinChannelFromList('#&quot;;
		output += name;
		output += &quot;')\&quot;&gt;#&quot;;
		output += name;
		output += &quot;&lt;/a&gt; &quot;;
		output += topic;
		output += &quot; (&quot;;
		output += users;
		output += &quot; users)&lt;br&gt;&lt;br&gt;&quot;;
	}

	channelListBuffer.buffer = channelListHeader;
	channelListBuffer.buffer += output;

	refreshCurrentBuffer(&quot;channels&quot;, channelListBuffer.buffer);
}

function joinChannelFromList(channelName)
{
	for (var i in mapChannelBuffer)
	{
		if (i == channelName)
		{
			switchTab(channelName);
			return;
		}
	}

	send(&quot;JOIN &quot; + channelName);
}
	  </pre>
<p><code>loadChannelList</code>は、AJAXでサーバからIRCチャンネルリストを得る関数である。<a href="../../dice/index_j.html" target="_blank">DICE</a>は、/channelsパスにアクセスするとIRCチャンネルリストをXMLで返す。IRC側にもチャンネルリストを得るコマンドは勿論存在するが、ここでは元々webチャンネルブラウザのために実装したインターフェイスを再利用している。そのコールバック関数が<code>showChannels</code>で、AJAXリクエスト完了時に受け取ったXMLを処理する。<code>joinChannelFromList</code>は、チャンネルリスト上のチャンネル名をクリックしたときに呼ばれる関数で、目的のチャンネルへユーザを参加させる。</p>
<pre class="brush: cpp;">
function onKeyDown(e)
{
	if (e.keyCode == 13 &amp;amp;&amp;amp; isConnected) // Enter
	{
		sendMessage();
	}
}
 </pre>
<p>これがJavaScriptコードの最後の部分で、先に挙げたHTMLのデザイン部分が後に続く。<code>onKeyDown</code>はキー入力を監視し、Enterキーが押されるとメッセージ欄に記入されているメッセージを送信する。このバージョンではIEだとEnterキー押下時に音が鳴るが、formタグの使用を止めれば回避できる。このIRCクライアントwebアプリケーションは<a href="../../dice/index_j.html" target="_blank">DICE</a>のパッケージに同梱されているので、デザインも一新されている最新版での実際の動作は<a href="../../dice/index_j.html" target="_blank">DICE</a>をインストールして確かめてみて欲しい(本記事で掲載しているバージョンのActionScriptコードに存在する日本語JISコードの取り扱いに関するバグはそちらでは修正済みである)。</p>
<p>AJAXやWeb 2.0と形容されるものを超えた先にあるものは何か?  webブラウザがHTTPではないプロトコルのサーバに自由に接続することが当たり前のようになるとき、webブラウザはwebを超えたプラットフォーム となり、webそのものの刷新が起こるのではないか。そのときwebブラウザはwebブラウザではない他の物に、webはwebではない他の存在になって いるのかもしれない。</p>
<p>(2008-04-11追記) 現時点で、Adobe Flash Player 9.0.124.0でのFlashのセキュリティ仕様変更により、本稿のクライアントを動作させるにはサーバ側の対応が必要となる。<a href="../../dice/index_j.html" target="_blank">DICE</a>ではバージョン0.9.1.1で対応完了している。</p>
]]></content:encoded>
			<wfw:commentRss>http://zzz.zggg.com/2006/11/12/server-push-today-marginal-ajax/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
