わたしの前回の記事 (「参考文献」を参照) では、Javaサーブレットと2つの異なるクライアント側の技法を使用してサーバー側のメソッドを起動して、メッセージング・アーキテクチャーをサーバーとブラウザー間に配置するための基盤を築きました。この基盤にはメソッドを作成するための抽象基底クラス(abstruct base class)が含まれていて、ほとんどの最新ブラウザーからリモートで利用できるようになっています。この基盤の上にカテゴリー/サブカテゴリーのドロップダウン・ボックス(メニュー)の実際の例を作りました。"親"メニューの値によってサーバーから対応する値をとってくる事が必要か望ましいような場合です。
この記事で紹介するメッセージング・アーキテクチャーは、前回の記事で取り上げたものと、次の2つの点で大きく異なります。まず、クライアントは、ユーザー・アクションでメソッド・コールを起こすのではなく、サーバーを定期的にポーリングして新規メッセージを取り出します。2番目に、オリジナルのフレームワークでは、メッセージ・フォーマットがインプリメンテーションにまかされているのに対して、サーバーから取り出したメッセージのフォーマットが厳密に定義され、構文解析が容易に行えなければなりません。
基本的な考えは、サーバーからクライアントへ送られたメッセージを収集し、それぞれのメッセージを名前 (つまり、本質的にはメッセージ・タイプ)、テキスト値、および、オプションで属性 (名前 / 値のペア) の集合でカプセル化します。このようなリッチ・メッセージングにはXMLが理想的な選択ですが、残念ながら、最も制限された環境での使用を除いて、XMLをサポートしている信頼できるブラウザーはありません。IE5のXML data islandフィーチャーを使えば、XMLをメッセージ・フォーマットとして使用することができます。(これは、おそらく、Netscape 6や他のXML対応ブラウザーでも機能するでしょう。これについては読者の方への課題にしておきましょう。) しかしJavaScriptにも、同等のリッチ・メッセージを送信する機能が備わっています。したがって、このアーキテクチャーを利用すれば、アプリケーション固有のコードを変更することなく、いずれのフォーマットでも互換性をもって使用することができます (ただし、rsmsg_useJavaScriptFormat を使って希望のフォーマットを指定する場合を除きます)。
サーバー・サイド・コードには、リスト1 に示されているように、希望の名前、テキスト値、およびオプションの属性をカプセル化した RSMessage クラスが組み込まれています。
リスト 1.
RSMessage (サーバー・サイド Java クラス)
package com.ehatchersolutions.rs;
import java.util.Properties;
public class RSMessage {
private String name = "";
private String text = "";
private Properties attributes = new Properties();
public RSMessage(String n)
{
name = n;
}
public String getName ()
{
return name;
}
public void setText (String t)
{
text = t;
}
public String getText()
{
return text;
}
public void addAttribute (String name, String value)
{
attributes.setProperty(name, value);
}
public Properties getAttributes()
{
return attributes;
}
}
|
リモート・スクリプト・メッセージング・メソッドのアプリケーション固有のインプリメンテーションは、RSMessage オブジェクトの配列を戻します。この順序付きメッセージ・コレクションは、次に、クライアントが希望するフォーマットに応じて、JavaScriptまたはXML表記に変換されます。XMLのメッセージは次のようになります。
<Messages> <Message1 >message 1 data</Message1> <Message2 prop1="value" >msg2 data</Message2> </Messages> |
JavaScriptでの同じメッセージ・コレクションは次のようになります。
var ma=new Array();var a=new Array();var m=new Message
('Message1','message 1 data',a);ma[ma.length]=m;var a=new
Array();a['prop1']='value';var m=new Message('Message2','msg2
data',a);ma[ma.length]=m;var msgs=new Messages(ma);msgs |
ユーザーのWebブラウザー操作を邪魔しないために、JavaScriptを使用して、サーバーの非同期ポーリングを定期的に行います。rsmsg.js へ進み、JavaScriptタイマーが始動するたびに呼び出されるメソッドについて検討しましょう。_getMessages メソッドは、リスト2 に示されているような形でインプリメントされます。(注: 接頭部に下線が付いたメソッドと変数は、rsmsg.js 内部で使用されますので、直接アクセスすることはできません。)
リスト 2.
getMessages (rsmsg.js に定義されたクライアント・サイドJavaScript)
/**
* Invokes the remote scripting method.
*/
function _getMessages(){ var params = rsmsg_getParams();
var method = _rsmsg_method + ((_rsmsg_usingJavaScript) ? "_JavaScript" : "_XML");
if (_rsmsg_usingJSRS) {
// JSRS
jsrsExecute(_rsmsg_url, _processMessages, method, params);
} else {
// MSRS
var e = "RSExecute(_rsmsg_url, method";
if (params != null) {
for (var i=0; i < params.length; i++) {
e += ", '" + params[i] + "'";
}
}
e += ", _processMessagesMSRS);";
eval(e);
}
//reset timer
_setTimer();
}
|
_setTimer メソッド (組み込みコードのrsmsg.js を参照) は、デベロッパーが指定したインターバルに従い _getMessages の実行のトリガーとなるタイマーをセットします。このタイマーは、_getMessages の終了時にリセットされて、再度_getMessages の呼び出しのトリガーとなります。
第1回の記事で説明したように、クライアント・サイド・インプリメンテーションは、MicrosoftのRemote Scripting (MSRS) 機能、またはBrent AshleyのJavaScriptリモート・スクリプト (JSRS) 機能のいずれかを利用するように設計されています(これらに関する情報にリンクするには、「参考文献」を参照してください)。また、JavaScriptだけをメッセージ・フォーマットとして使用するのではなく、この柔軟なインプリメンテーションにより、クライアントは、メッセージ・フォーマットとしてJavaScriptまたはXMLのどちらを使用するかを決定することができます。リスト2 のコードは、実際よりもトリッキーに作られているように見えます (MSRSセクションのeval ) が、複雑になっているのはパラメータ処理を、一般化かつアプリケーションレベルで定義できるようにするためです。実際にこのコードがやっているのは単にRSExecute を呼び出しているだけです(MSRS定義のメソッドとして)。
クライアント・サイド・スクリプト作業の多くは、rsmsg.js でインプリメントされます。rsmsg.js は、メッセージングのふるまいを制御するいくつかの "パブリック" メソッドを公開します。これらのメソッドのうち、呼び出す必要があるのは3つだけです。rsmsg_setURL、rsmsg_setMethod、およびrsmsg_start です。その他のパラメーターは、デフォルト値を持っていて、明示的に呼び出す必要はありません。テーブル1 は、各メソッドの説明と、その使用法を例示したものです。
テーブル1: タスクおよびメソッドの定義
| タスク / 定義 | 例 |
|---|---|
rsmsg_setURL
[必須]
呼び出すリモート・メソッドが含まれているサーブレットにリンクするURLを定義します。 |
rsmsg_setURL("/servlets/com.ehatchersolutions.examples.rs.RSMessagingExample"); |
rsmsg_setMethod
[必須]
呼び出すリモート・メソッドを定義します。 |
rsmsg_setMethod("getMessages"); |
rsmsg_start
[必須]
メッセージ・ポーリング・タイマーを始動します。 |
rsmsg_start();
|
rsmsg_stop
メッセージ・ポーリング・タイマーを停止します | rsmsg_stop(); |
rsmsg_useJavaScriptFormat
(boolean)
true である場合に、メッセージをJavaScriptフォーマットで検索することを指定します。false であれば、XMLフォーマットが使用されます (このためには、rsxml XMLデータ・アイランドが存在していて、かつブラウザーがこのような形式でXMLをサポートしていることが必要です)。true がデフォルトです。 |
rsmsg_useJavaScriptFormat(document.form1.format[0].checked);
注: XMLフォーマット・メッセージを使用する場合は、以下のようにして、'rsxml' で示されたHTML |
rsmsg_useJSRS
(boolean)
true の場合にJSRSをリモート・スクリプト・メソッドとして使用することを指定します。false であれば、MSRSを使用します。true がデフォルトです。 |
rsmsg_useJSRS(document.form1.clientType[1].checked);
|
rsmsg_setInterval
(integer)
メッセージを検索するリモート・メソッド呼び出し間のインターバル (秒単位) を指定します。デフォルトは10秒です。 |
rsmsg_setInterval(parseInt(document.form1.interval.value));
|
上記の必須パラメーター設定のほかに、rsmsg_processMessage とrsmsg_getParams の2つのメソッドも定義する必要があります。
各メッセージの処理はアプリケーションによって異なるため、カスタマイズが必要です。rsmsg.js で作成されたフレームワークを使用すれば、メソッドやフォーマットに依存しないでメッセージを収集でき、またそれらを受け取ったら、個々に
rsmsg_processMessage に渡すことができます。rsmsg_processMessage のインプリメンテーションの例は、リスト3 に示されています。
リスト3:
rsmsg_processMessage (インプリメンテーションの例)
function rsmsg_processMessage(msg) {
switch (msg.nodeName) {
case "System" :
eval(msg.text);
break;
case "Message1":
break;
case "Message2":
break;
default : alert("Unknown Message: \n\n" + msg.xml);
}
log(msg);
}
|
メッセージを検索するサーバー・サイド・メソッドに対しては、パラメーターを渡す必要がある場合もない場合もあります。各リモート呼び出しごとに動的パラメーターが渡されるようにするには、このフレームワークをインプリメントする開発者は rsmsg_getParams も定義する必要があります。rsmsg_getParams は、リモート・メソッドへの引数となるJavaScriptのストリング配列 (または、リモート・メソッドが引数をとらない場合は、null ) を必ず戻します。これによって各リモート・メソッド呼び出しごとに、他のパラメーターより前にrsmsg_getParams が呼び出され、このため、ポーリング間隔などのパラメーターが変更可能になるという、よい副次効果が発生します。次に、rsmsg_getParams のインプリメンテーションの例を示します (リスト4 を参照)。
リスト 4.
rsmsg_getParams (インプリメンテーションの例)
function rsmsg_getParams()
{
// This method is called prior to any other parameters being accessed, so all
// messaging parameters can be modified dynamically here to affect the next
// remote method invocation
rsmsg_useJavaScriptFormat(document.form1.format[0].checked);
rsmsg_useJSRS(document.form1.clientType[1].checked);
rsmsg_setInterval(parseInt(document.form1.interval.value));
// just for grins, lets send the server something different every time
lastmsgid++;
return new Array(lastmsgid.toString());
}
|
上の例では、メッセージ・フォーマットとリモート・スクリプト・メソッドの両方が変更されます。この2つのパラメーターは変更されることはありませんから、上の例は一般的な状況とはいえません。インターバルを変更することさえ、このアーキテクチャーの典型的なアプリケーションとはいえませんが、必要であれば、これを使用してメッセージングを行う頻度を動的に制御することができます。メッセージ・フォーマット、リモート・スクリプト・メソッド、およびインターバルは、単に、使用可能なすべての機能を陳列し、これらのパラメーターを例示HTMLページから動的に制御できるようにするだけです。
rsmsg.js には、JavaScriptメッセージとXMLメッセージをフォーマットを意識することなく処理を行える、非常に良くできたJavaScriptのコードがいくつか含まれています。IE5のXML data island機能を使用すれば、XMLストリングを簡単にブラウザーDOMにロードすることができます。リスト5は、_processMessages インプリメンテーションを示しています。
リスト 5.
_processMessages (rsmsg.js に定義されたクライアント・サイドJavaScript)
function _processMessages(str)
{
var msgs = null;
if (_rsmsg_usingJavaScript) {
msgs = eval(str);
} else {
if (!rsxml.XMLDocument.loadXML(str)) {
alert ("Data load failed");
return;
}
msgs = rsxml.XMLDocument.documentElement.childNodes;
}
if (msgs == null) return;
for (var msg=msgs.nextNode(); msg; msg=msgs.nextNode()) {
rsmsg_processMessage(msg);
}
}
|
メッセージ・フォーマットがJavaScriptであれば、それは単に、Messagesオブジェクトを入手するための、結果をeval する問題に過ぎません。メッセージ・フォーマットがXMLであれば、結果がrsxml data islandにロードされます (ここでは、XML文書が構文解析されて文書オブジェクト・モデルになります)。_processMessages の最後の
for によるループは、msgs コレクションでループしますが、このコレクションは、XMLエレメント・ノードのセットまたはMessagesオブジェクトのいずれかです。JavaScriptはこれを見事に処理します。なぜならば、Messagesオブジェクト (rsmsg.js で定義されている) にはnextNode メソッドがあるからです (この操作は、Javaの異なる2つの基底クラス・オブジェクトで同じインターフェースをインプリメントするのと似ています)。rsmsg_processMessage も、この状況を適切に処理します。その場合、XMLエレメント・オブジェクトとMessageオブジェクトの両方で提供される属性を使用します。この2つのオブジェクトは、nodeName、text、getAttribute 属性、およびXMLを持っています (つまり、JavaScriptフォーマットを使用する場合でも、希望すれば、各メッセージごとにXMLフォーマットの結果を得ることができます)。
わたしの前回の記事では、RemoteScriptingServlet 抽象基底クラスを作成しました。この抽象基底クラスは、使用するリモート・スクリプト・メソッド (JSRSまたはMSRS) に応じ、Javaリフレクションを使用して該当するメソッドを検出し、それを起動し、その戻り値を適切にバンドルします。この場合、抽象基底クラスは、簡単なリファクターが行われ、新しい抽象 RSMessagingServlet クラスの基底クラスとして再使用されます。主要な変更として、適切なメソッドを検出して起動するコードを、invoke というオーバーライド可能なメソッドに移動します。RSMessagingServlet がinvoke にオーバーライドすると、処理方法が少し変わってきます。RSMessagingServlet を基底クラスとして使用してアプリケーション固有の具体的なクラスを作成しても、単に、RSMessages の配列を戻し、かつString引数のみをパラメーターとして受け取るパブリックの静的メソッドを、サブクラス化して作成するだけです。このメソッドには任意の数のパラメーターを指定できますが、それぞれのパラメーターはStringオブジェクトに限られています。
わたしは、データをどのように戻すかをサーバーに伝える方法をクライアントに指示する段階になって、パスが不明瞭であることに気付きました。HTTP要求を出すときに GET パラメーターを使用することもできましたが、そのためには、MSRSとJSRSの機能の仕方を微調整する必要がありました。また、それを隠蔽された "メソッド・パラメーター" にして、そのメソッドを起動する前に隠蔽を除去することもできました。代わりにわたしは、クライアントから送られたメソッド名にサフィックスを付けることにしました。クライアントがサフィックスを指定していない場合は、JavaScriptがデフォルトのメッセージ・フォーマットになります。起動した実際のメソッド名に "_JavaScript" または "_XML" を付加すると、サーブレットに希望のフォーマットが伝えられます。サフィックスの付加は、rsmsg.js の背後で処理されます。ずべての開発者が行わなければならないことは、rsmsg_setMethod を使用して実際のメソッド名を設定し、 rsmsg_useJavaScriptFormat で希望のフォーマットを指定することだけです。オーバーライドされた invoke メソッドは、正しいメソッドを起動した後、RSMessagingServlet によって提供されたメソッドtoXML またはtoJavaScript (RSMessages の配列を提供する) を呼び出します。invoke は、リスト 6 のようにインプリメントされます。
上記のすべてのフレームワークを考慮に入れると、サーバーからブラウザーへリッチ・メッセージを非同期送信するサーブレットを作成することは簡単です。この例はプロトタイプ的ですが、これを拡張して、キューイング・メカニズムから受け取ったメッセージをパッケージしたり、絶えず変わる情報を報告したりできます。このようなメソッドへ入力するパラメーターには、通常、クライアントID (多分、ブラウザー・ページを最初にロードしたときセッションIDから動的に指定されたもの)、最後のメッセージID、またはブラウザーの状態の値 (フォーム・フィールド値など) といった情報が含まれます。リスト7 は、その一例です。
リスト 7.
RSMessaging の例 (RSMessagingServlet使用のサーバー・サイドの例)
public class RSMessagingExample extends RSMessagingServlet {
public static RSMessage[] getMessages (String lastidstr) throws Exception
{
RSMessage[] msgArray = new RSMessage[2];
RSMessage msg = new RSMessage("Message1");
msg.setText("message 1 data");
msg.addAttribute("lastid", lastidstr);
msgArray[0] = msg;
msg = new RSMessage("Message2");
msg.setText("msg2 data");
msg.addAttribute("prop1", "value");
msgArray[1] = msg;
return msgArray;
}
}
|
メッセージ名、属性名、値、またはメッセージ・テキストの中に特殊文字が含まれていれば、問題を起こす可能性があります。属性値やメッセージ・テキストの文字エスケープを処理するために、メタキャラクター・エスケープが行われていますが、現在は、メッセージ名や属性名ではチェックやエスケープは行われていません。また、前回の記事で紹介した問題、特にセキュリティーとスケーラビリティーは、引き続き今回のアーキテクチャーにも当てはまります。すべてのクライアントがサーバーを頻繁にポーリングすると、スケーラビリティーの問題が重要課題になり、実動アプリケーション用の解決が必要になります。2つのリモート・スクリプト・メソッドとのブラウザーの互換性についても、前回の記事で検討しました。
アーキテクチャーへの "last ID" パラメーターを追加すべきかどうかを検討し、個々にそれを組み込むのを止めました。わたしが以前行ったこのリモート・スクリプト・スキームのインプリメンテーションでは、クライアントが、最後に受け取ったメッセージIDをサーバーに送るようにし、サーバーは、そのメッセージIDの後でだけメッセージを戻すというものでした。わたしはこの機能をアプリケーション依存にしました。なぜなら、メッセージのID化はインプリメンテーションに固有なものだからです。メッセージID化を追加するには、単に次のことを行うだけです。
- メッセージに固有タグを付ける必要のあるスキームを決定する
- その値を持つ属性を戻った各
RSMessageに追加する - 最後のIDをパラメーターの1つとして受け入れるようにサーバー・サイド・メソッドを定義する
- メソッド呼び出しのたびに、最後に受け取ったIDを送信するようにクライアントを変更する
- 処理した各メッセージごとに、クライアント・サイド
rsmsg_processMessage処理に最後のIDを保管させる
代替法として、クライアントが最後の値を更新する対象を指定する特殊な "end of messages" メッセージでメッセージの配列を終了することもできます。
追加すると便利なもう1つの機能は、HttpServletRequest オブジェクトをサーバー・サイド・メソッド に渡すことです。そうすれば、メッセージを戻すセッションまたは要求情報の要因分析をすることができます。
この記事で記述しているフレームワークを使用すれば、"静的な" Webページとサーバーとの通信が非常に簡単になります。メッセージング・レイヤーは完全に抽象化され、単純なAPIの背後に隠されます。このメカニズムは、株式相場表示器、継続して更新されるスコアボード、さらには高度に動的なサーバー制御Webページにまで活用することができます。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Source code for this article | wa-rich-rsmsg.zip | 119 KB | HTTP |
- この記事は、わたしの前回の記事 「サーブレットを使用するリモート・スクリプト」のアーキテクチャー基盤をベースにしてまとめられています。
- JSRSは、Brent Ashley's Remote Scripting ページから入手できます。
- この記事で使用している完全なコードは、この zip ファイル から入手できます。
- リモート・スクリプト・アプリケーションの例は、ここからオンライン・デモで見ることができます。
-
Apache から提供されるすばらしいソフトウェア (つまり、ApacheWebサーバー、Tomcat、およびAnt) が、この記事を書く喜びをわたしに与えてくれました。
Erik Hatcherは、今年2回dot.bombを受けましたが(ドットコム企業への投資に失敗しましたが)、そのつど自分の腕を磨くために技術記事を書きました。現在は、次のプロフェッショナル・アドベンチャーを探していて、それが見つかるまでeHatcher Solutions, Inc. でのコンサルティングの仕事を楽しんでいます。Erikは、XMLおよびJavaScriptにおけるBrainbenchで認定され、最近、XMLテストの見直しについてBrainbenchのコンサルティングを行いました。彼の電子メール・アドレスは、erik@hatcher.net です。