ブラウザに表示されるチャットのページは、次の図3のように非常に単純なものです。名前とメッセージを入力して送信すると、図のようにチャットログが表示されます。
図3 チャットのページ
このページ、js-chat.htmlのHTMLソースを次に示します。
1: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2: <html>
3: <head>
4: <meta http-equiv="Content-Type" content="text/html;
charset=UTF-8" />
5: <title>JavaScript Chat</title>
6: <script type='text/JavaScript' src='dwr/interface/JsChat.js'>
</script>
7: <script type='text/JavaScript' src='dwr/engine.js'> </script>
8: <script type='text/JavaScript' src='dwr/util.js'> </script>
9: <script type="text/JavaScript">
10: function init() {
11: dwr.engine.setActiveReverseAjax(true);
12: sendMessage();
13: }
14: function sendMessage() {
15: var name = DWRUtil.getValue("name");
16: var text = DWRUtil.getValue("text");
17: DWRUtil.setValue("text", "");
18: JsChat.addMessage(name, text);
19: }
20: function receiveMessages(logs) {
21: DWRUtil.removeAllOptions("chatlog");
22: DWRUtil.addOptions("chatlog", logs);
23: }
24: </script>
25: </head>
26: <body onload="init()">
27: JavaScript Chat
28: <hr />
29: <ul id="chatlog" style="list-style-type: none">
30: </ul>
31: <hr />
32: お名前
33: <input id="name" size="12" />
34: <input id="text" size="40"
35: onkeypress="DWRUtil.onReturn(event, sendMessage)" />
36: <input type="button" value="送る" onclick="sendMessage()" />
37: </body>
38: </html> |
6〜8行目で読み込んでいる3つのjsファイルは、DWRではもうおなじみのファイルです。
JsChat.js(6行目)はサーバ側のJavaオブジェクトを呼び出すためのインターフェイスを提供しています。engine.js(7行目)はDWRを使用するときに必須なJavaScriptファイルです。util.js(8行目)は便利なユーティリティ機能を提供してくれます。この例でも、値の取得や設定に使用しています。
10行目の「init()」は26行目のonloadによりHTMLファイルが読み込まれた後に、一度だけ呼び出されます。11行目の
dwr.engine.setActiveReverseAjax(true); |
は、前述したように、リバースAjaxを有効にするために必ず必要な設定です。12行目の「sendMessage()」はチャットのログをリフレッシュさせるために呼んでいます。
14行目の「sendMessage()」がチャットへの書き込みをサーバ側に送信する関数です。15、16行目で、入力フォームから名前とメッセージを取得しています。17行目で、入力されたメッセージ・フォームをクリアし、18行目でサーバ側のメソッドを呼び出しています。
18行目で注意してほしいのは、コールバック関数の指定がないことです。前回までのDWRのサンプルでは、サーバ側のメソッドを呼び出す場合、かならず最後の引数にコールバック関数を指定していました。コールバック関数を指定すると、非同期通信により呼び出したサーバ側のメソッドが終了したあとに、指定したコールバック関数が呼び出され、メソッド呼び出しの結果を受け取れました。
プッシュ型では、リバースAjaxの機能によって、サーバ側からクライアント側のJavaScript関数を呼び出して結果を通知するため、コールバック関数は必要ありません。
20行目の「receiveMessages」関数がリバースAjaxによってサーバ側から呼び出される関数です。引数には、チャットのログとして文字列の配列が渡ってきます。21行目で、チャットのログをクリアし、22行目でチャットのログ領域(29、30行目の「ul」タグ)にログを書き込みます。
35行目のDWRUtil.onReturn()は、リターンキーが押されたときに送信するためのユーティリティ関数です。ブラウザによって、リターンキーが押されたときの振る舞いが微妙に異なるため、この関数を利用して差異を吸収しています。
サーバ側では、次の処理を行います。
- チャットへの書き込みを受け取ると、
- チャットのログに書き足し、
- 最後に、ログをすべてのブラウザに通知
チャットへの書き込みはサーバ側のメソッド呼び出しになります。クライアント(ブラウザ)のJavaScriptから「addMessage()」メソッドを呼び出します。ソースコードは次の通りです。
1: package com.sample.chat;
2:
3: import java.util.Collection;
4: import java.util.ArrayList;
5: import java.util.Iterator;
6:
7: import org.directwebremoting.ScriptBuffer;
8: import org.directwebremoting.ScriptSession;
9: import org.directwebremoting.WebContext;
10: import org.directwebremoting.WebContextFactory;
11: import org.directwebremoting.annotations.*;
12: import org.directwebremoting.proxy.dwr.Util;
13:
14: @RemoteProxy(scope=ScriptScope.APPLICATION)
15: public class JsChat {
16: private ArrayList<String> messages = new ArrayList<String>();
17:
18: @RemoteMethod
19: public void addMessage(String nm, String txt) {
20: if (nm.trim().length() == 0) {
21: nm = "Guest";
22: }
23: if (txt.trim().length() > 0) {
24: messages.add(nm + ": " + txt);
25: while (messages.size() > 10) {
26: messages.remove(0);
27: }
28: }
29:
30: ScriptBuffer script = new ScriptBuffer();
31: script.appendScript("receiveMessages(")
32: .appendData(messages)
33: .appendScript(");");
34:
35: WebContext wctx = WebContextFactory.get();
36: String currentPage = wctx.getCurrentPage();
37:
38: Collection<ScriptSession> pages
= wctx.getScriptSessionsByPage(currentPage);
39: for (ScriptSession otherSession : pages) {
40: otherSession.addScript(script);
41: }
42: }
43: } |
チャットへの書き込みはサーバ側のメソッド呼び出しになります。クライアント(ブラウザ)のJavaScriptから「addMessage()」メソッドを呼び出します。
20〜28行目がチャットのログを作成している部分になります。ここでは、チャットのログを文字列の配列として実現しており、チャットに書き込まれた内容は配列の最後に追加されます(24行目)。
もし、ログの長さが10行を超えると、最初の行を削除します(25〜27行目)。
サーバ側からブラウザに送信するJavaScriptのコードを組み立てるには、ScriptBufferを使用します。名前から想像されるように、StringBufferと似ています。
ScriptBufferには、大きく分けて2種類のappendメソッドが容易されています。「appendScript()」と「appendData()」です。JavaScriptのコードには、「appendScript()」を使用します(31、33行目)。Object、文字列やプリミティブな値を追加する場合には、「appendData()」を使用します(32行目)。
30〜33行目で、チャットログの配列を引数とする「recieveMessages()」関数の呼び出しコードを構築しています。
次に、組み立てたJavaScriptのコードを、チャットのページに接続しているすべてのブラウザに送信します。
35行目でカレントのWebContextオブジェクトを取り出しています。このオブジェクトを利用すると、サーブレットの情報を取得できます。36行目では、このオブジェクトを利用してページのパス名を取得しています。
38行目で、このページに接続しているブラウザの情報(ScriptSession)をコレクションとして取得し、39、40行目のforeachループで各ブラウザにスクリプトを送信しています。
お気付きのように、今回はアノテーションを利用することによってdwr.xmlを作成しなくても済むようにしています。web.xmlは次のようになります。
classesパラメータにアノテーションを使用しているクラス名をカンマで区切って指定しています。なお、com.sample.chat.JavaChatはスクリプトプロキシーを利用したクラスであり、後ほど解説します。リバースAjaxを利用する場合は、activeReverseAjaxEnabledパラメータをtrueに設定します。
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>
org.directwebremoting.servlet.DwrServlet
</servlet-class>
<init-param>
<param-name>classes</param-name>
<param-value>
com.sample.chat.JsChat,
com.sample.chat.JavaChat
</param-value>
</init-param>
<init-param>
<param-name>activeReverseAjaxEnabled</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping> |
MyEclipseの[AJAXリクエスト・モニター]機能を利用すると、実際にどのようなデータがサーバ側からクライアント側に送信されているのかを観察できます。詳しくは連載第1回を参照してください。
次の図は、図3のチャットのある非同期通信をモニタリングした例です。
図4 チャットのモニタリング
ご覧のように、レスポンスに「receiveMessages(["Foo: Hi","Bar: Hello"]);」
という、31〜33行目で構築したJavaScriptのコードが見えます。このモニタリング結果から、確かにサーバ側からJavaScriptがブラウザ側に送信されていることが分かります。