Ajax と XML: チャットのための Ajax

Ajax と PHP を利用してチャット・アプリケーションを作成する

Ajax (Asynchronous JavaScript™ + XML) と PHP を使って Web アプリケーションにチャット・システムを組み込む方法を学んでください。チャット・システムを組み込めば、特別なインスタント・メッセージ・ソフトウェアをダウンロードまたはインストールしなくても、サイトのコンテンツについてサイトの利用者が意見を述べたり、利用者同士が話し合えるようになります。

Jack D. Herrington, Senior Software Engineer, Leverage Software Inc.

Jack D. Herrington は、20 年以上の経験を持つシニア・ソフトウェア・エンジニアです。彼の著書には、『、『Code Generation in Action』、『Podcasting Hacks』、『PHP Hacks』の 3 冊があります。また、彼はこれまで 30 本以上の記事を書いています。連絡先は jherr@pobox.com です。



2007年 12月 04日

Web 2.0 の話題となると、開発者たちはコミュニティーについて雄弁に語り出します。そして大げさに誇張していると思うかもしれませんが、サイトの利用者や読者が身近なトピックやサイトで販売している製品についてその場で話し合えるようにするという考えは、かなり魅力的な発想です。しかし、どうやってそれを実現するのでしょう。製品リストと同じページにチャット・ボックスを配置して、利用者が特別なソフトウェアや、さらには Adobe Flash Player でさえもインストールしなくて済むようにできるのでしょうか。いや、ご心配には及びません。PHP、MySQL、DHTML (Dynamic HTML)、Ajax、それに Prototype.js ライブラリーといった容易に手に入れられる無料のツールだけで実現できます。

前置きはこれくらいにして、早速実装に取り掛かりましょう。

ログイン

チャットする際の最初のステップは ID を取得することです。それには、リスト 1 に記載するような基本的なログイン・ページが必要になります。

リスト 1. index.html
<html>
<head><title>Chat Login</title></head>
<body>
<form action="chat.php" method="post">
Username: <input type="text" name="username">
<input type="submit" value="Login">
</form>
</body>
</html>

図 1 は、このページのスクリーンショットです。

図 1. チャット用ログイン・ウィンドウ
チャット用ログイン・ウィンドウ

注: このサンプルでこのログイン・ページが必要なのは、誰が何を言っているのかを判別するためだけです。実際のアプリケーションで既にログインが済んでいる場合は、ログイン時のユーザー名を使用して構いません。


基本的なチャット・システム

チャット・システムとは単なる文字列のテーブルで、このテーブルに含まれるそれぞれの文字列がチャットに参加している誰かに属しています。もっとも基本的なスキーマをリスト 2 に記載します。

リスト 2. chat.sql
DROP TABLE IF EXISTS messages;

CREATE TABLE messages (
	message_id INTEGER NOT NULL AUTO_INCREMENT,
	username VARCHAR(255) NOT NULL,
	message TEXT,
	PRIMARY KEY ( message_id )
);

上記のスクリプトには、自動的にインクリメントされるメッセージ ID、ユーザー名、メッセージが含まれています。メッセージがいつ送信されたかをわかるようにすることが重要と考えるなら、各メッセージにタイムスタンプを追加することもできます。

それぞれにトピックが異なる複数の会話を管理するには、トピックを追跡するためのテーブルを別に用意し、関連 topic_id を messages テーブルに含める必要があります。しかしこのサンプルは単純にしておきたいので、可能な限り単純なスキーマを使用しています。

データベースをセットアップしてこのスキーマをロードするために、以下の命令を使用しました。

% mysqladmin create chat
% mysql chat < chat.sql

MySQL サーバーのセットアップとそのセキュリティー設定およびパスワードによって、使用するコマンドは多少異なってきます。

チャットの基本ユーザー・インターフェース (UI) をリスト 3 に記載します。

リスト 3. chat.php
<?php
if ( array_key_exists( 'username', $_POST ) ) {
  $_SESSION['user'] = $_POST['username'];
}
$user = $_SESSION['user'];
?>
<html>
<head><title><?php echo( $user ) ?> - Chatting</title>
<script src="prototype.js"></script>
</head>
<body>

<div id="chat" style="height:400px;overflow:auto;">
</div>

<script>
function addmessage()
{
  new Ajax.Updater( 'chat', 'add.php',
  {
     method: 'post',
     parameters: $('chatmessage').serialize(),
     onSuccess: function() {
       $('messagetext').value = '';
     }
  } );
}
</script>

<form id="chatmessage">
<textarea name="message" id="messagetext">
</textarea>
</form>

<button onclick="addmessage()">Add</button>

<script>
function getMessages()
{
  new Ajax.Updater( 'chat', 'messages.php', {
    onSuccess: function() { window.setTimeout( getMessages, 1000 ); }
  } );
}
getMessages();
</script>

</body>
</html>

スクリプトの先頭では、ログイン・ページで送信された引数からユーザー名を取得して、それをセッションに保存しています。このページでは続いて非常に重要な Prototype.js JavaScript ライブラリーをロードします。このライブラリーが、すべての Ajax 作業を自動的に処理してくれます。

ライブラリーのロードが完了すると、ページにはチャット・メッセージを表示する領域が用意されます。この領域には、ファイルの終わりにある getMessages() JavaScript 関数によってメッセージが入力されます。

メッセージ領域の下にあるのは、フォーム、ユーザーがメッセージ・テキストを入力する textarea、そしてチャットにメッセージを追加する Add というラベルが付いたボタンです。

このページは図 2 のように表示されます。

図 2. 単純なチャット・ウィンドウ
単純なチャット・ウィンドウ

getMessages() 関数をよく見てみると、このページは 1,000 ミリ秒 (1 秒) 毎にサーバーをポーリングして新しいメッセージがあるかどうかをチェックし、ある場合はそれをページ先頭のメッセージ領域に表示していることがわかります。ポーリングについては後で詳しく説明することにして、ここではとりあえず現行のメッセージ・セットを返す messages.php ページの説明をして、チャットの基本実装を完了したいと思います。このページはリスト 4 のとおりです。

リスト 4. messages.php
<table>
<?php
// Install the DB module using 'pear install DB'
require_once 'DB.php';

$db =& DB::Connect( 'mysql://root@localhost/chat', array() );
if (PEAR::isError($db)) { die($db->getMessage()); }

$res = $db->query('SELECT * FROM messages' );
while( $res->fetchInto( $row ) )
{
?>
<tr><td><?php echo($row[1]) ?></td>
<td><?php echo($row[2]) ?></td></tr>
<?php
}
?>
</table>

スクリプトの先頭では、DB ライブラリーによってデータベースに接続しています。この DB ライブラリーは PEAR から入手することができます (「参考文献」を参照)。このライブラリーをまだインストールしていないのであれば、以下のコマンドでインストールすることができます。

% pear install DB

PEAR がインストールされていれば、スクリプトを使って現行のメッセージにクエリーを実行して各行をフェッチし、ユーザー名とコメント・テキストを出力することができます。

最後のスクリプトとなる add.php は、ページ上で addmessage() 関数に含まれる Prototype.js Ajax コードから呼び出されるスクリプトです。このスクリプトはセッションからメッセージ・テキストとユーザー名を取得し、新しい行を messages テーブルに挿入します。このコードは、リスト 5 のとおりです。

リスト 5. add.php
<?php
// Install the DB module using 'pear install DB'
require_once 'DB.php';

$db =& DB::Connect( 'mysql://root@localhost/chat', array() );
if (PEAR::isError($db)) { die($db->getMessage()); }

$sth = $db->prepare( 'INSERT INTO messages VALUES ( null, ?, ? )' );
$db->execute( $sth, array( $_SESSION['user'], $_POST['message'] ) );
?>
<table>
<?php
$res = $db->query('SELECT * FROM messages' );
while( $res->fetchInto( $row ) )
{
?>
<tr><td><?php echo($row[1]) ?></td>
<td><?php echo($row[2]) ?></td></tr>
<?php
}
?>
</table>

add.php スクリプトは現行のメッセージ・リストも返します。これは、当初のページでは Ajax コードが戻り HTML コードからチャット・メッセージを更新するためです。この振る舞いによって、ユーザーが会話に追加したコメントはすぐにユーザーにフィードバックされることになります。

以上が、チャット・システムの基本です。次のセクションでは、ポーリングを効率化する方法について説明します。


チャットをより快適にする方法

当初のチャット・システムでは、ページが毎秒、会話のチャット・メッセージすべてを要求します。この動作は短い会話であればそれほど問題にはなりませんが、会話が長く続いている場合にはパフォーマンスに大きな影響を与えます。幸いなことに、これにはかなり簡単なソリューションがあります。message_id は各メッセージに関連付けられており、その番号は順番にインクリメントされていきます。そこで、特定の ID に関連付けられたメッセージがあることがわかっている場合、その ID の後に発生するメッセージを要求すれば、メッセージ・トラフィックを大々的に抑えられるというわけです。ほとんどの要求で新しいメッセージを取得することがないため、パケットは非常に小さくなります。

セットアップをより効率的な設計に変えるには、chat.php ページに多少の変更が必要です (リスト 6 を参照)。

リスト 6. chat.php (修正後)
<?php
if ( array_key_exists( 'username', $_POST ) ) {
  $_SESSION['user'] = $_POST['username'];
}
$user = $_SESSION['user'];
?>
<html>
<head><title><?php echo( $user ) ?> - Chatting</title>
<script src="prototype.js"></script>
</head>
<body>

<div style="height:400px;overflow:auto;">
<table id="chat">
</table>
</div>

<script>
function addmessage()
{
  new Ajax.Request( 'add.php', {
     method: 'post',
     parameters: $('chatmessage').serialize(),
     onSuccess: function( transport ) {
       $('messagetext').value = '';
     }
  } );
}
</script>

<form id="chatmessage">
<textarea name="message" id="messagetext">
</textarea>
</form>

<button onclick="addmessage()">Add</button>

<script>
var lastid = 0;
function getMessages()
{
  new Ajax.Request( 'messages.php?id='+lastid, {
    onSuccess: function( transport ) {
      var messages = transport.responseXML.getElementsByTagName( 'message' );
      for( var i = 0; i < messages.length; i++ )
      {
        var message = messages[i].firstChild.nodeValue;
        var user = messages[i].getAttribute('user');
        var id = parseInt( messages[i].getAttribute('id') );

        if ( id > lastid )
        {
          var elTR = $('chat').insertRow( -1 );
          var elTD1 = elTR.insertCell( -1 );
          elTD1.appendChild( document.createTextNode( user ) );
          var elTD2 = elTR.insertCell( -1 );
          elTD2.appendChild( document.createTextNode( message ) );

          lastid = id;
        }
      }
      window.setTimeout( getMessages, 1000 );
    }
  } );
}
getMessages();
</script>

</body>
</html>

上記では、すべてのメッセージを保持していた "chat" の <div> タグの代わりに <table> タグを使用しています。このタグには、新しいメッセージを受け取るたびに動的に行を追加します。ご覧のとおり、getMessages() 関数は変更され、最初のバージョンに比べていくらか大規模になっています。

この新しいバージョンの getMessages() は、messages.php ページの結果が新規メッセージの XML ブロックであることを想定しています。messages.php も id というパラメーターを取るようになっています。このパラメーターは、ページに表示された最後のメッセージの message_id です。最初この ID は 0 であるため、messages.php ページはそこに含まれるすべてのメッセージを返しますが、それ以降 getMessages() はページに表示された最後のメッセージの ID を送信します。

XML 応答は onSuccess ハンドラーによって分解され、それぞれの要素は insertRow()insertCell()appendChild() などの標準DHTML の DOM (Document Object Model) 関数によってテーブルに追加されます。

リスト 7 のアップグレードされた messages.php ファイルは、HTML でなく XML を返します。

リスト 7. messages.php
<?php
// Install the DB module using 'pear install DB'
require_once 'DB.php';

header( 'Content-type: text/xml' );

$id = 0;
if ( array_key_exists( 'id', $_GET ) ) { $id = $_GET['id']; }

$db =& DB::Connect( 'mysql://root@localhost/chat', array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
?>
<messages>
<?php
$res = $db->query( 'SELECT * FROM messages WHERE message_id > ?', $id );
while( $res->fetchInto( $row ) )
{
?>
<message id="<?php echo($row[0]) ?>" user="<?php echo($row[1]) ?>">
<?php echo($row[2]) ?>
</message>
<?php
}
?>
</messages>

この新しい拡張バージョンを図 3 に記載します。

図 3. 最適化されたチャット・ウィンドウ
最適化されたチャット・ウィンドウ

ルック・アンド・フィールの点では何も変わっていませんが、当初よりも遥かに効率的になっています。


「リアルタイム」の神話

Ajax の初心者や現場での経験と知識が豊富な良識あるプログラマーにとって、「ポーリング」という考えは背筋がぞっとするものかもしれませんが、チャット・システムではあいにくポーリングが唯一の手段となります。クライアントにも、サーバーにも特別なソフトウェアをインストールせずに、この両者を継続的に接続するクロスプラットフォームかつクロスブラウザーの方法はありません。そのような方法があったとしても、それを実現するには特殊なファイアウォール構成が必要になってくるでしょう。したがって、誰もが使える簡単なソリューションとしては、Ajax とポーリングを使用するしか手がないのです。

それにしても、製品紹介で「リアルタイム」と主張されているのはどういったことでしょうか。ポーリングがリアルタイムのはずはありませんが、実はリアルタイムにできるのでしょうか。それは、各自がどのようにリアルタイムを定義するかによると思います。例えば私が電気生理学のデータを取得するためのコードを作成したとき、リアルタイムとは数マイクロ秒のことでした。地質学者であれば、数分、数日、あるいは場合によっては数年をリアルタイムと見なすこともあるでしょう。

ウィキペディアを調べてみると、人間の平均反応時間は 200 ミリ秒から 270 ミリ秒とあります。これは、ボールを打つなどの行動についての時間でしかありません。メッセージを読み取ってそれに対する返信を考え始めるのにかかる時間は、たとえ会話に没頭しているとしても、それより遥かに長いはずです。したがって、チャット・メッセージを待つ時間が 200 ミリ秒程度 (あるいはそれより多少長い程度) であれば、実際にはまったく問題ないのです。このサンプルではポーリング間隔を 1 秒にしていますが、それでもあまり気になりません。

developerWorks の Ajax フォーラム (「参考文献」を参照) でモデレーターを務めていると、ポーリングとリアルタイムの問題が少なくとも月 1 回は話題に上ります。Ajax でのポーリングとリアルタイムの問題については、このフォーラムで真相に迫れているとよいのですが。私が提案しているのは、途方もなく複雑なリアルタイム・ソリューションを作成する前に、ポーリングを試してみることです。せめてカスタムのソリューションを作成する前に、簡単に入手できるツールで何ができるかを調べてください。


ここから先の展開

この記事の内容が、アプリケーション内に独自のチャット・システムを実装する出発点になることを願っています。さらにこのチャット・システムを以下のように拡張することが考えられます。

  • ユーザーの追跡: アクティブに会話に参加しているユーザーのリストをチャットの横に配置します。こうすることによって、誰が現在チャットに加わっているか、そしてユーザーが参加したこと、退場したことがわかるようにします。
  • 複数の会話の許可: それぞれに話題が異なる複数の会話を同時進行できるようにします。
  • エモティコン (顔文字) の許可: :-) などの文字グループを該当する笑顔の画像に変換します。
  • URL 解析の使用: クライアント側 JavaScript コードで正規表現を使用して URL を検索し、見つかった URL をハイパーリンクに変換します。
  • Enter キーの操作: Add ボタンを使用する代わりに textarea で onkeydown イベントを利用して、ユーザーが Enter キーまたは Return キーを押したかどうかを監視します。
  • ユーザー入力時の表示: ユーザーによる入力開始時にサーバーにアラートを出し、返信が作成中であることが他の参加者にわかるようにします。これにより、ユーザーが入力に時間をかけている場合に、会話が終わってしまったと認識されることが少なくなります。
  • 送信メッセージのサイズ制限: 会話を維持するもう 1 つの方法は、メッセージのサイズを小さく抑えることです。この場合も onkeydown イベントを利用することによって textarea の最大文字数を制限し、会話が迅速に進むようにします。

上記はこの記事に記載したコードに考えられる拡張のほんの数例でしかありません。他の拡張を行って、それをコミュニティーと共有したい場合には、ぜひお知らせください。「ダウンロード」から入手できるソース・コードに組み入れたいと思います。


まとめ

正直言って私はたいしたチャッターではありません。自分でチャット・クライアントを立ち上げたこともなければ、テキスト・メッセージを使うのもごくまれです。私のチャットのハンドルネームは冗談抜きで idratheryouemail となっています。そうは言っても、この記事で説明したような、状況に即したチャットは実に魅力的だと思います。なぜなら、このようなチャットではサイトで扱っている主題に焦点が絞られ、最新の「TomKat (訳注: トム・クルーズとケイティ・ホームズの愛称)」ニュースなどにまつわる気の散る話が最小限に抑えられるからです。

この記事のサンプル・コードを Web アプリケーションで試してみてください。サイトの読者や利用者をリアルタイムの会話に夢中にさせられるかどうかを調べて、その結果をdeveloperWorks Ajax フォーラム・サイトに報告してください。あなたにとって結果が嬉しい驚きになることを期待しています。


ダウンロード

内容ファイル名サイズ
Source code for chat applicationx-ajaxxml8-chat.zip38KB

参考文献

学ぶために

  • PHP ホーム・ページ: PHP プログラマーのための貴重な情報源にアクセスしてください。
  • Prototype ライブラリー: 動的 Web アプリケーションの開発を簡易化するために設計されたこの JavaScript フレームワークについて調べてください。
  • Scriptaculous JavaScript ライブラリー: この Prototype ベースのフレームワークで、Web サイトを成功させるための表示支援機能と効果を見つけてください。
  • Prototype.js 資料ページ: Prototype JavaScript ライブラリーについての詳細を調べてください。ここには、正式な Prototype ブログをはじめとする豊富な資料へのリンクが記載されています。
  • jQuery: Prototype.js と同様の機能を提供するもう 1 つの JavaScript ライブラリーです。
  • Yahoo! UI Library: Yahoo! の Ajax 対応ツールキットです。
  • developerWorks XML ゾーン: developerWorks XML ゾーンで XML のすべてについて学んでください。
  • IBM XML 認証: XML や関連技術の IBM 認定開発者になる方法について調べてください。
  • XML Technical library: 広範な技術に関する記事とヒント、チュートリアル、標準、そして IBM レッドブックについては、developerWorks XML ゾーンを参照してください。
  • developerWorks technical events and webcasts: これらのセッションで最新情報を入手してください。

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Web development
ArticleID=282129
ArticleTitle=Ajax と XML: チャットのための Ajax
publish-date=12042007