第14章 WebSocket アプリケーションの作成

WebSocket プロトコルは、Web クライアントとサーバー間の双方向通信を提供します。クライアントとサーバー間の通信はイベントベースであるため、ポーリングベースの通信よりも処理が高速になり、帯域幅が小さくなります。WebSocket は、JavaScript API を用いて Web アプリケーションで使用したり、Java Websocket API を用いてクライアント WebSocket エンドポイントで使用したりできます。

最初に接続はクライアントとサーバー間で HTTP 接続として確立されます。その後、クライアントは Upgrade ヘッダーを使用して WebSocket 接続を要求します。同じ TCP/IP 接続上ではすべて全二重通信になり、データのオーバヘッドが最小化されます。各メッセージには不必要な HTTP ヘッダーコンテンツが含まれていないため、Websocket 通信で必要な帯域幅は小さくなります。その結果、通信パスのレイテンシーが低くなるため、リアルタイムの応答が必要なアプリケーションに適しています。

JBoss EAP WebSocket 実装は、サーバーエンドポイントに対して完全な依存関係注入サポートを提供しますが、クライアントエンドポイントに対して CDI サービスを提供しません。

WebSocket アプリケーションには以下のコンポーネントと設定変更が必要です。

  • Javaクライアントまたは WebSocket が有効になっている HTML クライアント。HTML クライアントのブラウザーサポートは、http://caniuse.com/#feat=websockets を参照してください。
  • WebSocket サーバーエンドポイントクラス。
  • WebSocket API で依存関係を宣言するために設定されたプロジェクト依存関係。

WebSocket アプリケーションの作成

以下のコード例は、JBoss EAP に同梱される websocket-hello クイックスタートの一部です。これは、接続を開き、メッセージを送信し、接続を閉じる WebSocket アプリケーションの単純な例です。他の機能を実装したり、実際のアプリケーションで必要となるエラー処理を含むことはありません。

  1. JavaScript HTML クライアントを作成します。

    以下は WebSocket クライアントの例になります。この例には 3 つの JavaScript 関数が含まれています。

    • connect(): この関数は WebSocket URI を渡す WebSocket 接続を作成します。リソースの場所は、サーバーエンドポイントクラスに定義されたリソースと一致します。この関数は、WebSocket の onopenonmessageonerror、および onclose もインターセプトし、処理します。
    • sendMessage(): この関数はフォームに入力された名前を取得し、メッセージを作成します。 さらに、WebSocket.send() コマンドを使用してメッセージを送信します。
    • disconnect(): この関数は WebSocket.close() コマンドを実行します。
    • displayMessage(): この関数は、ページ上の表示メッセージを WebSocket エンドポイントメソッドによって返された値に設定します。
    • displayStatus(): この関数は WebSocket の接続状態を表示します。

      例: アプリケーション index.html コード

      <html>
        <head>
          <title>WebSocket: Say Hello</title>
          <link rel="stylesheet" type="text/css" href="resources/css/hello.css" />
          <script type="text/javascript">
            var websocket = null;
            function connect() {
              var wsURI = 'ws://' + window.location.host + '/websocket-hello/websocket/helloName';
              websocket = new WebSocket(wsURI);
              websocket.onopen = function() {
                  displayStatus('Open');
                  document.getElementById('sayHello').disabled = false;
                  displayMessage('Connection is now open. Type a name and click Say Hello to send a message.');
              };
              websocket.onmessage = function(event) {
                  // log the event
                  displayMessage('The response was received! ' + event.data, 'success');
              };
              websocket.onerror = function(event) {
                  // log the event
                  displayMessage('Error! ' + event.data, 'error');
              };
              websocket.onclose = function() {
                  displayStatus('Closed');
                  displayMessage('The connection was closed or timed out. Please click the Open Connection button to reconnect.');
                  document.getElementById('sayHello').disabled = true;
              };
            }
            function disconnect() {
              if (websocket !== null) {
                  websocket.close();
                  websocket = null;
              }
              message.setAttribute("class", "message");
              message.value = 'WebSocket closed.';
              // log the event
            }
            function sendMessage() {
              if (websocket !== null) {
                  var content = document.getElementById('name').value;
                  websocket.send(content);
              } else {
                  displayMessage('WebSocket connection is not established. Please click the Open Connection button.', 'error');
              }
            }
            function displayMessage(data, style) {
              var message = document.getElementById('hellomessage');
              message.setAttribute("class", style);
              message.value = data;
            }
            function displayStatus(status) {
              var currentStatus = document.getElementById('currentstatus');
              currentStatus.value = status;
            }
          </script>
        </head>
        <body>
          <div>
            <h1>Welcome to Red Hat JBoss Enterprise Application Platform!</h1>
            <div>This is a simple example of a WebSocket implementation.</div>
            <div id="connect-container">
              <div>
                <fieldset>
                  <legend>Connect or disconnect using websocket :</legend>
                  <input type="button" id="connect" onclick="connect();" value="Open Connection" />
                  <input type="button" id="disconnect" onclick="disconnect();" value="Close Connection" />
                </fieldset>
              </div>
              <div>
                  <fieldset>
                    <legend>Type your name below, then click the `Say Hello` button :</legend>
                    <input id="name" type="text" size="40" style="width: 40%"/>
                    <input type="button" id="sayHello" onclick="sendMessage();" value="Say Hello" disabled="disabled"/>
                  </fieldset>
              </div>
              <div>Current WebSocket Connection Status: <output id="currentstatus" class="message">Closed</output></div>
              <div>
                <output id="hellomessage" />
              </div>
            </div>
          </div>
        </body>
      </html>

  2. WebSocket サーバーエンドポイントを作成します。

    以下の方法のいずれかを使用して WebSocket サーバーエンドポイントを作成できます。

    • Programmatic Endpoint: エンドポイントは Endpoint クラスを拡張します。
    • Annotated Endpoint: エンドポイントクラスはアノテーションを使用して WebSocket イベントと対話します。これは、プログラム的なエンドポイントよりも簡単にコーディングできます。

    以下のコード例では、アノテーション付きエンドポイントが使用され、以下のイベントが処理されます。

    • @ServerEndpoint アノテーションは、このクラスを WebSocket サーバーエンドポイントとして識別し、パスを指定します。
    • WebSocket 接続が開かれると @OnOpen アノテーションがトリガーされます。
    • メッセージが受信されると、@OnMessage アノテーションがトリガーされます。
    • WebSocket 接続が閉じられると、@OnClose アノテーションがトリガーされます。

      例: WebSocket エンドポイントコード

      package org.jboss.as.quickstarts.websocket_hello;
      
      import javax.websocket.CloseReason;
      import javax.websocket.OnClose;
      import javax.websocket.OnMessage;
      import javax.websocket.OnOpen;
      import javax.websocket.Session;
      import javax.websocket.server.ServerEndpoint;
      
      @ServerEndpoint("/websocket/helloName")
      public class HelloName {
      
          @OnMessage
          public String sayHello(String name) {
              System.out.println("Say hello to '" + name + "'");
              return ("Hello" + name);
          }
      
          @OnOpen
          public void helloOnOpen(Session session) {
              System.out.println("WebSocket opened: " + session.getId());
          }
      
          @OnClose
          public void helloOnClose(CloseReason reason) {
              System.out.println("WebSocket connection closed with CloseCode: " + reason.getCloseCode());
          }
      }

  3. プロジェクト POM ファイルで WebSocket API の依存関係を宣言します。

    Maven を使用する場合は、プロジェクト pom.xml ファイルに以下の依存関係を追加します。

    例: Maven 依存関係

    <dependency>
      <groupId>org.jboss.spec.javax.websocket</groupId>
      <artifactId>jboss-websocket-api_1.1_spec</artifactId>
      <scope>provided</scope>
    </dependency>

JBoss EAP に同梱されるクイックスタートには、追加の WebSocket クライアントとエンドポイントのコード例が含まれます。