Comet と Java による開発

Servlet 3.0 仕様を実装する

Comet を使用した開発方法のさまざまな実装を調べてみましょう。この記事では、Jetty や Tomcat などのよく使われている Java™ Web サーバーではどのようにして Comet アプリケーションを実現しているかを説明します。さらに、それぞれのサーバーでのプログラミング方法を紹介し、最後に、次期バージョンとなる Servlet 3.0 仕様および JavaEE 6 仕様の一部となっている Java での Comet 標準化の提案について説明します。

Michael Galpin (mike.sr@gmail.com), Software architect, eBay

Michael Galpin's photoMichael Galpin has been developing Java software professionally since 1998. He currently works for eBay. He holds a degree in mathematics from the California Institute of Technology.



2009年 5月 26日

はじめに

この記事では、さまざまな Java 技術を使用して単純な Comet スタイルの Web アプリケーションを構築する方法を説明します。Java サーブレット、Ajax、そして JavaScript の多少の知識があると役に立ちます。また Tomcat と Jetty の Comet 対応機能について調べるため、Tomcat、Jetty それぞれの最新バージョンが必要になります。この記事では Tomcat 6.0.14 と Jetty 6.1.14 を使用しました。また、Java 5 以降をサポートする Java Development Kit も必要になります。この記事で使用したのは JDK 1.5.0-16 です。さらに、この記事で取り上げる Servlet 3.0 仕様を実装する Jetty 7 のプレリリース・バージョンを調べてみることもお勧めします。それぞれのダウンロード・リンクについては、「参考文献」を参照してください。


Comet の概要

最近話題になっている Comet という言葉は、皆さんも聞いたことがあるのではないでしょうか。Comet はリバース Ajax、サーバー・サイド・プッシュと呼ばれることもあります。Comet の概念は至って単純で、ブラウザーからのリクエストなしで、サーバーからブラウザーにデータをプッシュするというものです。単純なことのように聞こえますが、Web アプリケーションや特に HTTP プロトコルについての知識が十分にあるなら、決して単純なことではないとわかるはずです。Comet スタイルの Web アプリケーションをブラウザーとサーバーの両方でスケーラブルに実装することが一般に可能になったのは、ここ数年のことに過ぎません。よく使われている Java Web サーバーがスケーラブルな Comet アーキテクチャーをどのようにして実現するかについては後で説明するとして、まずは Comet アプリケーションを作成する動機、そして Comet アプリケーションの実装に適用される一般的な設計パターンについて見て行きましょう。

Comet を使用する動機

HTTP プロトコルの成功は反論しがたい事実です。インターネットでの情報交換のほとんどは HTTP プロトコルを基に行われていますが、HTTP プロトコルにはいくつかの制約もあります。特に制約となっているのは、これがステートレスな片方向のプロトコルだという点です。リクエストが Web サーバーに送信され、そのサーバーがリクエストに応じてレスポンスを発行すれば、それで話は終わりです。リクエストはクライアントが開始する必要があり、サーバーはリクエストに対する応答としてしかデータを送信できません。多くのタイプの Web アプリケーションは、少なくともこの制約によって多少実用性が失われます。その典型的な例は、チャット・プログラムです。また、スポーツの得点を通知するプログラムや、株価情報を通知するプログラム、E メールなどのプログラムも例として挙げられます。

HTTP の制約は逆に、その成功にもある程度貢献しています。リクエスト/レスポンスのサイクルは、従来の 1 つの接続につき 1 つのスレッドというモデルに役立つからです。リクエストに素早く対応できる限り、この方法は大々的に拡張可能で、1 秒あたりに膨大なリクエスト数を処理できることから比較的少ない数のサーバーで大量のユーザーに対応することができます。そのため、コンテンツ管理システム、検索アプリケーション、e-コマース・サイトなどをはじめとする多くの従来型の Web アプリケーションには最適です。サーバーはユーザーが要求したデータをその都度提供すれば、コネクションを閉じてスレッドを解放し、そのスレッドを他のリクエストに対応させることができます。最初に要求されたデータを提供した後も対話動作が可能であり、コネクションが開いたままであったとしたら、サーバーはスレッドを解放することができず、多数のユーザーに対応することはできません。

しかし、リクエストに応答して要求された最初のデータを送信した後に、ユーザーとの対話動作を続ける必要があったとしたらどうでしょう。その場合、初期の頃の Web ではメタ・リフレッシュを使用していました。このメタ・リフレッシュでは自動的にブラウザーに対し、特定の秒数の経過後にページをリロードするように指示することによって、ポーリングのような形をとります。しかし、この方法はユーザー・エクスペリエンスとして好ましくないだけでなく、一般の処理方法としてもひどく非効率的です。ページに表示する新しいデータが何もない場合を考えてみてください。まったく同じページが再びレンダリングされるだけに過ぎません。また、ページの変更がほんのわずかしかなく、ページの大部分は変わらないとしたらどうでしょう。それでもやはり、必要がどうかに関わらずページのすべてが再びリクエストされ、ページ全体が再び描画されることになってしまいます。

このシナリオを変えたのが、Ajax の登場とその人気です。今やサーバーとは非同期で連絡できるようになり、ページ全体を再びリクエストする必要はもうありません。インクリメンタルに更新することが可能になったからです。それには XMLHttpRequest を使用してサーバーをポーリングするだけで、後は何も必要ありません。この手法は、一般に Comet として知られています。Comet にはいくつかのバリエーションがあり、その形によってパフォーマンスとスケーラビリティーの特性が異なります。次は、この Comet のさまざまなスタイルについて説明します。

Comet のスタイル

Comet を可能にしたのは Ajax の力です。Ajax を利用することで、HTTP の片方向性は事実上克服することが可能で、これを成功させるには、実際にはいくつかの方法があることがわかっています。なかでも Comet を可能にする最も簡単な方法は、おそらく想像できると思いますが、ポーリングです。XMLHttpRequest を使ってサーバーを呼び出し、応答が返ってくると同時に (通常は JavaScript の setTimeout 関数で設定された) 一定の時間待機し、それから再度サーバーを呼び出します。これは非常に一般的な手法で、例えば大抵の Web メール・アプリケーションでは、この手法を使って新着 E メール・メッセージの通知を行っています。

ポーリング手法には利点もあれば、欠点もあります。この仕組みのなかで期待されるのは、その他すべての Ajax リクエストと同様にレスポンスが素早く返ってくることですが、リクエストを送信してから次のリクエストを送信するまでの一時停止は避けられません。一時停止が設けられていなければ絶え間なくサーバーにリクエストが殺到するため、この手法がスケーラブルでないことは明らかです。また、この一時停止はアプリケーションに待ち時間をもたらすことにもなります。一時停止が長ければ、それだけ新しいデータがサーバーからクライアントに送られるまでに時間がかかります。だからと言って一時停止を短くすると、サーバーを過負荷の危険にさらしてしまうため元も子もありません。その一方、ポーリングが Comet を実装するのに最も簡単な方法であることは確かです。

ここで指摘しておかなければならないのは、多くの人々はポーリングが Comet になるとは考えていないことです。反対に、Comet はポーリングの制限に対するソリューションになると考えています。しかし最もよく使われている「正真正銘の」Comet 手法は、ロング・ポーリングと呼ばれるポーリングのバリエーションです。ポーリングとロング・ポーリングの大きな違いは、サーバーが応答するまでにかかる時間です。ロング・ポーリングはコネクションを長時間オープンの状態に維持します。通常は数秒間ですが、1 分以上接続をオープンにしたままにする場合もあります。サーバーでイベントが発生すると、レスポンスが送信されてコネクションが閉じられ、それと同時に改めて新しいポーリングが始まります。

ロング・ポーリングが通常のポーリングに勝る点は、データが使用可能になると同時に、そのデータがサーバーからクライアントに送信されることです。送信するデータが何もないためにリクエストが長時間待たされることもありますが、新しいデータは発生した時点でクライアントに送信されます。そのため待ち時間はありません。Web ベースのチャット・プログラムや、「リアルタイム」を謳っているアプリケーションを使った経験があるとしたら、おそらくそのアプリケーションではロング・ポーリングの手法が使用されていたことでしょう。

ロング・ポーリングのバリエーションでもある、Comet の 3 つ目のスタイルについても言及しておく価値があります。これは通常、ストリーミングと呼ばれます。このスタイルでは、サーバーがクライアントにデータをプッシュしても、コネクションは閉じられません。コネクションはタイムアウトになるまでオープン状態を維持し、リクエストを再び開始させます。XMLHttpRequest 仕様に記載されているように、readyState が 3 すなわち Receiving であるかを確認して (通常はこれとは逆に readyState が 4 すなわち Loaded であるかを確認します)、サーバーからの「ストリーミング」データを取得することができます。待ち時間がないという点では、ストリーミングはロング・ポーリングと似ていて、サーバー上にデータが用意された時点で、データがクライアントに送信されます。ストリーミングには、サーバーに対するリクエスト数が大幅に減るという利点があり、そのためサーバーとのコネクションをセットアップする際のオーバーヘッドと待ち時間がなくなります。しかし残念ながら、XMLHttpRequest の実装はブラウザーによってかなり異なります。この手法が確実に機能するのは、最近のMozilla Firefox バージョンだけです。Internet Explorer や Safari ではロング・ポーリングに頼らざるを得ません。

ここまで読んだ時点で、読者の皆さんはロング・ポーリングとストリーミングにはどちらも大きな問題があると思っていることでしょう。それは、リクエストが長時間、サーバー上に存続することです。リクエストに対するスレッドが解放されることはないため、1 つのリクエストに 1 つのスレッドというモデルは崩れてしまいます。さらに悪いことに、このスレッドは送信対象のデータがない限り、アイドル状態になります。これでは当然、スケーラブルではありません。幸いなことに、最近の Java Web サーバーには、この問題に対処する方法があります。


Java での Comet

多くの Web サーバーはこれまで常に Java でビルドされてきました。その理由の 1 つは、Java には充実したネイティブ・スレッド・モデルがあるからです。そのため、従来の 1 つのコネクションにつき 1 つのスレッドというモデルを比較的簡単に実装することができます。このモデルは Comet にはそれほど有効に機能しませんが、この問題についてもやはり、Java にはそのソリューションがあります。Comet を効率的に処理するためには非ブロッキング I/O が必要ですが、Java はその NIO ライブラリーを介して非ブロッキング I/O を用意します。オープンソース・サーバーのなかで最もよく使われている Apache Tomcat と Jetty は、どちらも NIO を利用して非ブロッキング I/O を追加し、それによって Comet を実現しています。ただし、この 2 つのサーバーでの実装はかなり異なります。それでは、Tomcat と Jetty それぞれがどのように Comet をサポートしているかを調べてみましょう。

Tomcat と Comet

Apache Tomcat で Comet を機能させるためには、2 つの重要な作業があります。その 1 つは、Tomcat の構成ファイル server.xml に多少の変更を加えることです。変更といっても、デフォルトでは一般的な同期 I/O コネクターが有効になっているので、これを非同期バージョンに変更すればよいだけの話です (リスト 1 を参照)。

リスト 1. Tomcat の server.xml の変更
<!-- This is the usual Connector, comment it out and add the NIO one -->
   <!-- Connector URIEncoding="utf-8" connectionTimeout="20000" port="8084" 
protocol="HTTP/1.1" redirectPort="8443"/ -->
<Connector connectionTimeout="20000" port="8080" protocol="org.apache.
coyote.http11.Http11NioProtocol" redirectPort="8443"/>

上記の変更によって、Tomcat はさらに多くのコネクションを同時に扱えるようになりますが、これらのコネクションの多くはほとんどの時間アイドル状態になります。このことを利用するには、org.apache.catalina.CometProcessor インターフェースを実装するサーブレットを作成するのが最も簡単です。このインターフェースは明らかに、Tomcat に固有なものです。リスト 2 に、このサーブレットの一例を記載します。

リスト 2. Tomcat の Comet サーブレット
public class TomcatWeatherServlet extends HttpServlet implements CometProcessor {

    private MessageSender messageSender = null;
    private static final Integer TIMEOUT = 60 * 1000;

    @Override
    public void destroy() {
        messageSender.stop();
        messageSender = null;

    }

    @Override
    public void init() throws ServletException {
        messageSender = new MessageSender();
        Thread messageSenderThread =
                new Thread(messageSender, "MessageSender[" + getServletContext()
.getContextPath() + "]");
        messageSenderThread.setDaemon(true);
        messageSenderThread.start();

    }

    public void event(final CometEvent event) throws IOException, ServletException {
        HttpServletRequest request = event.getHttpServletRequest();
        HttpServletResponse response = event.getHttpServletResponse();
        if (event.getEventType() == CometEvent.EventType.BEGIN) {
            request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT);
            log("Begin for session: " + request.getSession(true).getId());
            messageSender.setConnection(response);
            Weatherman weatherman = new Weatherman(95118, 32408);
            new Thread(weatherman).start();
        } else if (event.getEventType() == CometEvent.EventType.ERROR) {
            log("Error for session: " + request.getSession(true).getId());
            event.close();
        } else if (event.getEventType() == CometEvent.EventType.END) {
            log("End for session: " + request.getSession(true).getId());
            event.close();
        } else if (event.getEventType() == CometEvent.EventType.READ) {
            throw new UnsupportedOperationException("This servlet does not accept 
data");
        }

    }
}

CometProcessor インターフェースには event メソッドを実装する必要があります。これは Comet 対話用のライフサイクル・メソッドで、Tomcat がさまざまな CometEvent インスタンスを使って呼び出します。CometEventeventType をチェックすることで、ライフサイクルのどの段階にいるのかを判断することができます。BEGIN イベントが発生するのは、リクエストが最初に到着した時点です。READ イベントはデータが送信中であることを示すので、リクエストが POST の場合にしか必要ありません。リクエストは、END イベントまたは ERROR イベントのいずれかで終了します。

リスト 2 の例で、サーブレットがデータを送信するため使用しているクラスは MessageSender です。このクラスはサーブレットの init メソッド実行中に固有のスレッドにセットアップされ、サーブレットの destroy メソッドで削除されます。リスト 3 に、この MessageSender を記載します。

リスト 3. MessageSender
private class MessageSender implements Runnable {

    protected boolean running = true;
    protected final ArrayList<String> messages = new ArrayList<String>();
    private ServletResponse connection;
    private synchronized void setConnection(ServletResponse connection){
        this.connection = connection;
        notify();
    }
    public void send(String message) {
        synchronized (messages) {
            messages.add(message);
            log("Message added #messages=" + messages.size());
            messages.notify();
        }
    }
    public void run() {
        while (running) {
            if (messages.size() == 0) {
                try {
                    synchronized (messages) {
                        messages.wait();
                    }
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
            String[] pendingMessages = null;
            synchronized (messages) {
                pendingMessages = messages.toArray(new String[0]);
                messages.clear();
            }
            try {
                if (connection == null){
                    try{
                        synchronized(this){
                            wait();
                        }
                    } catch (InterruptedException e){
                        // Ignore
                    }
                }
                PrintWriter writer = connection.getWriter();
                for (int j = 0; j < pendingMessages.length; j++) {
                    final String forecast = pendingMessages[j] + "<br>";
                    writer.println(forecast);
                    log("Writing:" + forecast);
                }
                writer.flush();
                writer.close();
                connection = null;
                log("Closing connection");
            } catch (IOException e) {
                log("IOExeption sending message", e);
            }
        }
    }
}

このクラスはほとんどがボイラープレート・コードで、Comet には直接関係しませんが、いくつか注意する点があります。まず、このクラスは ServletResponse オブジェクトを使用します。リスト 2 の event メソッドを見ると、このレスポンス・オブジェクトはイベントが BEGIN のときに MessageSender に渡されます。MessageSender はその run メソッドのなかで ServletResponse を使用してデータをクライアントに送り返します。ここで注意する点は、このクラスはキューに入れられたすべてのメッセージを送信した時点でコネクションを閉じることです。したがって、このクラスはロング・ポーリングを実装しています。ストリーミング・スタイルの Comet を実装する場合には、コネクションは開いたままになりますが、それでもデータはフラッシュしてください。

もう一度リスト 2 を見てみると、ここでは Weatherman クラスが作成されていることがわかります。このクラスは、MessageSender を使用してデータをクライアントに返します。これは、Yahoo RSS フィードを使用して、さまざまな郵便番号の地域に関する気象情報を取得し、クライアントに送信するクラスです。この例は、データを非同期で送信するデータ・ソースをシミュレートするために考案されています。そのためのコードはリスト 4 のとおりです。

リスト 4. Weatherman
private class Weatherman implements Runnable{

    private final List<URL> zipCodes;
    private final String YAHOO_WEATHER = "http://weather.yahooapis.com/forecastrss?p=";

    public Weatherman(Integer... zips) {
        zipCodes = new ArrayList<URL>(zips.length);
        for (Integer zip : zips) {
            try {
                zipCodes.add(new URL(YAHOO_WEATHER + zip));
            } catch (Exception e) {
                // dont add it if it sucks
            }
        }
    }

   public void run() {
       int i = 0;
       while (i >= 0) {
           int j = i % zipCodes.size();
           SyndFeedInput input = new SyndFeedInput();
           try {
               SyndFeed feed = input.build(new InputStreamReader(zipCodes.get(j)
.openStream()));
               SyndEntry entry = (SyndEntry) feed.getEntries().get(0);
               messageSender.send(entryToHtml(entry));
               Thread.sleep(30000L);
           } catch (Exception e) {
               // just eat it, eat it
           }
           i++;
       }
   }

    private String entryToHtml(SyndEntry entry){
        StringBuilder html = new StringBuilder("<h2>");
        html.append(entry.getTitle());
        html.append("</h2>");
        html.append(entry.getDescription().getValue());
        return html.toString();
    }
}

このクラスは、Yahoo!天気情報からの RSS フィードを構文解析するために Project Rome ライブラリーを使用します。RSS または Atom フィードを生成または取り込む必要がある場合、このライブラリーは大いに役立ちます。このコードでは、気象情報データを 30 秒ごとに送信する別のスレッドを生成しているのも興味深い点です。最後にもう 1 つ検討しなければならないのは、このサーブレットを使用するためのクライアント・コードです。この例の場合、わずかな JavaScript が含まれる単純な JSP で事足ります。このコードをリスト 5 に記載します。

リスト 5. クライアントの Comet コード
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Comet Weather</title>
        <SCRIPT TYPE="text/javascript">
            function go(){
                var url = "http://localhost:8484/WeatherServer/Weather"
                var request =  new XMLHttpRequest();
                request.open("GET", url, true);
                request.setRequestHeader("Content-Type","application/x-javascript;");
                request.onreadystatechange = function() {
                    if (request.readyState == 4) {
                        if (request.status == 200){
                            if (request.responseText) {
                                document.getElementById("forecasts").innerHTML = 
request.responseText;
                            }
                        }
                        go();
                    }
                };
                request.send(null);
            }
        </SCRIPT>
    </head>
    <body>
        <h1>Rapid Fire Weather</h1>
        <input type="button" onclick="go()" value="Go!"></input>
        <div id="forecasts"></div>
    </body>
</html>

上記のコードは、ユーザーが Go ボタンをクリックすると単純にロング・ポーリングを開始するだけです。注意する点として、このコードは XMLHttpRequest オブジェクトを直接使用するので、Internet Explorer 6 では機能しません。ブラウザー間の違いを解決するには、おそらく Ajax ライブラリーを使用することになります。もう 1 つの重要な点は、コールバック関数、つまりリクエストの onreadystatechange 関数のために作成されるクロージャーです。この関数はサーバーからの新規データを貼り付けてから、go 関数を再び呼び出します。

Tomcat での単純な Comet アプリケーションは以上のとおりです。Tomcat のために必要な作業は 2 つありました。1 つは Tomcat のコネクターを構成すること、もう 1 つは Tomcat 固有のインターフェースをサーブレットに実装することです。ここで皆さんは、このコードを Jetty に移植するのがどれくらい難しいのかと不安に思っていることでしょう。それについて、次に説明します。

Jetty と Comet

Jetty サーバーがスケーラブルな Comet 実装を可能にするために使う手法は多少異なります。Jetty は、continuation (継続) として知られるプログラミング構成概念をサポートします。この概念は非常に単純なもので、リクエストを中断し、将来のある時点で継続させるというものです。リクエストはタイムアウトやその他の意味のあるイベントによって再開されます。リクエストが中断されている間、そのスレッドは解放されます。

Jetty の org.mortbay.util.ajax.ContinuationSupport クラスを使用すれば、どんな HttpServletRequest にでも org.mortbay.util.ajax.Continuation のインスタンスを作成することができます。そのため、Comet に対してかなり違った手法をとることも可能ですが、論理的に同じスタイルの Comet を実装するには、continuation を使用します。リスト 6 は、リスト 2 の Weather サーブレットを Jetty に「移植」した後のコードです。

リスト 6. Jetty の Comet サーブレット
public class JettyWeatherServlet extends HttpServlet {
    private MessageSender messageSender = null;
    private static final Integer TIMEOUT = 5 * 1000;
    public void begin(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException {
        request.setAttribute("org.apache.tomcat.comet", Boolean.TRUE);
        request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT);
        messageSender.setConnection(response);
        Weatherman weatherman = new Weatherman(95118, 32408);
        new Thread(weatherman).start();
    }
    public void end(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException {
        synchronized (request) {
            request.removeAttribute("org.apache.tomcat.comet");
            Continuation continuation = ContinuationSupport.getContinuation
(request, request);
            if (continuation.isPending()) {
                continuation.resume();
            }
        }
    }
    public void error(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException {
        end(request, response);
    }
    public boolean read(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException {
        throw new UnsupportedOperationException();
    }
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException {
        synchronized (request) {
            Continuation continuation = ContinuationSupport.getContinuation
(request, request);
            if (!continuation.isPending()) {
                begin(request, response);
            }
            Integer timeout = (Integer) request.getAttribute
("org.apache.tomcat.comet.timeout");
            boolean resumed = continuation.suspend(timeout == null ? 10000 : 
timeout.intValue());

            if (!resumed) {
                error(request, response);
            }
        }
    }
    public void setTimeout(HttpServletRequest request, HttpServletResponse response, 
int timeout) throws IOException, ServletException,
            UnsupportedOperationException {
        request.setAttribute("org.apache.tomcat.comet.timeout", new Integer(timeout));
    }
}

上記で最も重要な点は、この構造が Tomcat バージョンのコードとよく似ていることです。beginreadend、および error メソッドは、Tomcat での同じイベントに対応します。サーブレットの service メソッドは、リクエストが最初に到着した時点で continuation を作成するようにオーバーライドされ、タイムアウトになるか、または別のイベントによって再開されるまでリクエストを中断します。上記では、init および destroy メソッドは Tomcat バージョンとまったく同じなので記載していません。このサーブレットは、Tomcat バージョンと同じ MessageSender を使用します。変更は一切必要ありません。begin メソッドがどのように Weatherman インスタンスを作成するかに注目してください。このクラスも、Tomcat バージョンとまったく同じように使用されています。クライアント・コードでさえ完全に同じです。変更されているのはサーブレットだけですが、大幅に変更されています。ただし、Tomcat のイベント・モデルに直接的に対応付けることができます。

この例で心強く思ってもらえたことを願います。まったく同じコードが Tomcat と Jetty の両方で機能するわけではありませんが、それぞれで機能するコードは非常によく似ています。JavaEE の魅力の 1 つは当然、移植性です。Tomcat で動作するコードの大部分は何も変更しなくても Jetty で動作します。その逆も然りです。このことから、Java Servlet 仕様の次期バージョンに非同期リクエスト処理、つまり Comet の基礎となる技術の標準化が含まれているのも当然のことと言えます。それでは次に、ここで話題に持ち上がった Servlet 仕様の次期バージョン、Servlet 3.0 仕様に目を向けてみましょう。


Servlet 3.0 仕様

ここでは Servlet 3.0 仕様の素晴らしい詳細のすべてを掘り下げるのではなく、Comet サーブレットが Servlet 3.0 コンテナー内で実行されるとしたら、どのような形になる可能性があるかに焦点を絞ります。「可能性」という言葉を使ったことに注意してください。この仕様は公開レビューに向けてすでにリリースされていますが、この記事を執筆している時点では、まだ最終決定に至っていないためです。したがって、リスト 7 には公開レビューの時点での仕様に準拠した実装を記載します。

リスト 7. Servlet 3.0 Comet
@WebServlet(asyncSupported=true, asyncTimeout=5000)
public class WeatherServlet extends HttpServlet {
   private MessageSender messageSender;

// init and destroy are the same as other
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        AsyncContext async = request.startAsync(request, response);
        messageSender.setConnection(async);
        Weatherman weatherman = new Weatherman(95118, 32444);
        async.start(weatherman);;
    }
}

上記の実装で優れている点は、大幅に単純化されていることです。公平に見ると、Tomcat のイベント・モデルに従おうとしなければ、Jetty でも同じような実装は可能です。イベント・モデルには実用的な感があり、Jetty などの Tomcat 以外のコンテナーにも簡単に実装することができますが、イベント・モデルを中心とした標準化は行われないと思います。

リスト 7 を見てみると、アノテーションで非同期処理をサポートし、タイムアウトを設定することを宣言しています。startAsyncHttpServletRequest での新しいメソッドで、このメソッドは javax.servlet.AsyncContext という新しいクラスのインスタンスを返します。MessageSender は、ServletResponse ではなく AsynContext への参照に渡されていることに注目してください。レスポンスを終了する代わりに、AsyncContext インスタンスで完全なメソッドを呼び出すことになります。また、WeathermanAsyncContext インスタンスの start メソッドに直接渡されていることも注目の点です。これによって、現行の ServletContext で新規スレッドが開始されます。

このように、Tomcat とも Jetty ともかなり異なるプログラミング手法ですが、このスタイルのプログラミングを適応させて Servlet 3.0 仕様で提案されている API を扱えるようにするのはそれほど難しいことではありません。注目すべきことに、Jetty 7 は Servlet 3.0 を実装するよう意図されており、現在 Jetty 7 のベータ版が入手可能になっています。ただし、この記事を執筆している時点では、上記に示した最新バージョンの仕様はまだ実装されていません。


まとめ

Comet スタイルの Web アプリケーションは、まったく新しいレベルの対話性を Web にもたらします。このような機能を大掛かりな規模で実装しようとすると複雑な問題も出てきますが、主要な Java Web サーバーにはいずれも Comet を実装するための完成度の高い安定した技術が用意されています。この記事では、現在の Tomcat と Jetty での Comet が持つ相違点と類似点、Servlet 3.0 仕様に向けて現在進められている標準化作業について説明しました。スケーラブルな Comet アプリケーションの構築を可能にする Tomcat と Jetty は、Servlet 3.0 で標準化への道を着実に進んでいます。


ダウンロード

内容ファイル名サイズ
Weather Server Source CodeWeatherServer.zip347KB

参考文献

学ぶために

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

  • この記事では JDK 1.5.0-16 を使用しました。
  • Jetty: この記事では Jetty 6.1.14 を使用しました。
  • Tomcat: この記事では Tomcat 6.0.14 を使用しました。
  • IBM ソフトウェアの試用版を使用して、次のオープンソース開発プロジェクトを革新してください。ダウンロード、あるいは DVD で入手できます。
  • IBM 製品の評価版をダウンロードして、DB2、Lotus、Rational、Tivoli、および WebSphere のアプリケーション開発ツールとミドルウェア製品を使ってみてください。

議論するために

コメント

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=Web development
ArticleID=398851
ArticleTitle=Comet と Java による開発
publish-date=05262009