アノテーション型 Push
@Qualifier @RetentionSE(RUNTIMESE) @TargetSE({METHODSE,FIELDSE,PARAMETERSE}) public @interface Push
CDI アノテーション
@
Push
を使用すると、WAR 内のコンテナー管理のアーティファクトに、特定の<f:websocket>
チャネルに関連付けられたPushContext
を挿入できます。@Inject @Push private PushContext channelName;
構成
まず、
web.xml
の boolean コンテキストパラメーターで、websocket エンドポイントを有効にします。<context-param> <param-name>jakarta.faces.ENABLE_WEBSOCKET_ENDPOINT</param-name> <param-value>true</param-value> </context-param>
使用方法 (クライアント)
少なくとも
channel
名とonmessage
JavaScript リスナー関数を使用して、Jakarta Faces ビューで<f:websocket>
タグを宣言します。チャネル名は Jakarta Expression Language 式ではなく、英数字、ハイフン、アンダースコア、ピリオドのみを含めることができます。これは、既存の JavaScript リスナー関数を参照する例です。
<f:websocket channel="someChannel" onmessage="someWebsocketListener" />
function someWebsocketListener(message, channel, event) { console.log(message); }
インライン JavaScript リスナー関数を宣言する例を次に示します。
<f:websocket channel="someChannel" onmessage="function(message) { console.log(message); }" />
onmessage
JavaScript リスナー関数は、次の 3 つの引数で呼び出されます。message
: JSON オブジェクトとしてのプッシュメッセージ。channel
: チャンネル名。event
: 生のMessageEvent
インスタンス。
サーバーが HTTP コンテナーとは異なる TCP ポートで WS コンテナーを実行するように構成されている場合は、
web.xml
のオプションのjakarta.faces.WEBSOCKET_ENDPOINT_PORT
整数コンテキストパラメーターを使用して、ポートを明示的に指定できます。<context-param> <param-name>jakarta.faces.WEBSOCKET_ENDPOINT_PORT</param-name> <param-value>8000</param-value> </context-param>
正常に接続されると、ドキュメントが開いている限り Websocket はデフォルトで開いたままになり、接続が閉じられたり中断されたりすると、一定間隔で自動再接続されます。ネットワークエラーまたはサーバーの再起動。最初の接続試行がすでに失敗した場合、自動再接続は行われません。ドキュメントがアンロードされると、Websocket は暗黙的に閉じられます。
使用方法 (サーバー)
WAR 側では、
@Named
や@WebServlet
などの CDI/ コンテナー管理下のアーティファクトの中で、プッシュメッセージを送信したい場所の指定されたチャネル名に@
Push
アノテーションを介してPushContext
を注入し、プッシュメッセージを表す任意の Java オブジェクトでPushContext.send(Object)
を起動します。@Inject @Push private PushContext someChannel; public void sendMessage(Object message) { someChannel.send(message); }
デフォルトでは、チャネルの名前は、注入が行われる変数の名前から取得されます。チャネル名は、
channel
属性を介してオプションで指定できます。以下の例では、チャネル名foo
のプッシュコンテキストをbar
という名前の変数に挿入します。@Inject @Push(channel = "foo") private PushContext bar;
メッセージオブジェクトは JSON としてエンコードされ、
channel
名に関連付けられたonmessage
JavaScript リスナー関数のmessage
引数として配信されます。プレーンなバニラString
にすることもできますが、コレクション、マップ、さらには javabean にすることもできます。Websocket は双方向通信をサポートしますが、
<f:websocket>
プッシュはサーバーからクライアントへの一方向通信用に設計されています。クライアントからサーバーにデータを送信する場合は、通常どおり Jakarta Faces ajax を使用してください。これには、特に、Jakarta Faces ビューステート、HTTP セッション、重要なビジネスサービスメソッドのすべてのセキュリティ制約を維持できるという利点があります。スコープとユーザー
デフォルトでは、Websocket は
application
スコープです。つまり、同じ Websocket チャネルが開いている Web アプリケーション全体のビュー / セッションは、同じプッシュメッセージを受け取ります。プッシュメッセージは、すべてのユーザーとアプリケーション自体が送信できます。オプションの
scope
属性をsession
に設定して、プッシュメッセージを現在のユーザーセッションのすべてのビューのみに制限できます。プッシュメッセージは、ユーザー自身のみが送信でき、アプリケーションは送信できません。<f:websocket channel="someChannel" scope="session" ... />
scope
属性をview
に設定して、プッシュメッセージを現在のビューのみに制限することもできます。プッシュメッセージは、同じ URL であっても、同じセッションの他のビューには表示されません。プッシュメッセージは、ユーザー自身のみが送信でき、アプリケーションは送信できません。<f:websocket channel="someChannel" scope="view" ... />
scope
属性は Jakarta Expression Language 式ではない可能性があり、許可される値はapplication
、session
、view
であり、大文字と小文字は区別されません。さらに、オプションの
user
属性は、ログインしたユーザーの一意の識別子(通常はログイン名またはユーザー ID)に設定できます。このようにして、プッシュメッセージは特定のユーザーをターゲットにすることができ、他のユーザーやアプリケーション自体からも送信できます。user
属性の値は、少なくともSerializable
SE を実装し、メモリフットプリントが小さい必要があるため、ユーザーエンティティ全体を配置することはお勧めしません。E.g。コンテナー管理認証または関連するフレームワーク / ライブラリを使用している場合:
<f:websocket channel="someChannel" user="#{request.remoteUser}" ... />
または、Jakarta Expression Language に
#{someLoggedInUser}
としてカスタムユーザーエンティティがあり、その識別子を表すid
プロパティがある場合:<f:websocket channel="someChannel" user="#{someLoggedInUser.id}" ... />
user
属性が指定されている場合、scope
はデフォルトでsession
に設定され、application
に設定することはできません。サーバー側では、プッシュメッセージは
PushContext.send(Object, Serializable)
を介してuser
属性で指定されたユーザーをターゲットにすることができます。プッシュメッセージは、すべてのユーザーとアプリケーション自体が送信できます。@Inject @Push private PushContext someChannel; public void sendMessage(Object message, User recipientUser) { Long recipientUserId = recipientUser.getId(); someChannel.send(message, recipientUserId); }
ユーザー ID を保持する
Collection
SE をPushContext.send(Object, Collection)
に渡すことにより、複数のユーザーをターゲットにすることができます。public void sendMessage(Object message, Group recipientGroup) { Collection<Long> recipientUserIds = recipientGroup.getUserIds(); someChannel.send(message, recipientUserIds); }
条件付きで接続
オプションの
connected
属性を使用して、WebSocket を自動接続するかどうかを制御できます。<f:websocket ... connected="#{bean.pushable}" />
デフォルトは
true
で、内部では JavaScript 命令として解釈され、websocket プッシュ接続を開くか閉じるかを指定します。値が Jakarta Expression Language 式であり、ajax リクエスト中にfalse
になる場合、その ajax リクエストの oncomplete 中にプッシュ接続が明示的に閉じられます。また、明示的に
false
に設定し、faces.push.open(clientId)
を呼び出してコンポーネントのクライアント ID を渡すことにより、クライアント側でプッシュ接続を手動で開くこともできます。<h:commandButton ... onclick="faces.push.open('foo')"> <f:ajax ... /> </h:commandButton> <f:websocket id="foo" channel="bar" scope="view" ... connected="false" />
1 回限りのプッシュを予定していて、それ以上のメッセージを期待しない場合は、オプションで、
faces.push.close(clientId)
を呼び出し、コンポーネントのクライアント ID を渡すことにより、クライアント側からプッシュ接続を明示的に閉じることができます。例:onmessage
の JavaScript リスナー関数は次のとおりです。function someWebsocketListener(message) { // ... faces.push.close('foo'); }
イベント (クライアント)
オプションの
onopen
JavaScript リスナー関数を使用して、クライアント側で websocket のオープンをリッスンできます。これは、成功するかどうかに関係なく、最初の接続試行で呼び出されます。これは、Websocket が最初の接続成功後に切断された接続を自動再接続する場合には呼び出されません。<f:websocket ... onopen="websocketOpenListener" />
function websocketOpenListener(channel) { // ... }
onopen
JavaScript リスナー関数は、次の 1 つの引数で呼び出されます。channel
: チャネル名。グローバルリスナーを使用する場合に役立ちます。
オプションの
onerror
JavaScript リスナー関数を使用して、Websocket が再接続を試みる接続エラーをリッスンできます。これは、websocket が最初の接続成功後に切断された接続で自動再接続を試行できる場合に呼び出されます。これは、最初の接続試行が失敗した場合、またはサーバーがクローズ理由コード1000
(通常のクローズ) または1008
(ポリシー違反) を返した場合、または再接続試行の最大回数を超えた場合には呼び出されません。代わりに、onclose
が呼び出されます。<o:socket ... onerror="websocketErrorListener" />
function websocketErrorListener(code, channel, event) { if (code == 1001) { // Server has returned an unexpected response code. E.g. 503, because it's shutting down. } else if (code == 1006) { // Server is not reachable anymore. I.e. it's not anymore listening on TCP/IP requests. } else { // Any other reason which is usually not -1, 1000 or 1008, as the onclose will be invoked instead. } // In any case, the websocket will attempt to reconnect. This function will be invoked again. // Once the websocket gives up reconnecting, the onclose will finally be invoked. }
onerror
JavaScript リスナー関数は、次の 3 つの引数で呼び出されます。code
: 終了理由コード (整数)。すべての終了コードの詳細なリストについては、RFC6455 セクション 7.4.1 およびCloseReason.CloseCodes
API も参照してください。channel
: チャネル名。グローバルリスナーを使用する場合に役立ちます。event
: 生のCloseEvent
インスタンス。インスペクションする場合に役立ちます。
オプションの
onclose
JavaScript リスナー関数を使用して、websocket の (ab)normal close をリッスンできます。これは、最初の接続試行が失敗した場合、またはサーバーが終了理由コード1000
(通常の終了) または1008
(ポリシー違反) を返した場合、または再接続試行の最大回数を超えた場合に呼び出されます。これは、Websocket が最初の接続成功後に切断された接続で自動再接続を試行できる場合には呼び出されません。代わりに、onerror
が呼び出されます。<f:websocket ... onclose="websocketCloseListener" />
function websocketCloseListener(code, channel, event) { if (code == -1) { // websockets not supported by client. } else if (code == 1000) { // Normal close (as result of expired session or view). } else { // Abnormal close reason (as result of an error). } }
onclose
JavaScript リスナー関数は、次の 3 つの引数で呼び出されます。code
: 終了理由コード (整数)。これが-1
の場合、Websocket は単にクライアントによってサポートされていません。これが1000
の場合、セッションまたはビューの有効期限が切れたため、通常は閉じられています。これが1000
でない場合は、エラーが発生する可能性があります。すべての終了コードの詳細なリストについては、RFC6455 セクション 7.4.1 およびCloseReason.CloseCodes
API も参照してください。channel
: チャンネル名。event
: 生のCloseEvent
インスタンス。
セッションまたはビュースコープの Websocket がサーバーによって終了理由コード
1000
で自動的に閉じられた場合 (したがって、クライアントによってfaces.push.close(clientId)
を介して手動で閉じられたわけではありません)、セッションまたはビューが期限切れになったことを意味します。イベント (サーバー)
Websocket が開かれると、新しい CDI
WebsocketEvent
が@
WebsocketEvent.Opened
修飾子で起動されます。Websocket が閉じられると、新しい CDIWebsocketEvent
が@
WebsocketEvent.Closed
修飾子で起動されます。これらは、以下のようにアプリケーションスコープの CDI Bean でのみ観察および収集できます。@ApplicationScoped public class WebsocketObserver { public void onOpen(@Observes @Opened WebsocketEvent event) { String channel = event.getChannel(); // Returns <f:websocket channel>. Long userId = event.getUser(); // Returns <f:websocket user>, if any. // ... } public void onClose(@Observes @Closed WebsocketEvent event) { String channel = event.getChannel(); // Returns <f:websocket channel>. Long userId = event.getUser(); // Returns <f:websocket user>, if any. CloseCode code = event.getCloseCode(); // Returns close reason code. // ... } }
セキュリティに関する考慮事項
特定のロールを持つログインユーザーのみに制限されているページで websocket が宣言されている場合、プッシュハンドシェイクリクエスト URL の URL を制限付き URL のセットに追加することができます。
プッシュハンドシェイクリクエスト URL は、URI プレフィックス
/jakarta.faces.push/
とそれに続くチャネル名で構成されます。たとえば、コンテナー管理セキュリティの場合、サンプルページ/user/foo.xhtml
を、以下のようにweb.xml
のサンプル URL パターン/user/*
でサンプルロールUSER
を持つログインユーザーにすでに制限しています。<security-constraint> <web-resource-collection> <web-resource-name>Restrict access to role USER.</web-resource-name> <url-pattern>/user/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>USER</role-name> </auth-constraint> </security-constraint>
.. そしてページ
/user/foo.xhtml
に<f:websocket channel="foo">
が含まれている場合は、以下のように/jakarta.faces.push/foo
のプッシュハンドシェイクリクエスト URL パターンに制限を追加する必要があります。<security-constraint> <web-resource-collection> <web-resource-name>Restrict access to role USER.</web-resource-name> <url-pattern>/user/*</url-pattern> <url-pattern>/jakarta.faces.push/foo</url-pattern> </web-resource-collection> <auth-constraint> <role-name>USER</role-name> </auth-constraint> </security-constraint>
追加のセキュリティとして、特にセキュリティ制約によって制限できないパブリックチャネルの場合、
<f:websocket>
はこれまでに宣言されたすべてのチャネルを現在の HTTP セッションに登録し、受信する Websocket オープンリクエストは、これまでに登録されたものと一致するかどうかをチェックします。現在の HTTP セッションのチャネル。チャネルが不明な場合 (たとえば、エンドユーザーによってランダムに推測またはスプーフィングされた場合、またはセッションの有効期限が切れた後に手動で再接続された場合)、Websocket は終了理由コードCloseReason.CloseCodes.VIOLATED_POLICY
(1008
) ですぐに閉じられます。また、HTTP セッションが破棄されると、まだ開いているすべてのセッションスコープおよびビュースコープチャネルは、閉じる理由コードCloseReason.CloseCodes.NORMAL_CLOSURE
(1000
) でサーバー側から明示的に閉じられます。アプリケーションスコープの Websocket のみが開いたままになり、クライアント側のページに関連付けられたセッションまたはビューが期限切れになった場合でも、サーバーエンドから到達できます。Ajax サポート
受信したプッシュメッセージに応じて複雑な UI 更新を実行する場合は、
<f:websocket>
内に<f:ajax>
をネストできます。次に例を示します。<h:panelGroup id="foo"> ... (some complex UI here) ... </h:panelGroup> <h:form> <f:websocket channel="someChannel" scope="view"> <f:ajax event="someEvent" listener="#{bean.pushed}" render=":foo" /> </f:websocket> </h:form>
ここで、プッシュメッセージは単に ajax イベント名を表しています。任意のカスタムイベント名を使用できます。
someChannel.send("someEvent");
別の方法は、
<w:websocket>
を<h:commandScript>
と組み合わせることです。例:<h:panelGroup id="foo"> ... (some complex UI here) ... </h:panelGroup> <f:websocket channel="someChannel" scope="view" onmessage="someCommandScript" /> <h:form> <h:commandScript name="someCommandScript" action="#{bean.pushed}" render=":foo" /> </h:form>
Map<String,V>
または JavaBean をプッシュメッセージオブジェクトとして渡すと、すべてのエントリ / プロパティがコマンドスクリプトメソッド#{bean.pushed}
のリクエストパラメーターとして透過的に使用可能になります。- 導入:
- 2.3
- 関連事項:
PushContext
,UIWebsocket
,WebsocketEvent
要素の詳細
channel
StringSE channel
(オプション)プッシュチャネルの名前。指定しない場合、注入ターゲットフィールドの名前が使用されます。- 戻り値:
- プッシュチャネルの名前。
- デフォルト:
- ""