このエントリーは、前回からの続きです。
サーバの実装
クライアント側の実装は、
- リクエストを発行してから、カウンターの値が返ってくるまで待つlong-pollの仕組み
- ユーザからのイベントをうけて、カウンターの値を増加させるようにリクエストする仕組み
Jetty 6では、Ajaxアプリケーションのために、org.mortbay.util.ajax.AjaxFilterが用意されています。このAjaxFilterを継承して、サーバ側プログラムを作成していきます。AjaxFilterは、javax.servlet.Filterインターフェースを実装しているクラスです。
サンプルアプリケーションのweb.xmlをみてください。/conter/*というURLパターンに一致するリクエストは、AjaxFilterを継承したCounterFilterによって処理されます。このフィルターがクライアントからのリクエストを処理し、クライアントへレスポンスを返します。
<filter>
<filter-name>Counter Filter</filter-name>
<filter-class>
net.recompile.comet.jetty.CounterFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>Counter Filter</filter-name>
<url-pattern>/counter/*</url-pattern>
</filter-mapping>
それでは、CounterFilterの実装をみていきましょう。まず、クライアントからのリクエストは、CounterFilter#handle(String method, String message, HttpServletRequest request, AjaxResponse response)によって、処理されます。
public void handle(String method,
String message,
HttpServletRequest request,
AjaxResponse response)
{
if ("count".equals(method)) {
// 現在のカウンターの数値をクライアントに送信する
response.elementResponse("count", String.valueOf(_count));
} else if ("poll".equals(method)) {
// 継続オブジェクトを生成する
Continuation c =
ContinuationSupport.getContinuation(request, mutex);
// 継続オブジェクトをインスタンス変数(Map)に保存する
_continuations.put(sessid, c);
// 再開されるまで一時停止する
c.suspend(10000L);
// 内部カウンターの数値をクライアントに送信する
response.elementResponse("count", String.valueOf(_count));
methodには、クライアントから渡されるリクエストパラメータであるajaxの値がはいっています。クライアントは、最初にページがロードされたときにajaxパラメータに文字列「count」をいれて、サーバへとリクエストを発行します。
サンプルアプリケーションを起動し、http://localhost:8080/counter/?ajax=count&message=へとアクセスしてみてください。次のようなレスポンスがすぐに返ってきます。
<ajax-response> <response type="element" id="count">0</response> </ajax-response>
次に、http://localhost:8080/counter/?ajax=poll&message=へとアクセスしてみましょう。しばらく砂時計がまわったままでいて、それから同じレスポンスがかえってきたはずです。
リクエストパラメータによってpollが渡されると、サーバ側では継続オブジェクトを生成し、その継続オブジェクトを利用して、処理を一時停止をします。インスタンスメソッドに保存されている継続オブジェクトに再開のメッセージが届くか、タイムアウト(1000ミリ秒)が訪れると、一時停止されていた処理が同じ場所から再開されます。
イベントによって処理を再開させるコードは、次のようになります。リクエストパラメータajaxにincrementという値がはいっていたとき、CounterFilterは、内部カウンターを増加させ、それからインスタンス変数に保存しておいた継続オブジェクトを再開させます。
} else if ("increment".equals(method)) {
// カウンターを増加させる
_count ++;
// インスタンス変数のMapに保存されていた継続オブジェクトへのアクセス
// (実際には同期化処理が必要!)
for (Continuation c : _continuations.values()) {
// 処理を再開させる
c.resume();
}
// 成功したことをクライアントへ伝える
response.elementResponse("success", "success");