SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事(AD)

「WebSphere Application Server Liberty Core」で新たに正式サポートされたWebSocketを使ってみた

  • X ポスト
  • このエントリーをはてなブックマークに追加

 IBMの「WebSphere Application Server Liberty Core」は、Webアプリケーションの開発や実行に特化して設計された軽量なJavaアプリケーションサーバーである。同社のアプリケーションサーバー「WebSphere Application Server」(以下、WAS)のラインナップの一つとして提供されており、主なターゲットをWebアプリケーションに絞ることで、他のエディションに比べて少ないリソースで高速な動作を実現している点が大きな特徴だ。

  • X ポスト
  • このエントリーをはてなブックマークに追加

 Liberty CoreはWAS v8.5から追加されたLibertyプロファイルをベースとして構成されている。LibertyプロファイルはJava EE 6のWebプロファイルを完全サポートしたアプリケーションランタイムである。Webプロファイルに含まれるAPI群だけでなく、JAX-WSやJAX-RS、JMSといったWebサービスに必要となる各種APIもサポートされており、現在はJava EE 7の一部機能の実装も進められている。

 そのJava EE 7で新たに追加された注目機能の一つに「WebSocket」がある。Java用のWebSocket APIは「Java API for WebSocket」としてJSR 356で標準化されており、Libertyプロファイルでは2014年12月8日にリリースされたV8.5.5.4より正式にJSR 356へのサポートが追加されている。そこで本稿では、この新機能を活用してWebSocketアプリケーションを開発する方法を紹介したい。

1. WebSocketとJava

 まずWebSocketについて簡単におさらいしておこう。WebSocketはWebサーバーとWebブラウザが直接コネクションを張って双方向通信するための技術規格である。HTTPとは異なる独自の軽量プロトコルを利用して通信を行うため、オーバーヘッドが小さく、長時間に渡って通信する場合でもHTTPコネクションを占有することがないというメリットがある。オンラインゲームやオンライントレーディング、チャットやSNSによるコミュニケーションなど、リアルタイム性が求められるサービスやアプリケーションで威力を発揮するものとして期待されている。

 WebSocketのプロトコル仕様は「RFC 6455 - The WebSocket Protocol」として、API仕様は「The WebSocket API」として仕様化が進められている。そして冒頭で触れたように、Javaアプリケーションから利用するためのAPIは「JSR 356: Java API for WeSocket」として標準化されており、Java EE 7を構成するAPIの一つになっている。

 WebSocketを利用したJava Webアプリケーション開発の一般的な流れについては、IBM developerWorksのサイト内で公開されている『Java EE 7 アプリケーション設計ガイド - WebSocket編』による解説が詳しい。WebSocket自体についての詳しい解説やエンタープライズアプリケーションでWebSocketを利用する上でのポイントなどが掲載されているので、本稿と併せて参考にしていただきたい。

2. 最新版Libertyプロファイルの導入

 Liberty Core向けアプリケーションの開発環境を整えるには、Eclipse用プラグイン「WebSphere Application Server Developer Tools(WDT)」を導入するのが一番手軽である。今回は、Eclipse 4.4(Luna) Java EE Developers(Pleiadesで日本語化済み)をベースに、プラグイン「IBM WebSphere Application Server V8.5.5 Liberty Profile Developer Tools for Eclipse Luna V8.5.5.4」を追加して利用した。WebSocketが正式にサポートされているのはこのV8.5.5.4からなので、それ以前のバージョンを導入済みの人も最新版にアップデートしておく必要がある。

 WDTのEclipseへのインストールは、マーケットプレースから行うことができる。[ヘルプ]-[Eclipse マーケットプレース]を選択してマーケットプレースを立ち上げ、「WebSplere Liberty」で検索をかければ図2.1のように対象のプラグインが見つかるはずだ。環境構築の方法についての詳細はこちらの記事を参照していただきたい。

図2.1
図2.1

 プラグインをインストールできたら、Libertyプロファイルのサーバー定義を作成する。サーバー定義の作成手順もV8.5.5.1の場合とほぼ同様だが、今回はWebSocketを使いたいため、プラグインの選択画面から「Java API for WebSocket 1.0」をインストールしておこう。図2.2のように右側の[インストール]ボタンをクリックして[インストール保留中]という表示に変えた上で[次へ]をクリックし、ライセンスに同意すればインストールできる。

図2.2
図2.2

 サーバー定義が作成できたら、続いてWebSocketを使うための設定を追加しておく。サーバーマネージャーにおいて「サーバー構成」をダブルクリックしてservers.xmlを開き、「フィーチャーマネージャー」を表示して、 [追加]ボタンで「websocket-1.0」をフィーチャーに追加すればよい(図2.3)。これで、このサーバー定義でWebSocketが有効になる。

図2.3
図2.3

3. サーバー側のエンドポイントの実装

 環境が構築できたら、早速WebSocketを利用したアプリケーションを作成してみよう。まず手始めに、クライアントから送信されたメッセージをそのまま返送するアプリケーションを作ってみる。プロジェクトの種類は通常のWebアプリケーションと同様に「動的Webプロジェクト」でよい。

図3.1
図3.1

 プロジェクト名は「WebSocketSample」とする。ターゲット・ランタイムには「WebSphere Application Server Liberty Profile」を指定しておく。

図3.2
図3.2

 プロジェクトが作成されたら、まずはサーバー側のエンドポイント(接続の一端となるサーバー側のコンポーネント)を作成する。WebSocket用プラグインを導入している場合、ウィザードを使ってエンドポイントのひな形を作成できるようになっている。新規作成ウィザードの[Web]カテゴリーに図3.3のように[WebSocket Endpoint]が追加されているので、これを選択すれば図3.4のようなウィザードが立ち上がる。

図3.3
図3.3
図3.4
図3.4

 Java API for WebSocketでWebSocketエンドポイントを作るには2つの方法が用意されており、一つは抽象クラスのjavax.websocket.Endpointクラスを継承して独自に実装する方法、もう一つはアノテーションを使って定義する方法となる。今回はより手軽な後者の方法を利用する。

 この場合、ウィザード下部の[Implementation Optios]で[Create an Annotated Server Endpoint]をチェックすればサーバーエンドポイントが定義できる。[URL]には、クライアントからアクセスする際に使用する識別子を設定する。今回は「Hello」とした。

 なお、クライアント側のエンドポイントをJavaを使って作りたい場合は[Create an Annotated Client Endpoint]を、Endpointクラスを継承して自前でエンドポイントを実装したい場合には[Create a Programmatic Endpoint]をチェックしておけばよい。

 一連の設定を入力したら、[完了]をクリックすれば図3.5のようにクラスのひな形が作成される。ポイントはクラスに対して@ServerEndpointアノテーションが指定されていることである。URLに指定した識別子は引数として設定される。また、プロジェクトツリーにWebSocketsの枝が追加されている点にも注目しよう。ここには作成したWebSocketエンドポイントの一覧が表示される。

図3.5
図3.5

 Java API for WebSocketにはエンドポイントのイベントハンドラを定義するための以下の4つのアノテーションが用意されている。なお、ここで重要なのはアノテーションなので、メソッド名は任意に指定してよい。

 これを踏まえて、クライアントから受け取ったメッセージをそのまま返送するようなエンドポイントは、次のようなコードで書くことができる。まず接続が確立した際には@OnOpenを指定したOnOpen()メソッドが呼び出されるので、ここでセッションを保存しておく。メッセージを受信した場合は@OnMesssageを指定したreceiveMessage()メソッドが呼び出されるので、受け取ったメッセージの先頭に「Hello」の文字列を付加して返信する。

 クライアントへメッセージを送信するには、SessionオブジェクトからRemoteEndpointオブジェクトを取得した上で、それに対してsendText()メソッドを実行すればよい。RemoteEndpointオブジェクトには同期型のRemoteEndpoint.Basicと非同期型のでRemoteEndpoint.Asyncの2種類があり、それぞれSession.getBasicRemote()メソッドおよびSession.getAsyncRemote()メソッドで取得できる。ここでは同期型のRemoteEndpointを使用している。

リスト3.6 HelloEndpoint.java
@ServerEndpoint("/Hello")
public class HelloEndpoint {
    
    // 現在のセッションを記録
    Session currentSession = null;
    
    /*
     * 接続がオープンしたとき
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig ec) {
        this.currentSession = session;
    }
   
    /*
     * メッセージを受信したとき
     */
    @OnMessage
    public void receiveMessage(String msg) throws IOException {
        //メッセージをクライアントに送信する
        this.currentSession.getBasicRemote().sendText("Hello " + msg + ".");
    }
    
    /*
     * 接続がクローズしたとき
     */
    @OnClose
    public void onClose(Session session, CloseReason reason) {
        //...
    }
    
    /*
     * 接続エラーが発生したとき
     */
    @OnError
    public void onError(Throwable t) {    
        //...
    }
}

4. クライアント側のエンドポイントの実装

 クライアント側については、今回はJavaScriptで実装することにした。W3Cが策定中の標準APIである「The WebSocket API」を使ったWebSocket通信は、接続先のURLを指定してWebSocketインターフェースのインスタンスを作成することから始まる。URLは「ws:」または「wss:」(httpsに相当するセキュア接続用)というURIスキームを持つ形式で表される。次の例は、上で作成したHelloEndpoint.javaに接続する例である。

リスト4.1 WebSocketによる接続の開始
WebSocket webSocket = new WebSocket('ws://localhost:9080/WebSocketSample/Hello');

 このコンストラクタはインスタンスを作成すると同時に対象のURLに対してWebSocketプロトコルによる接続を試みる。作成したWebSocketインスタンスには、クライアントエンドポイントの4つのイベント(onpen、onmessage、onclose、onerror)に対応したハンドラをそれぞれ定義する。次のコードは、WebSocketの接続を開始し、4つのイベントハンドラを定義する例である。

リスト4.2 各種ハンドラの定義例
var webSocket = null;

function connect() {
    if (webSocket == null || 
        webSocket.readyState != webSocket.CONNECTING || 
        webSocket.readyState != webSocket.OPEN) {
        
        // WebSocketオブジェクトのインスタンス化
        webSocket = new WebSocket('ws://localhost:9080/WebSocketSample/Hello');

        // 各種イベントハンドラの定義
        // 接続がオープンしたとき
        webSocket.onopen = function(event) {
            document.getElementById("status").innerHTML = "接続中";
        };

        // メッセージを受信したとき
        webSocket.onmessage = function(event) {
            var message = event.data;
            document.getElementById("message").innerHTML = "<strong>" + message + "</strong>";
        };

        // 接続がクローズしたとき
        webSocket.onclose = function(event) {
            document.getElementById("status").innerHTML = "切断中";
            webSocket = null;
        };

        // エラーが発生したとき
        webSocket.onerror = function(event) {
            alert("An error was occured.");
            document.getElementById("status").innerHTML = "切断中";
        };
    }
}

 この例では、接続の開始と切断、そしてエラー発生時に接続状態の表示を切り替えるようになっている。そしてメッセージを受信した場合には、その内容をページに表示する。現在の接続状態を知るにはWebSocketインスタンスのreadyState属性の値を調べればよい。WebSocketインターフェースには次の4つの状態が定義されている。

  • CONNECTING:接続を開始しているが、まだ確立していない
  • OPEN:接続が確立している
  • CLOSING:接続のクローズを実行中
  • CLOSED:接続がクローズされたか、または接続をまだ開始していない

 つまりこの例では、まだ接続を開始していない場合にのみ接続を試みているというわけだ。サーバーにメッセージの送信したい場合には、接続状態にあるWebSocketインスタンスに対して次のようにsend()メソッドを実行すればよい。テキストを送信したい場合には、このメソッドに文字列を渡せばそれがサーバーに送信される。

リスト4.3 サーバーエンドポイントへのメッセージの送信
function sendMessage() {
    if (webSocket.readyState == webSocket.OPEN) {
        webSocket.send("HELLO!");
    }
}

 接続をクローズするには、次のようにclose()メソッドを実行する。

リスト4.4 WebSocket接続のクローズ
function disconnect() {
    if (webSocket.readyState == webSocket.OPEN) {
        webSocket.close();
    }
}

 以上をまとめて、Helloエンドポイントと通信するクライアントのコードは次のように書くことができる。

リスト4.5 index.html
<!DOCTYPE HTML>
<html>

<head>
    <title>WebSocketのサンプル</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script type="text/javascript">
        var webSocket = null;

        // WebSocket接続
        function connect() {
            if (webSocket == null || 
                    webSocket.readyState != webSocket.CONNECTING || 
                    webSocket.readyState != webSocket.OPEN) {
                // WebSocketオブジェクトのインスタンス化
                webSocket = new WebSocket('ws://localhost:9080/WebSocketSample/Hello');
            
                // 各種イベントハンドラの定義
                // 接続がオープンしたとき
                webSocket.onopen = function(event) {
                    document.getElementById("status").innerHTML = "接続中";
                };
                
                // メッセージを受信したとき
                webSocket.onmessage = function(event) {
                    var message = event.data;
                    document.getElementById("message").innerHTML 
                                        = "<strong>" + message + "</strong>";
                };
                
                // 接続がクローズしたとき
                webSocket.onclose = function(event) {
                    document.getElementById("status").innerHTML = "切断中";
                    webSocket = null;
                };
                
                // エラーが発生したとき
                webSocket.onerror = function(event) {
                    alert("An error was occured.");
                    document.getElementById("status").innerHTML = "切断中";                    
                };
            }
        }

        // ソケットの切断
        function disconnect() {
            if (webSocket.readyState == webSocket.OPEN) {
                webSocket.close();
            }
        }
            
        // メッセージの送信
        function sendMessage() {
            if (webSocket.readyState == webSocket.OPEN) {
                var text = document.getElementById('inputmessage').value;
                webSocket.send(text);
                document.getElementById('inputmessage').value = "";
            }
        }
    
    </script>
</head>

<body>
    <h1>WebSocketのサンプル</h1>
    
    <div>
        <button type="submit" onclick="connect()">接続</button>
        <button type="submit" onclick="disconnect()">切断</button>
        <span id="status" style="font-style:italic;margin-left:100px;">切断中</span>
    </div>
    
    <div style="margin-top:20px;">
        メッセージ入力:<input id="inputmessage" type="text" size="50" />
        <button type="submit" onclick="sendMessage()">送信</button>
    </div>

    <p>受信メッセージ:</p>
    <div id="message" style="margin-left:20px;"></div>
</body>

</html>

5. WebSocketによるサーバー・クライアント通信の実行例

 それでは、完成したアプリケーションを実行してみる。実行する対象のサーバーにはもちろん先ほど作成したLiberty プロファイルを指定する。

図5.1
図5.1

 サーバーが起動したら、WebブラウザからクライアントのWebページにアクセスしてみよう。初期状態では図5.2のように「切断中」と表示されており、[接続]ボタンをクリックして接続が確立すると図5.3のように表示が「接続中」に変わる。この状態でメッセージを入力して[送信]ボタンを押せば、入力した内容に「Hello」が付いて返信され、その内容が図5.4のように表示されるはずだ。[切断]ボタンをクリックするとソケットをクローズして図5.5のように表示が「切断中」に戻る。

図5.2
図5.2
図5.3
図5.3
図5.4
図5.4
図5.5
図5.5

 この一連の操作によって、接続や切断、メッセージ受信などのタイミングでそれぞれイベントが発生し、対応するハンドラが呼び出されていることが確認できるだろう。

 ちなみに、[接続]ボタンをクリックして接続要求を行ったときのHTTPヘッダーを確認してみると、要求ヘッダーは図5.6、応答ヘッダーは図5.7のようになっており、Upgradeヘッダを利用してHTTPからWebSocketへとプロトコルを切り替えていることが確認できる。

図5.6
図5.6
図5.7
図5.7

6. WebSocketを使った一対多の通信

 HelloEndpointのサンプルは、サーバーとクライアントの一対一の通信の例だった。そこで今度は、1つのサーバーに対して複数のクライアントが接続して一対多の通信をするチャットアプリケーションをWebSocketを使って作ってみよう。すなわち、サーバー側のエンドポイントは複数のクライアントからの接続を同時に受け付け、クライアントからメッセージを受け取った場合にはそれを接続中のすべてのクライアントに対して転送する。

 まず、送信側となるクライアントのJavaScriptを考えよう。せっかくならば発言したユーザ名などの情報も送りたいので、メッセージ送信にはJSONを利用してみる。WebSocketではバイナリデータを送ることも可能だが、単純なメッセージであればJSONで事足りるだろう。今回は次のようなJSONデータを使うことにする。type属性でメッセージの種類を指定し、data属性でメッセージデータを格納したオブジェクトを渡す。メッセージデータ部分のname属性はユーザ名を、text属性はテキストメッセージを表す。

リスト6.1 メッセージ通信用のJSONフォーマット
■クライアント→サーバー

ログイン時: { type:login, data:{ name:xxx } }
ログアウト時: { type:logout, data:{ name:xxx } }
メッセージ送信時: { type:message, data:{ name:xxx, text:xxx } }


■サーバー→クライアント

メッセージ送信時: { type:message, data:{ text:xxx } }

 接続がオープンしたときには、次のようにしてログインメッセージを送信する。

リスト6.2 ログイン時のJSONデータの送信
webSocket.onopen = function(event) {
    var name = document.getElementById("name").value; // ユーザ名
    var message = {
        type: "login",
        data: { name: name }
    };
    webSocket.send(window.JSON.stringify(message));
}

 接続をクローズするときには、次のようにログアウトメッセージを送信してからclose()メソッドを呼び出す。

リスト6.3 ログアウト時のJSONデータの送信
if (webSocket.readyState == webSocket.OPEN) {
    var name = document.getElementById("name").value; // ユーザ名
    var message = {
        type: "logout",
        data: { name: name }
    };
    webSocket.send(window.JSON.stringify(message));
    webSocket.close();
}

 そしてテキストメッセージを投稿するときには、ユーザ名とメッセージの内容を送信する。送信するメッセージの内容がJSON形式になっただけで、基本的にはテキストメッセージを送る方法と違いはない。

リスト6.4 メッセージを載せたJSONデータの送信
if (webSocket.readyState == webSocket.OPEN) {
    var name = document.getElementById("name").value;                 // ユーザ名
    var inputmessage = document.getElementById('inputmessage').value; // メッセージ
    var message = {
        type: "message",
        data: { name: name, text: inputmessage }
    };
    webSocket.send(window.JSON.stringify(message));
    document.getElementById('inputmessage').value = "";
}

 サーバーから送られてくるメッセージもJSON形式なので、そこからメッセージテキスト部分を取り出して画面のコンテンツに追加する。

リスト6.5 サーバーから送られたJSONデータの受信
webSocket.onmessage = function(event) {
    var receive = JSON.parse(event.data);
    var message = receive.data.text;
    var msgBox = document.getElementById("message");
    var currentText = msgBox.innerHTML;
    msgBox.innerHTML = "<div>" + message + "</div>" + currentText;
}

 さて、サーバー側では受け取ったJSON形式のデータを解析して、type属性の値に応じて処理を変えなければならない。JavaプログラムでJSONデータを扱うために、今回はLibertyプロファイルに付属するJSONライブラリ(com.ibm.json.javaパッケージ)を利用する。このライブラリは「com.ibm.websphere.appserver.api.json_1.0.1.jar」というファイルに納められている。プロジェクトのプロパティ設定で、ビルド・パスに「WebSphere Application Server Liberty Profile」を追加しておけば使うことができる。もちろん、この部分はJSR 353として標準化されている「Java API for JSON Processing(javax.json)」を使うように置き換えてもよい。

図6.6
図6.6

 com.ibm.json.javaでは、JSONデータをJSONObjectというクラスとして定義している。このクラスはMapクラスを継承している。従って、基本的なデータの出し入れはMapクラスと同じ方法でできると考えてよい。JSON形式のテキストデータからJSONObjectオブジェクトに変換するには、JSONObject.parse()メソッドを使用する。次の例は、受け取ったJSONデータから各属性の値を取り出し、type属性に応じてそれぞれ異なるメッセージをクライアントに送信する例である。メッセージの送信は後述するsendMessage()メソッドで行う。

リスト6.7 クライアントからのメッセージの受信
@OnMessage
public void receiveMessage(String msg, Session session) throws IOException {
    JSONObject json = JSONObject.parse(msg);
    String type = (String)json.get("type");
    JSONObject data = (JSONObject)json.get("data");
    String name = (String)data.get("name");
    switch (type) {
        case "login":
            sendMessage(name + "さんが入室しました。", session);
            break;
        case "logout":
            sendMessage(name + "さんが退室しました。", session);
            break;
        case "message":
            String text = (String)data.get("text");
            sendMessage(name + ": " + text, session);
            break;
        default:
            break;
    }
}

 接続が確立しているすべてのセッションに同時にメッセージを送信するには、それらのセッションの情報を知る必要がある。この情報は、1つのSessionオブジェクトから簡単に取得できるようになっている。従ってメッセージを受け取った際には、その送信元となったSessionオブジェクトを取得し、そこから他のセッションのリストを取り出してそれぞれに対して受け取った内容を転送すればいいわけだ。具体的には、次のようにgetOpenSessions()メソッドを呼び出してSessionオブジェクトが格納されたSetオブジェクトを取得し、それぞれにsendText()メソッドを実行するようなコードになる。

リスト6.8 メッセージの一斉送信
private void sendMessage(String msg, Session currentSession) {
    // 返すメッセージのJSON形式
    // { type:message, data:{ message: xxxx } }
    JSONObject data = new JSONObject();
    data.put("text", msg);
    JSONObject response = new JSONObject();
    response.put("type", "message");
    response.put("data", data);
    System.out.println("送信: " + response.toString());

    Set<Session> sessions = currentSession.getOpenSessions();
    for(Session session: sessions) {
        try {
            session.getBasicRemote().sendText(response.serialize());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 以上のことを踏まえて、チャットアプリケーションを実現するクライアント側とサーバー側のプログラムは、添付サンプルコードの「chat.html」および「ChatEndpoint.java」のようになった。

 このプロジェクトを実行してWebブラウザからアクセスすると、最初に図6.9のように表示される。名前を入力して[入室]ボタンを押せば、WebSocketによる通信が開始されて、図6.10のようにサーバーから受け取ったメッセージが表示される。複数のWebブラウザで同時に接続すれば、図6.11のようにチャットを楽しむことができる。

図6.9
図6.9
図6.10
図6.10
図6.11
図6.11

7. jQueryと組み合わせる

 WebSocketのJavaScript APIは極めてシンプルに設計されているので、サードパーティ製のJavaScriptフレームワークとも簡単に組み合わせて利用することができる。本稿では、先ほど作成したチャットアプリケーションのクライアント側のコードを、「jQuery」および「jQuery UI」を組み合わせて使うように書き直した例を、添付サンプルコードの「chat_jquery.html」として用意した。jQueryはjquery.comから、jQuery UIはjqueryui.comから、それぞれ最新版をダウンロードできる。

 基本的にはオブジェクト操作やイベントハンドリングの部分をjQueryを利用する形式に置き換えればよく、WebSocketを使うために特別なことをする必要はない。添付した例では、コードを修正するついでに、接続/切断の状態に応じてボタンやテキストフィールドの活性/非活性を切り替える処理を追加している。また、jQuery UIに用意されたテーマ機能を使っているので、図7.1のように見た目も少しだけ修飾されている。

図7.1
図7.1

 jQueryでWebSocketの利用を少しだけ便利にする「jquery-websocket」のようなプラグインもある。これは、サーバーにメッセージを送る際に自分でJSONオブジェクトを構築する手間を省いてくれるもの。send()メソッドには、次のように第1引数にメッセージのタイプを、第2引数に送りたいデータオブジェクトを渡す。

リスト7.2 jquery-websocketを使ったメッセージの送信
var webSocket = $.websocket("ws://127.0.0.1:8080/", ...);
webSocket.send( 'message', { name: '名前', text: 'テキスト' } );

 すると、jquery-websocketは次のようなJSONデータを作成してサーバーに送信する。

リスト7.3 実際に送られるJSONデータ
{ type: 'message', data: { name: '名前', text: 'テキスト' } );

 もっとも、READMEにもある通りjquery-websocketの開発はアクティブではない。そのため、そのままの形で実務に利用するのはお勧めしないが、ほんの数十行の簡単なJavaScriptコードで作られているため、自前でjQuery用のWebSocketプラグインを作る際には参考になるだろう。

 本稿のサンプルコードには、先ほどのチャットアプリケーションをjquery-websocketを使うように書き直した例を「chat_jquery_websocket.html」として添付した。

8. ドラッグ可能なオブジェクト情報の送受信

 最後に、もう少しリアルタイム性の高い通信を行うサンプルを紹介しよう。チャットアプリケーションと同様にメッセージを複数のクライアントに送信するというものだが、投稿したメッセージは図8.1のように中央の領域に四角いボックスで表示される。自分で投稿したメッセージと他人が投稿したメッセージはボックスの色で区別できる。このボックスはマウスでドラッグして移動することができる。誰かが自分のクライアント内でボックスを移動すると、図8.2のように他のクライアントに表示されているボックスも追随して同じように移動する。

図8.1
図8.1
図8.2
図8.2

 ドラッグ可能なメッセージボックスはjQuery UIのDraggableオブジェクトを利用して実装している。次のように入力したメッセージを表示するdiv要素に対してdraggable()メソッドを呼び出すだけで、ドラッグによる移動が可能になる。ここではcontainment属性でドラッグ可能な範囲を指定している。

リスト8.3 jQuery UIによるDraggableオブジェクトの作成
var box = $("<div class='box ui-widget-content'>").text(text);
box.draggable({ containment: "[data-name='draggable-area']" })

 メッセージボックスの情報は、次のようなJSON形式にしてサーバー/クライアント間で共有する(なお、この例ではjquery-websocketは使用していない)。

リスト8.4 通信に利用するJSONデータのフォーマット
{ type: 'box',
  data: { boxid: <識別子>, 
          top: <y座標>, 
          left: <x座標>, 
          text: <メッセージ>, 
          owner: <作成元のセッションID> } }

 例えば、ボックスがドラッグされた場合には、次のようにしてJSON化した情報を送信すればよい。

リスト8.5 メッセージボックスをJSON化して送信
function msgbox_dragged(event, ui) {
    if (webSocket != null && webSocket.readyState == webSocket.OPEN) {
        var object = {
            type: "box", 
            data: { boxid: event.target.id,
                    top: ui.offset.top.toFixed(0) - baseX,
                    left: ui.offset.left.toFixed(0) - baseY, 
                    text: $('#'+event.target.id).text(),
                    owner: $('#'+event.target.id).data("owner")
                  }
        };
        var msg = window.JSON.stringify(object);
        webSocket.send(msg);
    }
}

 サーバーからのメッセージを受信した際には、IDを元にして該当するボックスを特定し、それを同じ場所に移動する。

リスト8.6 JSON化されたメッセージボックスの受信
webSocket.onmessage = function(event) {
    var message = event.data;
    var json = window.JSON.parse(message);
    if (json.type == "box") {
        var boxdata = json.data;
        var element = document.getElementById(boxdata.boxid);
        if(element!=null) {
            // すでに同じIDのボックスがある場合はそれを移動する
            $("#" + boxdata.boxid).offset({ "top": baseX + boxdata.top, "left": baseY + boxdata.left });
        }
        else {
            // 無い場合は新規で作成する
            makeBox(boxdata.boxid, boxdata.top, boxdata.left, boxdata.text, boxdata.owner);
        }
    }
}

 サーバー側のエンドポイントは、送られてきたメッセージボックスの情報をそのまま接続中のすべてのクライアントに転送するような実装になる。

 このサンプルの実装例を、クライアント側を「messageboard.html」、サーバー側を「MessageBoardEndpoint.java」としてサンプルコードに添付したので、詳細はそちらを参照していただきたい。

9. まとめ

 今回はLibertyプロファイルおよびLiberty Coreで新たに正式サポートされたWebSocketを使って、サーバー/クライアント間でリアルタイム通信を行うWebアプリケーションを作ってみた。WebSocketはプロトコルレベルでHTTPとは異なるため通信を確立するための内部的な処理は複雑だが、プログラム側からこれを利用するためのAPIは極めてシンプルに設計されており、内部の複雑さをまったく意識することなく使用することができる。

 WebSocketが登場した初期の段階では、サーバー環境を構築するための選択肢が限られているという問題があったが、現在は各種サーバー製品によるサポートも進んできている。WASもいち早くWebSocketに対応したJavaアプリケーションサーバーの一つである。このように少しずつWebSocketを使う環境が整っていく中で、将来的にこの新しい技術を商用分野でも活用していきたいと考えたならば、重要となるのはやはりエンタープライズレベルのニーズに耐えられるインフラをいかにして構築するかという点だ。

 Liberty Coreを利用する場合、今回紹介したようにサーバー側の特別な設定はほとんど必要とせずにWebSocektにも対応できる。Javaで完結したアプリケーションだけでなく、JavaScriptや他の言語によるクライアントの実装とも高い親和性を持っている。そしてエンタープライズ規模への適性についてもWASの実績に裏付けされたものがある。この実績は、将来的なスケールアップを考えたときにTomcatをはじめとするオープンソース系のアプリケーションサーバにはない安心感をもたらしてくれるものだ。Libertyプロファイルの手軽さにWASの信頼性・安定性をプラスしたLiberty Coreは、リアルタイムWebという新しい潮流を商用サービスに導入していく上で極めて有力な選択肢ということができるだろう。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加

【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/8418 2015/02/27 19:04

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング