この章では、WebLogic ServerでのWebSocketプロトコルの実装について説明します。
この章の内容は次のとおりです。
WebLogic ServerではWebSocketプロトコル(RFC 6455)がサポートされており、単一のTCP接続を介したクライアントとサーバー間の双方向同時通信が可能で、一方の側がもう一方の側とは独立してデータを送信できます。WebSocket接続を開始するために、クライアントはハンドシェイク・リクエストをサーバーに送信します。ハンドシェイク・リクエストが検証に合格し、サーバーがリクエストを受け入れると、接続が確立されます。WebSocket接続が作成されると、ブラウザ・クライアントは、WebLogic Serverインスタンスにデータを送信し、同時にそのサーバー・インスタンスからデータを受け取ることができます。HTML5仕様(http://www.w3.org/TR/html5/)の一部として、WebSocketプロトコルは、ほとんどのブラウザによってサポートされています。WebSocketプロトコルの全般的な情報については、http://tools.ietf.org/html/rfc6455を参照してください。
WebLogic Serverのサンプル・コンポーネントをマシンにインストールして構成すると、WebLogic ServerでのWebSocketの使用を実証するためのHTML5 WebSocketのサンプルを使用することができます。このサンプルの実行方法の詳細は、『Oracle WebLogic Serverの理解』のサンプル・アプリケーションおよびサンプル・コードに関する項を参照してください。
次の項では、WebLogic ServerにおけるWebSocketのライフ・サイクルについて説明します。
WebSocket接続はすべてクライアントによって開始されます。WebSocket接続を確立するには、クライアントがハンドシェイク・リクエスト(HTTP GETリクエスト)をサーバーに送信する必要があります。HTTPリクエストが、@WebSocketアノテーションのpathPatterns属性に一致する場合は、内部WebSocketサーブレットによりハンドシェイク・リクエストが検証されます。検証プロセスは、(WebSocketリスナーのacceptメソッドに実装された)アプリケーションの検証と、WebSocketプロトコルに定義されたルールに従います。ハンドシェイク・リクエストが有効な場合、サーブレットによりHTTPリクエストがWebSocket接続にアップグレードされ、サーバーからクライアントにWebSocketハンドシェイク・レスポンスが送信されます。リクエストが無効な場合、サーブレットによりリクエストが拒否されます。
WebSocket接続が確立された後、サーバーからリクエスト・スレッドが解放され、接続上の読込みイベントがリスニングされます。サーバーが読込みイベントを検出すると、サーバーは、WebSocketプロトコルの定義に従ってメッセージをデコードし、リスナーの対応するメソッドを呼び出します。たとえば、サーバーは、メッセージがテキストまたはバイナリ・メッセージである場合はonMessageメッセージを呼び出し、メッセージがフラグメントである場合はonFragmentメソッドを呼び出します。@WebSocketアノテーションのdispatchPolicy属性で指定されているワーク・マネージャは、これらのメソッドを呼び出すスレッドを制御します。
それと同時に、WebSocketが閉じられるまでWebSocketConnectionインタフェースの対応するメソッドを呼び出すことで、メッセージをWebSocketに送信することができます。例:
sendメソッドでは、WebSocketにメッセージが送信されます。
sendPingおよびsendPongメソッドでは、PingおよびPong制御フレームが送信されます。
streamメソッドでは、メッセージ・フラグメントが送信されます。
WebSocketに関連付けられたコンテキストは、その固有のWebSocketに結び付けられます。メッセージの送信は同期タスクであり、コールを発行するスレッド上で実行されます。
構成されたタイムアウト期間内に読込みまたは書込みアクティビティが発生せず、Pingメッセージを受け取らなかった場合、WebSocketはタイムアウトになります。コンテナにより、デフォルトで30秒のタイムアウト時間が強制されます。タイムアウト時間が-1に設定されている場合、接続のタイムアウトは設定されません。
タイムアウト時に、コンテナはリスナーのonTimeout()メソッドを呼び出します。このとき、リスナーはクライアントに適切なメッセージを送信します。onTimeout()メソッドが返された後、コンテナによりWebSocketが閉じられます。
WebSocket通信中いつでも、クライアントまたはサーバーはWebSocket接続を切断することができます。クライアントがクローズ・リクエストを発行すると、コンテナはリスナーのonClose()メソッドを呼び出して、基礎となる接続を切断します。サーバー側からは、WebSocketConnection.closeメソッドを使用して、WebSocket接続を閉じることができます。これらのメソッドが呼び出されると、サーバーはクライアントにクローズ・リクエストを発行してから、接続を閉じます。
WebSocket接続にアップグレードした後、メッセージはいずれの方向にも非同期的に送信可能です。ただし、メッセージの破損を避けるために、メッセージの送信は同期的である必要があります。複数のスレッドが同じWebSocket上でメッセージを送信しようとした場合、順序は保証されません。最初にロックを取得したスレッドが、最初のメッセージを送信します。
クライアントにメッセージを送信するWebSocketConnectionのメソッドは、スレッド・セーフではありません。複数のスレッドからWebSocketConnectionメソッドの同じインスタンスを使用してメッセージが送信された場合、アプリケーションで適切なロック機構を使用してメッセージを同期的に送信する必要があります。そうしない場合、メッセージが破損する可能性があります。
WebLogic Serverで、WebSocketプロトコルの実装とそれに付随するプログラミングAPIを使用して、クライアントと双方向通信するアプリケーションを開発およびデプロイすることができます。いかなる種類のクライアント/サーバー通信にもWebSocketを使用することができますが、WebSocket APIを使用するWebページを実行するブラウザとの通信には、この実装が最もよく使用されます。
次の項では、WebLogic ServerでWebSocketを使用する方法について説明します。
WebLogic ServerのWebSocketの実装には、次のコンポーネントが含まれています。
WebSocketプロトコルは、接続アップグレードの処理、接続の確立と管理およびクライアントとのやりとりの処理を行います。既存のWebLogic Serverスレッドとネットワーク・インフラストラクチャを使用して、WebLogic Serverは、その実装内でWebSocketプロトコルを完全に統合します。
WebLogic WebSocket API weblogic.websocketパッケージを使用して、WebSocketアプリケーションを開発することができます。表17-1では、このAPIに含まれるクラスとインタフェースを示し、それらについて簡単に説明します。詳細は、『Oracle WebLogic Server Java APIリファレンス』のweblogic.websocketを参照してください。
表17-1 WebLogic WebSocket Java APIのインタフェースとクラス
| 名前 | 説明 |
|---|---|
|
WebSocketメッセージを受信して処理するインタフェースです。WebSocketへのHTTPアップグレード・リクエストを処理する、対応するサーブレットにリスナーをワイヤリングする責任は、コンテナが持ちます。 |
|
|
クライアントとサーバー間のWebSocket接続を表すインタフェースです。アプリケーションは |
|
|
WebSocketリスナーによって処理される一連のWebSocket接続に関するコンテキスト情報を提供するインタフェースです。またこのインタフェースは、
|
|
|
|
|
|
WebSocketオープニング・ハンドシェイク・リクエスト情報をWebSocketリスナーに提供するオブジェクトを定義するクラスです。 |
|
|
オープニング・ハンドシェイク・レスポンスのクライアントへの送信を補助するオブジェクトを定義するクラスです。 |
@WebSocketアノテーションは、WebSocketプロトコルを使用して通信するWebSocketエンドポイントを宣言します。@WebSocketアノテーションを使用して、読込みイベントを処理するWebSocketエンドポイントとして公開する準備を整えるWebSocketリスナーとしてクラスをマークします。@WebSocketアノテーションによってアノテーションを付けられたクラスは、WebSocketListenerインタフェースを実装するか、WebSocketAdapterクラスから拡張する必要があります。詳細は、『Oracle WebLogic Server Java APIリファレンス』のweblogic.websocket.annotation.WebSocketを参照してください。
例17-1は、@WebSocketアノテーションの使用方法を示しています。
WebLogic WebSocket APIを使用すると、複数のHTTP接続をオープンにすることなく、サーバーとの双方向通信を必要とするブラウザベースのアプリケーションを作成することができます。
WebLogic Serverでは、wlserver/server/lib/wls-api.jarファイル内にWebLogic WebSocket APIが用意されています。WebLogic WebSocket APIを使用してアプリケーションを構築するには、アプリケーションのコンパイル時にクラスパスにこのライブラリを定義します。
Mavenを使用して、WebLogic WebSocket APIを使用したアプリケーションを構築することもできます。詳細は、「WebLogic開発Mavenプラグインの使用方法」を参照してください。
WebLogic Serverでは、標準のJava EE Webアプリケーション・アーカイブ(WARs)の一部としてWebSocketアプリケーションをデプロイします。これは、スタンドアロンのWebアプリケーションまたはエンタープライズ・アプリケーション内のWARモジュールのいずれかとして行います。Webアプリケーションをデプロイする際、WebLogic Serverはクラス上で@WebSocketアノテーションを検出し、それをWebSocketエンドポイントとして自動的に設定します。
web.xmlファイルやその他のデプロイメント記述子でWebSocketエンドポイントを構成したり、WebSocketエンドポイントを登録または有効にするために動的な操作を実行する必要はありません。
WebSocketアプリケーションにアクセスするクライアントは、WebLogic Serverインスタンスに直接接続するか、またはWebSocketプロトコルをサポートするWebプロキシ・サーバーを介して接続する必要があります。
現在、WebSocketをサポートする唯一のOracleプロキシ・サーバーは、Oracle Traffic Directorです。別のプロキシ・サーバーを使用してWebSocketアプリケーションにアクセスする場合は、そのプロキシ・サーバーでWebSocketがサポートされていることを確認してください。
WebSocketクライアント・アプリケーションは、通常、HTML5技術(HTMLマークアップ、CSS3、WebSocket JavaScript APIを利用するJavaScriptなど)の複合体です。Oracleでは、WebSocketクライアント用のWebSocket JavaScript APIを提供していません。ただし、ほとんどのブラウザでは、WebSocketを作成して動かすために使用できる標準のWebSocket APIをサポートしています。HTML5の詳細は、http://www.w3.org/TR/html5/を参照してください。
次の手順は、WebSocketプロトコルを使用してWebLogic Serverインスタンスにメッセージを送信する、クライアント上の実行フローの例を示しています。
クライアントは、ws://またはwss://プロトコル接頭辞を使用して、WebSocketエンドポイントをホストするサーバーへのWebSocket接続を開きます。詳細は、「セキュアなWebSocket接続の確立」を参照してください。
url = ((window.location.protocol == "https:") ? "wss:" : "ws:") + "//" + window.location.host + "/websocket-helloworld-wls/helloworld_delay.ws"; ws = new WebSocket(url);
クライアントは、リスナーをWebSocketオブジェクトに登録して、メッセージのオープン、クローズ、受信などのイベントに応答します。受信したイベントや情報に基づき、クライアントは適切なアクションを実行します。
ws.onopen = function(event) {
document.getElementById("status").innerHTML = "OPEN"
}
ws.onmessage = function(event) {
msg = event.data
document.getElementById("short_msg").innerHTML =
event.data;
}
クライアントは、アプリケーションの必要に応じて、WebSocketオブジェクトを介してサーバーにメッセージを送信します。
function sendMsg() {
// Check if connection is open before sending
if(ws == null || ws.readyState != 1) {
document.getElementById("reason").innerHTML
= "Not connected can't send msg"
} else {
ws.send(document.getElementById("name").value);
}
}
<input id="send_button" class="button" type="button" value="send" onclick="sendMsg()"/>
場合によっては、アプリケーションから、開いているすべてのWebSocket接続にメッセージを送信する必要があります。たとえば、株式アプリケーションの場合は、接続されているWebSocketクライアントに株価を送信する必要があり、チャット用アプリケーションの場合は、1人のユーザーから同じチャット・ルーム内の他のすべてのクライアントにメッセージを送信する必要があります。
すべてのWebSocketクライアントにメッセージをブロードキャストするために、アプリケーションではまず、WebSocketContextインタフェースのgetWebSocketConnections()メソッドを使用して、開いているすべてのWebSocket接続を判別する必要があります。次に、WebSocketConnectionをそれぞれ繰り返して、各クライアントにメッセージを送信することができます。
次の簡単なコードは、クライアントにメッセージをブロードキャストする方法を示しています。
import weblogic.websocket.WebSocketListener;
import weblogic.websocket.WebSocketConnection;
import weblogic.websocket.WebSocketContext;
@WebSocket(pathPatterns={"/demo"})
public class MyListener implements WebSocketListener {
…
public void broadcast(String message) {
for(WebSocketConnection conn :
getWebSocketContext().getWebSocketConnections()) {
try {
conn.send(message);
} catch (IOException ioe) {
// handle the error condition.
}
}
}
}
実際のアプリケーションでは、前述のコード・サンプルでは対処されていない次の2つの重要な側面を考慮する必要があります。
効率
開いているWebSocket接続が多すぎる場合、1つのスレッドを使用してメッセージをブロードキャストすることは非効率です。なぜなら、繰り返しプロセスにおいては、クライアントがメッセージを受信するのにかかる時間は、その場所に依存するからです。何千ものWebSocket接続が開いていると、繰り返しが遅くなるため、一部のクライアントはメッセージを早く受信するようになりますが、メッセージの受信がかなり後になるクライアントも出てきます。この遅れは、特定の状況では許容できません。たとえば、株式アプリケーションでは、各クライアントができるかぎり早く株価データを受信する必要があります。
効率を上げるために、アプリケーションで、開いているWebSocket接続を複数のグループに分割し、複数のスレッドを使用して、WebSocket接続の各グループにメッセージをブロードキャストすることができます。
同時実行性
WebSocketConnectionインタフェースの実装は、スレッド・セーフではありません。適切なロック機構を使用することなく、複数のスレッドから同じWebSocketConnectionインスタンスでメッセージが送信された場合、これらのメッセージに問題が発生する可能性があります。メッセージに問題があると、クライアントがメッセージを正しく解釈できないため、接続が予期せずに閉じられる場合があります。
アプリケーションでは、1つのスレッドが特定のWebSocketConnectionインスタンス上でメッセージを送信したときに、他のスレッドが同じWebSocketConnectionインスタンス上でメッセージを送信できないようにする適切なロック機構を選択する必要があります。
WebLogic Serverでは、Webアプリケーション・アーカイブ(WARs)の一部としてWebSocketアプリケーションをデプロイします。これは、スタンドアロンのWebアプリケーションまたはエンタープライズ・アプリケーション内のWARモジュールのいずれかとして行います。したがって、Webアプリケーションの保護に適用する多くのセキュリティ上の措置を、WebSocketアプリケーションに適用することができます。Webアプリケーションのセキュリティの詳細は、『WebLogicセキュリティ・サービスによるアプリケーションの開発』のセキュアなWebアプリケーションの開発に関する項を参照してください。
次の項では、WebLogic ServerにおけるWebSocketアプリケーションのセキュリティに関する考慮事項について説明します。
最新のブラウザでは、ある生成元からロードされたWebページ上で実行されているスクリプトが、別の生成元のリソースとやりとりしないようにするために、同一生成元ポリシーが使用されています。WebSocketプロトコル(RFC 6455)では、サーバーが生成元横断接続に同意するかどうかを決定できる、検証済の生成元ポリシーが使用されています。
スクリプトがWebSocketアプリケーションにオープニング・ハンドシェイク・リクエストを送信すると、WebSocketハンドシェイク・リクエストを使用してOrigin HTTPヘッダーが送信されます。アプリケーションは、Originを検証しない場合、すべての生成元からの接続を受けれ入れます。予想される生成元以外の生成元からの接続を受け入れないようにアプリケーションを設定すると、WebSocketアプリケーションは、接続を拒否することができます。WebSocketアプリケーションでWebSocketListener実装クラスのacceptメソッドを通してOriginを検証するようにできます。
次のコード・サンプルは、検証済の生成元ポリシーを適用する方法を示しています。
import weblogic.websocket.WebSocketListener;
import weblogic.websocket.WSHandshakeRequest;
import weblogic.websocket.WSHandshakeResponse;
@WebSocket(pathPatterns={"/demo"})
public class MyListener implements WebSocketListener {
private static final String ORIGIN = "http://www.example.com:7001";
public boolean accept(WSHandshakeRequest request, WSHandshakeResponse response) {
return ORIGIN.equals(request.getOrigin());
}
…
}
|
注意: ブラウザ以外のクライアント(Java Clientなど)では、WebSocketハンドシェイク・リクエストを使用して ブラウザ以外のクライアントが、 |
WebSocketプロトコル(RFC 6455)では、ハンドシェイク処理時のWebSocketクライアントの認証方法を指定しません。標準のWebコンテナ認証および認可機能を使用すると、未認可のクライアントがサーバー上でWebSocket接続を開かないようにすることができます。
認証
Webアプリケーションでサポートされている<auth-method要素の構成はすべて、WebSocketアプリケーションにも使用できます。これらの認証タイプとして、BASIC、FORM、CLIENT-CERTなどがあげられます。詳細は、『WebLogicセキュリティ・サービスによるアプリケーションの開発』のセキュアなWebアプリケーションの開発に関する項を参照してください。
WebSocketアプリケーションのweb.xmlデプロイメント記述子ファイルで該当する<security-constraint要素を構成することにより、pathPatterns属性をセキュリティで保護することができます。クライアントは、<security-constraintを構成することで、WebSocketハンドシェイク・リクエストを送信する前に認証する必要があります。そうしない場合、サーバーはWebSocketハンドシェイク・リクエストを拒否します。<security-constraint要素の詳細は、『Oracle WebLogic Server Webアプリケーション、サーブレット、JSPの開発』のweb.xmlデプロイメント記述子要素に関する項を参照してください。
次のコード・サンプルは、WebSocketListener実装クラス上でpathPatternsをセキュリティで保護する方法を示しています。ここで、pathPatterns={"/demo"}です。
<security-constraint>
<web-resource-collection>
<web-resource-name>Secured WebSocket Endpoint</web-resource-name>
<url-pattern>/demo/*</url-pattern>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/error.jsp</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>user</role-name>
</security-role>
認可
WebSocketListener実装クラスのacceptメソッドで特定のクライアントを認可するように、WebSocketアプリケーションを構成することができます。クライアントがWebSocket接続を作成することをアプリケーションが認可しない場合、サーバーはそのクライアントからのWebSocketハンドシェイク・リクエストを拒否します。
次のコード・サンプルは、WebSocketListener実装クラスのacceptメソッドを構成する方法を示しています。
import weblogic.websocket.WebSocketListener;
import weblogic.websocket.WSHandshakeRequest;
import weblogic.websocket.WSHandshakeResponse;
@WebSocket(pathPatterns={"/demo"})
public class MyListener implements WebSocketListener {
private static final String ORIGIN = "http://www.example.com:7001";
public boolean accept(WSHandshakeRequest request, WSHandshakeResponse response) {
return ORIGIN.equals(request.getOrigin()) || hasPermission(request.getUserPrincipal());
}
private boolean hasPermission(Principal user) {
// Verifies whether the user is authorized to create this WebSocket connection.
}
…
}
WebSocket接続を確立するために、クライアントはハンドシェイク・リクエストをサーバーに送信します。ws:// URIを使用してWebSocket接続を開く場合、ハンドシェイク・リクエストはプレーンHTTPリクエストになります。確立されたWebSocket接続を介して転送されるデータは暗号化されません。
セキュアなWebSocket接続を確立し、データがインターセプトされないようにするには、WebSocketアプリケーションが、wss:// URIを使用する必要があります。wss:// URIを使用すると、クライアントがハンドシェイク・リクエストをHTTPSリクエストとして送信し、転送されるデータがTLS/SSLによって暗号化されるようになります。
HTTPSハンドシェイク・リクエストのみを受け入れるようにWebSocketアプリケーションを構成することができます(この場合、すべてのWebSocket接続を暗号化する必要があり、暗号化されていないWebSocketハンドシェイク・リクエストは拒否されます)。WebSocketアプリケーションのweb.xmlデプロイメント記述子ファイルで<user-data-constraint要素を指定します。<user-data-constraint要素の詳細は、『Oracle WebLogic Server Webアプリケーション、サーブレット、JSPの開発』のweb.xmlデプロイメント記述子要素に関する項を参照してください。
次のコード・サンプルは、<user-data-constraint>要素の構成方法を示しています。
<security-constraint>
<web-resource-collection>
<web-resource-name>Secured WebSocket Endpoint</web-resource-name>
<url-pattern>/demo/*</url-pattern>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
スクリプトではws:// URI (プレーンHTTPリクエストを使用)を通してWebSocket接続を開こうとし、一方でトップレベルのWebページがHTTPSリクエストを通して取得される場合、そのWebページは複合コンテンツと呼ばれます。ほとんどのブラウザは複合コンテンツを許可しなくなっていますが、まだ許可しているものもあります。WebSocketアプリケーションでは混合コンテンツを回避する必要があります。なぜなら、これによってJSESSIONIDやCookieなどの保護する必要がある特定の情報の公開が可能になるからです。
混合コンテンツの詳細は、http://www.w3.org/TR/wsc-ui/#securepageのWeb Security Context: User Interface Guidelinesを参照してください。
WebLogic Server WebSocket APIでは、@WebSocketアノテーションの属性を構成することにより、WebSocket接続の制限を指定することができます。制限を指定するようにアプリケーションを構成することで、クライアントから多数の接続がリクエストされ、場合によってはメモリーやソケットなどのサーバーのリソースが使い果たされてしまうような状況が避けられます。
次の制限を指定することができます。
timeout
timeout属性の値は、WebSocket接続のアイドル・タイムアウト値を秒単位で与えます。
WebSocketプロトコル(RFC 6455)では、WebSocket接続が開いたままになるように、PingおよびPongコントロール・フレームが定義されています。timeout属性で大きい値を指定するかわりに、アプリケーションでは、WebSocket接続が開いたままになるように、PingまたはPongメッセージを送信する必要があります。
maxMessageSize
maxMessageSize属性の値は、サーバーが受信できるWebSocketメッセージの最大サイズを指定します。サーバーは、このサイズを超えるメッセージをすべて拒否します。メッセージが大きすぎる場合、クライアントはステータス・コード1009とともにクローズ・フレーム・メッセージを受信し、WebSocket接続が閉じられます。アプリケーションは、この属性に対して適切な値を指定する必要があります。
ステータス・コード1009の詳細は、http://tools.ietf.org/html/rfc6455のWebSocketプロトコル(RFC 6455)を参照してください。
maxConnections
maxConnections属性の値は、このWebSocket上で開ける接続の最大数を指定します。開いている接続の数がこの値を超えると、サーバーは新しいWebSocketハンドシェイク・リクエストをすべて拒否します。アプリケーションは、この属性に対して適切な値を指定する必要があります。
次のコード・サンプルは、これらの限を設定する方法を示しています。
import weblogic.websocket.WebSocketListener;
import weblogic.websocket.WSHandshakeRequest;
import weblogic.websocket.WSHandshakeResponse;
@WebSocket(pathPatterns={"/demo"},
timeout=15,
maxMessageSize=1024,
maxConnections=1000)
public class MyListener implements WebSocketListener {
…
}
例17-2では、WebLogic WebSocket APIを使用しています。この例では、WebSocketリスナーがブラウザからメッセージを受信して、受信したメッセージをカプセル化し、ブラウザに戻しています。
例17-2 WebLogic ServerでのWebSocketの使用
package examples.webapp.html5.websocket;
import weblogic.websocket.ClosingMessage;
import weblogic.websocket.WebSocketAdapter;
import weblogic.websocket.WebSocketConnection;
import weblogic.websocket.annotation.WebSocket;
import java.io.IOException;
@WebSocket(
timeout = -1,
pathPatterns = {"/ws/*"}
)
public class MessageListener extends WebSocketAdapter {
@Override
public void onMessage(WebSocketConnection connection, String payload) {
// Sends message from the browser back to the client.
String msgContent = "Message \"" + payload + "\" has been received by server.";
try {
connection.send(msgContent);
} catch (IOException e) {
connection.close(ClosingMessage.SC_GOING_AWAY);
}
}
}