Tomcatはどこまで“安全”にできるのか?(3)

Tomcat 6で実現!
Ajaxを超える通信技術Comet


株式会社アメニクス
x-lab チーム
2007/12/12


サーブレットにCometを実装する

 移動したら、サーブレットの作成に移ります。Cometはイベント受信後、エディタでCometServlet.javaを作成します。サーブレットには、以下の内容を記述していきます。

CometServlet.java(※Tomcatのリファレンスページにある内容を基に作成)
import java.util.*;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.CometEvent;
import org.apache.catalina.CometProcessor;


public class CometServlet
extends HttpServlet implements CometProcessor {

    protected ArrayList<HttpServletResponse> connections
        = new ArrayList<HttpServletResponse>();
    protected MessageSender messageSender = null;

    public void init() throws ServletException {
        messageSender = new MessageSender();
        Thread messageSenderThread =
            new Thread(messageSender, "MessageSender["
            + getServletContext().getContextPath() + "]");
        messageSenderThread.setDaemon(true);
        messageSenderThread.start();
    }
    public void destroy() {
        connections.clear();
        messageSender.stop();
        messageSender = null;
    }

    /**
    * Process the given Comet event.
    *
    * @param event The Comet event that will be processed
    * @throws IOException
    * @throws ServletException
    */
    public void event(CometEvent event)
    throws IOException, ServletException {

        HttpServletRequest request = event.getHttpServletRequest();
        HttpServletResponse response = event.getHttpServletResponse();

        if ("POST".equals(request.getMethod())) {
            final String user = request.getParameter("user");
            final String message = request.getParameter("message");
            messageSender.send(user,message);

            event.close();
            return;
        }

        if (event.getEventType() == CometEvent.EventType.BEGIN) {
            event.setTimeout(60 * 1000 * 30);
            log("Begin for session: "
                + request.getSession(true).getId());

            response.setContentType("text/html;charset=UTF-8");
            PrintWriter writer = response.getWriter();
            writer.println("<!doctype html public \"-//w3c//dtd html"
                +" 4.0 transitional//en\">");
            writer.println("<head><title>チャットメッセージ</title>"
                +"</head><body bgcolor=\"#FFFFFF\">");
            writer.flush();
            synchronized(connections) {
                connections.add(response);
            }
        } else if(event.getEventType() == CometEvent.EventType.ERROR){
            log("Error for session: "
                + request.getSession(true).getId());
            synchronized(connections) {
                connections.remove(response);
            }
            event.close();
        } else if (event.getEventType() == CometEvent.EventType.END) {
            log("End for session: "
                + request.getSession(true).getId());
            synchronized(connections) {
                connections.remove(response);
            }
            PrintWriter writer = response.getWriter();
            writer.println("</body></html>");
            event.close();
        } else if (event.getEventType() == CometEvent.EventType.READ){
        }
    }

    public class MessageSender implements Runnable {
        protected boolean running = true;
        protected ArrayList<String> messages=new ArrayList<String>();
        public MessageSender() { }
        public void stop() {
            running = false;
        }
        /**
        * Add message for sending.
        */
        public void send(String user, String message) {
            synchronized (messages) {
                messages.add("[" + user + "]: " + message);
                messages.notify();
            }
        }
        public void run() {
            while (running) {
               if (messages.size() == 0) {
                    try {
                        synchronized (messages) {
                            messages.wait();
                        }
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }
                synchronized (connections) {
                    String[] pendingMessages = null;
                    synchronized (messages) {
                        pendingMessages
                            = messages.toArray(new String[0]);
                        messages.clear();
                    }
                    // Send any pending message
                    // on all the open connections
                    for (int i = 0; i < connections.size(); i++) {
                        try {
                            PrintWriter writer
                                = connections.get(i).getWriter();
                            for(int j=0;j<pendingMessages.length;j++){
                                writer.println(pendingMessages[j]
                                    + "<br>");
                            }
                            writer.flush();
                        } catch (IOException e) {
                            log("IOExeption sending message", e);
                        }
                    }
                }
            }
        }
    }
}

 以上でサーブレットの記述は完了しました。

 Cometの実現には、Tomcat 6から提供されたorg.apache.catalina.CometEventクラスorg.apache.catalina.CometProcessorインターフェイスが使われています。

Cometでチャットをしてみよう!

 それでは、作成したサーブレットをコンパイルしましょう。

javac -classpath /opt/tomcat6/lib/servlet-api.jar:/opt/tomcat6/lib/catalina.jar CometServlet.java

 無事にエラーなく終了すれば、作成完了です。最後に、Tomcatの再起動を実行します。

/etc/rc.d/init.d/tomcat restart

 再起動が完了したら、実際に作成したチャットを開いてみましょう。「http://[サーバのIPアドレス]:8080/jsp/CometChat.jsp」を開いて、発言してみましょう。

 発言をすると、同時に接続しているページ全部へメッセージが送信され、描画されるのが分かります。複数のウィンドウで同時に接続して行うと、以下のようになります。

図4 チャット中のクライアント(1)
図4 チャット中のクライアント(1)

図5 チャット中のクライアント(2)
図5 チャット中のクライアント(2)

次回はTomcatが持つセキュリティリスクについて

 TomcatもCometをサポートし、閲覧者同士がリアルタイムに情報交換できるWebアプリケーションの提供など可能になったことは大きな進展ですね。

 Cometの活用事例については、@ITの記事「リバースAjax機能はAjax+Javaをもっとやさしくする?の「サーバプッシュ型Ajaxアプリケーションの応用例」や@ITの記事「サーバが通信を開始できるComet活用Webチャット」の「ハイライト2・快適かつ強力なチャットサービスLingr」に詳しいので、ご参照ください。

 今回まではTomcatの設定や運用、新機能について触れてきましたが、次回からはいよいよTomcatが持つセキュリティのリスクについて触れていきます。

@IT関連記事


サーバが通信を開始できるComet活用Webチャット
Ajax うきうき Watch(12)
 サーバが送りたいときにデータを送れる「Comet」の活用例「Lingr」や、JavaScriptなしにAjaxが書けてしまうフレームワーク「ZK」などが、続々登場
リッチクライアント & 帳票フォーラム 2006/9/23
JavaプログラマはAjaxに乗るべきか
安藤幸央のランダウン(31) 
Ajaxアプローチを活用したWebアプリケーション開発には数々の技術的スキルと、センスを要求され、設計や全体の出来に大きく影響する

Java Solution」フォーラム 2006/4/28
MyEclipseでAjax+Javaをやさしく開発
サーバサイドにJava EEを、クライアントサイドにAjaxを用いたWebアプリケーションを、MyEclipseを使って容易に開発する手法を紹介していく

第1回 Ajax+Java連携のEoDを実現するMyEclipse (2006/12/15)
第2回 DWRでAjaxアプリを作りたいJava開発者にお勧め (2007/1/27)
第3回 O/Rマッピングで郵便番号検索Ajaxアプリ作成! (2007/4/10)
第4回 リバースAjax機能はAjax+Javaをもっとやさしくする? (2007/6/11)
連載各回の解説はこちら
Ajaxのセキュリティ、特殊なものだと思ってました
もいちどイチから! HTTP基礎訓練中(3)
 AjaxでなければWeb2.0ではない、といわんばかりのAjaxブーム。対抗するには「セキュリティ2.0」が必要なのか、勉強会で確認!
Security&Trust」フォーラム 2007/12/7


プロフィール
x-lab チーム

株式会社アメニクスのR&D部門として、企業の価値向上を目的に結成された研究開発チーム(x-lab=amenix laboratory)。
アメニクスの社員をはじめ、システム開発技術者のみに限らず、MBA教授や外資系出身エンジニアなど幅広いメンバーが所属している。IT技術からマーケティング、金融テクノロジーなどさまざまな分野で活動中。

主な著書
最速導入!オープンソースでつくる実用オンラインショップ

1-2-3

 INDEX
第3回 Tomcat 6で実現! Ajaxを超える通信技術Comet
  Page1
Ajaxを超える? Tomcat 6で搭載されたCometとは?
Tomcatを用いてCometを実装するには?
  Page2
Cometを使ったチャットアプリケーション
Page3
Cometでチャットをしてみよう!
次回はTomcatが持つセキュリティリスクについて



Java Solution全記事一覧

TechTargetジャパン

Java Solution フォーラム 新着記事

@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

RSSフィード

キャリアアップ

- PR -
@IT Sepcial

イベントカレンダー

PickUpイベント

- PR -
もっと見る
- PR -

お勧め求人情報

ホワイトペーパーTechTargetジャパン

@IT Sepcial
ソリューションFLASH