WebSockets Next リファレンスガイド
この技術は、previewと考えられています。 preview では、下位互換性やエコシステムでの存在は保証されていません。具体的な改善には設定や API の変更が必要になるかもしれませんが、 stable になるための計画は現在進行中です。フィードバックは メーリングリスト や GitHub の課題管理 で受け付けています。 とりうるステータスの完全なリストについては、 FAQの項目 を参照してください。 |
quarkus-websockets-next
エクステンションは、WebSocket サーバーおよびクライアントエンドポイントを定義するための最新の宣言型 API を提供します。
1. WebSocket プロトコル
RFC6455 に記載されている WebSocket プロトコルは、単一の TCP 接続を介してクライアントとサーバー間の双方向通信チャネルを作成するための標準化された方法を確立します。 HTTP とは異なり、WebSocket は別個の TCP プロトコルとして動作しますが、HTTP とシームレスに連携して機能するように設計されています。 たとえば、同じポートを再利用し、同じセキュリティーメカニズムと互換性があります。
WebSocket を使用したやり取りは、WebSocket プロトコルに移行するための 'Upgrade' ヘッダーを使用する HTTP リクエストで開始されます。
サーバーは、 200 OK
レスポンスの代わりに 101 Switching Protocols
レスポンスを返し、HTTP 接続を WebSocket 接続へとアップグレードします。
このハンドシェイクが成功すると、最初の HTTP アップグレードリクエストで使用された TCP ソケットは開いたままとなり、クライアントとサーバーの双方が継続的に双方向のメッセージをやり取りできるようになります。
2. HTTP および WebSocket アーキテクチャースタイル
WebSocket は HTTP と互換性があり、HTTP リクエストを通じて開始されますが、2 つのプロトコルは異なるアーキテクチャーとプログラミングモデルを導くため、その違いを認識することが重要です。
HTTP/REST では、アプリケーションはリソース/エンドポイントを中心に構成され、さまざまな HTTP メソッドやパスを処理します。 クライアントとのやり取りは、適切なメソッドとパスを指定した HTTP リクエストを送信することで行われ、リクエスト/レスポンスのパターンに従います サーバーは、パス、メソッド、ヘッダーに基づいて、受信したリクエストを対応するハンドラーにルーティングし、明確に定義されたレスポンスで返します。
逆に、WebSocket では通常、最初の HTTP 接続に単一のエンドポイントが使用され、その後、すべてのメッセージが同じ TCP 接続を利用します。 これにより、非同期かつメッセージ駆動型のまったく異なるインタラクションモデルが導入されます。
WebSocket は、HTTP とは対照的に、低レベルのトランスポートプロトコルです。 メッセージの形式、ルーティング、または処理には、メッセージのセマンティクスに関するクライアントとサーバー間の事前の合意が必要です。
WebSocket クライアントとサーバーの場合、HTTP ハンドシェイクリクエストの Sec-WebSocket-Protocol
ヘッダーにより、より高レベルのメッセージングプロトコルのネゴシエーションが可能になります。このヘッダーがない場合、サーバーとクライアントは独自の規則を確立する必要があります。
3. Quarkus WebSockets と Quarkus WebSockets Next
このガイドでは、従来の quarkus-websockets
エクステンションに比べ、効率性と使いやすさが向上した WebSocket API の実装である quarkus-websockets-next
エクステンションを利用します。
オリジナルの quarkus-websockets
エクステンションは引き続きアクセス可能で、継続的なサポートが提供されますが、機能開発が行われる可能性は低いです。
quarkus-websockets
とは異なり、 quarkus-websockets-next
エクステンションは Jakarta WebSocket 仕様を 実装していません。
代わりに、使いやすさを重視した最新の API を導入しています。
さらに、Quarkus のリアクティブアーキテクチャーおよびネットワーク層とシームレスに統合するように調整されています。
Quarkus WebSockets Next エクステンションで使用されるアノテーションは、同じ名前を共有する場合もありますが、JSR 356 のアノテーションとは異なります。 JSR アノテーションには、Quarkus WebSockets Next エクステンションが従わないセマンティクスが含まれています。
4. プロジェクトのセットアップ
websockets-next
エクステンションを使用するには、プロジェクトに io.quarkus:quarkus-websockets-next
依存関係を追加する必要があります。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next</artifactId>
</dependency>
implementation("io.quarkus:quarkus-websockets-next")
5. Endpoints
サーバー API and クライアント API は両方とも、メッセージを消費および送信するために使用される エンドポイント を定義します。
エンドポイントは CDI Bean として実装され、注入をサポートします。
エンドポイントは、 @OnTextMessage
、 @OnBinaryMessage
、 @OnPingMessage
、 @OnPongMessage
、 @OnOpen
、 @OnClose
および @OnError
アノテーションが付けられた callback methods を宣言します。
これらのメソッドは、さまざまな WebSocket イベントを処理するために使用されます。
通常、接続されたクライアントがサーバーにメッセージを送信するとき、およびサーバーが接続されたクライアントにメッセージを送信するときに、 @OnTextMessage
アノテーションが付けられたメソッドが呼び出されます。
クライアント API には、新しい WebSocket 接続を設定および作成するために使用される connectors も含まれています。 |
5.1. サーバーエンドポイント
サーバーエンドポイントは、 @io.quarkus.websockets.next.WebSocket
アノテーションが付けられたクラスです。
WebSocket#path()
の値は、エンドポイントのパスを定義するために使用されます。
package org.acme.websockets;
import io.quarkus.websockets.next.WebSocket;
import jakarta.inject.Inject;
@WebSocket(path = "/chat/{username}") (1)
public class ChatWebSocket {
}
したがって、クライアントは ws://localhost:8080/chat/your-name
を使用して、この Web ソケットエンドポイントに接続できます。
TLS が使用されている場合、URL は wss://localhost:8443/chat/your-name
です。
エンドポイントパスは、 quarkus.http.root-path (デフォルトでは / ) によって設定された root context に対して相対的です。たとえば、 application.properties に quarkus.http.root-path=/api を追加すると、クライアントは http://localhost:8080/api/chat/the-name を使用してこのエンドポイントに接続できます。
|
5.2. クライアントエンドポイント
クライアントエンドポイントは、 @io.quarkus.websockets.next.WebSocketClient
アノテーションが付けられたクラスです。
WebSocketClient#path()
の値は、このクライアントが接続されるエンドポイントのパスを定義するために使用されます。
package org.acme.websockets;
import io.quarkus.websockets.next.WebSocketClient;
import jakarta.inject.Inject;
@WebSocketClient(path = "/chat/{username}") (1)
public class ChatWebSocket {
}
クライアントエンドポイントは、メッセージの受信と送信に使用されます。新しい WebSocket 接続を設定して開くには、connectors API が必要です。 |
5.3. パスパラメーター
WebSocket エンドポイントのパスには、パスパラメーターを含めることができます。
構文は JAX-RS リソースの場合と同じです: {parameterName}
パスパラメーター値には、それぞれ io.quarkus.websockets.next.WebSocketConnection#pathParam(String)
メソッドまたは io.quarkus.websockets.next.WebSocketClientConnection#pathParam(String)
を使用してアクセスできます。
あるいは、 @io.quarkus.websockets.next.PathParam
アノテーションが付与されたエンドポイントコールバックメソッドパラメーターが自動的に注入されます。
WebSocketConnection#pathParam(String)
の例@Inject io.quarkus.websockets.next.WebSocketConnection connection;
// ...
String value = connection.pathParam("parameterName");
パスパラメーターの値は常に文字列です。
パス内にパスパラメーターが存在しない場合、 WebSocketConnection#pathParam(String)
/WebSocketClientConnection#pathParam(String)
メソッドは null
を返します。
@PathParam
アノテーションが付けられたエンドポイントコールバックメソッドパラメーターがあり、パラメーター名がエンドポイントパスで定義されていない場合、ビルドは失敗します。
クエリーパラメーターはサポートされていません。ただし、 WebSocketConnection#handshakeRequest().query() を使用してクエリーにアクセスできます。
|
5.4. CDI スコープ
エンドポイントは CDI Bean として管理されます。
デフォルトでは、 @Singleton
スコープが使用されます。
ただし、開発者は特定の要件に合わせて代替スコープを指定できます。
@Singleton
および @ApplicationScoped
エンドポイントは、すべての WebSocket 接続間で共有されます。
したがって、実装はステートレスまたはスレッドセーフのいずれかである必要があります。
5.4.1. セッションコンテキスト
エンドポイントに @SessionScoped
アノテーションが付与されている場合、またはエンドポイントが直接的または間接的に @SessionScoped
Bean に依存している場合、各 WebSocket 接続は独自の セッションコンテキスト に関連付けられます。
セッションコンテキストは、エンドポイントコールバックの呼び出し中にアクティブになります。
同じ接続内で後続の コールバックメソッド が呼び出される場合、同じセッションコンテキストが利用されます。
セッションコンテキストは、接続が閉じられるまで (通常は @OnClose
メソッドの実行が完了したとき) アクティブなままになり、接続が閉じられた時点で終了します。
quarkus.websockets-next.server.activate-session-context 設定プロパティーを always に設定することもできます。この場合、 @SessionScoped Bean が依存関係ツリーに参加しているかどうかに関係なく、セッションコンテキストは常にアクティブ化されます。
|
@SessionScoped
エンドポイントimport jakarta.enterprise.context.SessionScoped;
@WebSocket(path = "/ws")
@SessionScoped (1)
public class MyWebSocket {
}
1 | このサーバーエンドポイントは共有されず、セッション/接続に限定されます。 |
5.4.2. リクエストコンテキスト
エンドポイントが以下の場合、各 WebSocket エンドポイントコールバックメソッドの実行は、新しい CDI リクエストコンテキスト に関連付けられます。
-
@RequestScoped
アノテーションが付けられています。 -
@RolesAllowed
などのセキュリティーアノテーションが付けられたメソッドがあります。 -
@RequestScoped
Bean に直接的または間接的に依存します。 -
標準のセキュリティーアノテーションで保護された CDI Bean に直接的または間接的に依存します。
quarkus.websockets-next.server.activate-request-context 設定プロパティーを always に設定することもできます。この場合、エンドポイントコールバックが呼び出されると、リクエストコンテキストが常にアクティブ化されます。
|
@RequestScoped
エンドポイントimport jakarta.enterprise.context.RequestScoped;
@WebSocket(path = "/ws")
@RequestScoped (1)
public class MyWebSocket {
}
1 | このサーバーエンドポイントは、コールバックメソッドの実行ごとにインスタンス化されます。 |
5.5. コールバックメソッド
WebSocket エンドポイントは以下を宣言できます。
-
最大 1 つの
@OnTextMessage
メソッド: 接続されたクライアント/サーバーからのテキストメッセージを処理します。 -
最大 1 つの
@OnBinaryMessage
メソッド: 接続されたクライアント/サーバーからのバイナリーメッセージを処理します。 -
最大 1 つの
@OnPingMessage
メソッド: 接続されたクライアント/サーバーからの ping メッセージを処理します。 -
最大 1 つの
@OnPongMessage
メソッド: 接続されたクライアント/サーバーからの pong メッセージを処理します。 -
最大 1 つの
@OnOpen
メソッド: 接続が開かれたときに呼び出されます。 -
最大 1 つの
@OnClose
メソッド: 接続が閉じられたときに実行されます。 -
任意の数の
@OnError
メソッド: エラーが発生したときに呼び出されます。つまり、エンドポイントコールバックがランタイムエラーをスローしたとき、変換エラーが発生したとき、または返されたio.smallrye.mutiny.Uni
/io.smallrye.mutiny.Multi
が失敗を受け取ったときです。
一部のエンドポイントのみにすべてのメソッドを含める必要があります。
ただし、少なくとも @On[Text|Binary]Message
または @OnOpen
が含まれている必要があります。
いずれかのエンドポイントがこれらのルールに違反すると、ビルド時にエラーがスローされます。 sub-websocket を表す静的なネストされたクラスは同じガイドラインに従います。
WebSocket エンドポイントの外部で @OnTextMessage 、 @OnBinaryMessage 、 @OnOpen 、および @OnClose アノテーションが付与されたメソッドはエラーとみなされ、適切なエラーメッセージが表示されてビルドが失敗します。
|
5.6. メッセージの処理
クライアントからメッセージを受信するメソッドには、 @OnTextMessage
または @OnBinaryMessage
アノテーションが付けられます。
OnTextMessage
は、クライアントから受信されるすべての テキスト メッセージに対して呼び出されます。
OnBinaryMessage
は、クライアントが受信するすべての バイナリー メッセージに対して呼び出されます。
5.6.1. 呼び出しルール
コールバックメソッドを呼び出すと、WebSocket 接続にリンクされた セッション スコープはアクティブなままになります。 さらに、リクエストスコープはメソッドが完了するまで (または非同期メソッドとリアクティブメソッドの場合は結果が生成されるまで) アクティブです。
WebSocket Next は、Quarkus REST に似た、メソッドの戻り値のタイプと @Blocking
や @NonBlocking
などの追加のアノテーションで決定される、ブロッキング と ノンブロッキング ロジックをサポートします。
実行に関するルールは次のとおりです。
-
@RunOnVirtualThread
、@Blocking
、または@Transactional
アノテーションが付与されたメソッドは、ブロッキングとみなされます。 -
@RunOnVirtualThread
アノテーションが付けられたクラスで宣言されたメソッドは、ブロッキングとみなされます。 -
@NonBlocking
アノテーションが付けられたメソッドは、ノンブロッキングとみなされます。 -
@Transactional
アノテーションが付けられたクラスで宣言されたメソッドは、@NonBlocking
アノテーションが付けられていない限り、ブロッキングとみなされます。 -
メソッドが上記のアノテーションのいずれも宣言していない場合、実行モデルは戻り値の型から派生します。
-
Uni
およびMulti
を返すメソッドは、ノンブロッキングとみなされます。 -
void
またはその他のタイプを返すメソッドは、ブロッキングとみなされます。
-
-
Kotlin の
suspend
関数は常にノンブロッキングとみなされるため、@Blocking
、@NonBlocking
、@RunOnVirtualThread
アノテーションのいずれかを付与することはできません。 また、@RunOnVirtualThread
アノテーションが付けられたクラス内に含めることもできません。 -
ノンブロッキングメソッドは、接続のイベントループスレッドで実行する必要があります。
-
ブロッキングメソッドは、
@RunOnVirtualThread
アノテーションが付けられている場合、または、@RunOnVirtualThread
アノテーションがクラスに付けられている場合を除き、 ワーカースレッド上で実行される必要があります。 -
@RunOnVirtualThread
アノテーションが付与されたメソッド、または@RunOnVirtualThread
アノテーションが付与されたクラスで宣言されたメソッドは、 仮想スレッド上で実行される必要があります。仮想スレッドは、呼び出しのたび新しく生成されます。
5.6.2. メソッドパラメーター
メソッドは、メッセージパラメーターを 1 つのみ受け入れる必要があります。
-
メッセージオブジェクト (任意のタイプ)。
-
メッセージタイプが X の
Multi<X>
。
ただし、次のパラメーターも受け入れる場合があります。
-
WebSocketConnection
/WebSocketClientConnection
-
HandshakeRequest
-
@PathParam
アノテーションが付けられたString
パラメーター
メッセージオブジェクトは送信されたデータを表し、raw のコンテンツ (String
、 JsonObject
、 JsonArray
、 Buffer
、または byte[]
) またはデシリアライズされた高レベルオブジェクトとしてアクセスできます。後者のアプローチが推奨されます。
Multi
を受信すると、メソッドは接続ごとに 1 回呼び出され、提供された Multi
はこの接続によって送信された項目を受信します。
メソッドが Multi
(受信したものから構築) を返す場合、Quarkus は自動的にそれをサブスクライブし、完了、失敗、またはキャンセルされるまで、発行された項目を書き込みます。
ただし、メソッドが Multi
を返さない場合は、データを使用するために受信した Multi
をサブスクライブする必要があります。
2 つの例があります。
// No need to subscribe to the incoming Multi as the method returns a Multi derived from the incoming one
@OnTextMessage
public Multi<ChatMessage> stream(Multi<ChatMessage> incoming) {
return incoming.log();
}
// ...
// Must subscribe to the incoming Multi as the method does not return a Multi, otherwise no data will be consumed
@OnTextMessage
public void stream(Multi<ChatMessage> incoming) {
incoming.subscribe().with(item -> log(item));
}
受信した Multi
のサブスクライブに関する詳細は、Uni
または Multi
をサブスクライブするタイミング を参照してください。
5.6.3. サポートされている戻り値のタイプ
@OnTextMessage
または @OnBinaryMessage
アノテーションが付与されたメソッドは、WebSocket 通信を効率的に処理するためにさまざまなタイプを返すことができます。
-
void
: 明示的なレスポンスがクライアントに返されないブロッキングメソッドを示します。 -
Uni<Void>
: 返されたUni
の完了が処理の終了を意味するノンブロッキングメソッドを示します。明示的なレスポンスはクライアントに返されません。 -
X
タイプのオブジェクトは、返されたオブジェクトがシリアライズされ、レスポンスとしてクライアントに送り返されるブロッキングメソッドを表します。 -
Uni<X>
: null 以外のUni
によって発行された項目がレスポンスとしてクライアントに送信されるノンブロッキングメソッドを指定します。 -
Multi<X>
: null 以外のMulti
によって発行された項目が完了またはキャンセルされるまでクライアントに順番に送信されるノンブロッキングメソッドを示します。 -
Unit
を返す Kotlin のsuspend
関数: 明示的なレスポンスがクライアントに返されないノンブロッキングメソッドを示します。 -
X
を返す Kotlin のsuspend
関数: 返された項目がレスポンスとしてクライアントに送信されるノンブロッキングメソッドを指定します。
これらの方法の例をいくつか示します。
@OnTextMessage
void consume(Message m) {
// Process the incoming message. The method is called on an executor thread for each incoming message.
}
@OnTextMessage
Uni<Void> consumeAsync(Message m) {
// Process the incoming message. The method is called on an event loop thread for each incoming message.
// The method completes when the returned Uni emits its item.
}
@OnTextMessage
ResponseMessage process(Message m) {
// Process the incoming message and send a response to the client.
// The method is called for each incoming message.
// Note that if the method returns `null`, no response will be sent to the client.
}
@OnTextMessage
Uni<ResponseMessage> processAsync(Message m) {
// Process the incoming message and send a response to the client.
// The method is called for each incoming message.
// Note that if the method returns `null`, no response will be sent to the client. The method completes when the returned Uni emits its item.
}
@OnTextMessage
Multi<ResponseMessage> stream(Message m) {
// Process the incoming message and send multiple responses to the client.
// The method is called for each incoming message.
// The method completes when the returned Multi emits its completion signal.
// The method cannot return `null` (but an empty multi if no response must be sent)
}
Uni
および Multi
を返すメソッドは、ノンブロッキングとみなされます。
さらに、Quarkus は返された Multi
/Uni
を自動的にサブスクライブし、完了、失敗、またはキャンセルされるまで、発行された項目を書き込みます。
失敗またはキャンセルにより接続が終了します。
5.6.4. ストリーム
WebSocket エンドポイントは、個々のメッセージに加えて、メッセージのストリームも処理できます。
この場合、メソッドは Multi<X>
をパラメーターとして受け取ります。
X
の各インスタンスは、上記と同じルールを使用してデシリアライズされます。
Multi
を受け取るメソッドは、別の Multi
または void
を返すことができます。
メソッドが Multi
を返す場合、受信する multi
をサブスクライブする必要はありません。
@OnTextMessage
public Multi<ChatMessage> stream(Multi<ChatMessage> incoming) {
return incoming.log();
}
このアプローチにより、双方向のストリーミングが可能になります。
メソッドが void
を返し、 Multi
を返さない場合、コードは受信する Multi
をサブスクライブする必要があります。
そうしないと、データが消費されず、接続が閉じられません。
@OnTextMessage
public void stream(Multi<ChatMessage> incoming) {
incoming.subscribe().with(item -> log(item));
}
また、 stream
メソッドは Multi
が完了する前に完了することに注意してください。
受信した Multi
のサブスクライブに関する詳細は、Uni
または Multi
をサブスクライブするタイミング を参照してください。
5.6.5. 返信をスキップする
メソッドがクライアントに書き込まれるメッセージを生成することを意図している場合、 null
を発行できます。
null
を発行すると、クライアントにレスポンスが送信されないことを意味し、必要なときにレスポンスをスキップできるようになります。
5.6.6. JsonObject および JsonArray
Vert.x の JsonObject
および JsonArray
インスタンスは、シリアライゼーションおよびデシリアライゼーションのメカニズムをバイパスします。
メッセージはテキストメッセージとして送信されます。
5.6.7. OnOpen および OnClose メソッド
クライアントが接続または切断したときに、WebSocket エンドポイントに通知することもできます。
これは、メソッドに @OnOpen
または @OnClose
アノテーションを付けることで行われます。
@OnOpen(broadcast = true)
public ChatMessage onOpen() {
return new ChatMessage(MessageType.USER_JOINED, connection.pathParam("username"), null);
}
@Inject WebSocketConnection connection;
@OnClose
public void onClose() {
ChatMessage departure = new ChatMessage(MessageType.USER_LEFT, connection.pathParam("username"), null);
connection.broadcast().sendTextAndAwait(departure);
}
@OnOpen
はクライアント接続時にトリガーされ、 @OnClose
は切断時に呼び出されます。
これらのメソッドは、セッションスコープ の WebSocketConnection
Bean にアクセスできます。
5.6.8. パラメーター
@OnOpen
および @OnClose
アノテーションが付けられたメソッドは、次のパラメーターを受け入れることができます。
-
WebSocketConnection
/WebSocketClientConnection
-
HandshakeRequest
-
@PathParam
アノテーションが付けられたString
パラメーター
@OnClose
アノテーションが付与されたエンドポイントメソッドは、接続を閉じる理由を示す io.quarkus.websockets.next.CloseReason
パラメーターも受け入れる場合があります。
5.6.9. サポートされている戻り値のタイプ
@OnOpen
メソッドと @OnClose
メソッドは、異なる戻り値のタイプをサポートします。
@OnOpen
メソッドには、 @On[Text|Binary]Message
と同じルールが適用されます。
したがって、 @OnOpen
アノテーションが付与されたメソッドは、接続後すぐにクライアントにメッセージを送信できます。
@OnOpen
メソッドでサポートされている戻り値のタイプは次のとおりです。
-
void
: 接続されたクライアントに明示的なメッセージが返されないブロッキングメソッドを示します。 -
Uni<Void>
: 返されたUni
の完了が処理の終了を意味するノンブロッキングメソッドを示します。クライアントにメッセージは返されません。 -
X
タイプのオブジェクト: 返されたオブジェクトがシリアライズされてクライアントに送り返されるブロッキングメソッドを表します。 -
Uni<X>
: null 以外のUni
によって発行された項目がクライアントに送信されるノンブロッキングメソッドを指定します。 -
Multi<X>
: null 以外のMulti
によって発行された項目が完了またはキャンセルされるまでクライアントに順番に送信されるノンブロッキングメソッドを示します。 -
Unit
を返す Kotlin のsuspend
関数: 明示的なメッセージがクライアントに返されないノンブロッキングメソッドを示します。 -
X
を返す Kotlin のsuspend
関数: 返された項目がクライアントに送信されるノンブロッキングメソッドを指定します。
クライアントに送信される項目は、 String
、 io.vertx.core.json.JsonObject
、 io.vertx.core.json.JsonArray
、 io.vertx.core.buffer.Buffer
、および byte[]
タイプを除いて、シリアライズ されます。
Multi
の場合、Quarkus は返された Multi
をサブスクライブし、項目が発行されると WebSocket
に書き込みます。
String
、 JsonObject
、 JsonArray
はテキストメッセージとして送信されます。
Buffers
とバイト配列はバイナリーメッセージとして送信されます。
@OnClose
メソッドの場合、サポートされる戻り値のタイプは次のとおりです。
-
void
: メソッドはブロッキングであるとみなされます。 -
Uni<Void>
: メソッドはノンブロッキングであるとみなされます。 -
Unit
を返す Kotlin のsuspend
関数: このメソッドはノンブロッキングとみなされます。
サーバーエンドポイントで宣言された @OnClose メソッドは、オブジェクトを返すことによって接続されたクライアントに項目を送信できません。
WebSocketConnection オブジェクトを使用してのみ、他のクライアントにメッセージを送信できます。
|
5.7. エラー処理
エラーが発生したときに WebSocket エンドポイントに通知することもできます。
@io.quarkus.websockets.next.OnError
アノテーションが付与された WebSocket エンドポイントメソッドは、エンドポイントコールバックがランタイムエラーをスローしたとき、または変換エラーが発生したとき、
あるいは返された io.smallrye.mutiny.Uni
/io.smallrye.mutiny.Multi
が失敗した場合に呼び出されます。
メソッドは、error パラメーター、つまり java.lang.Throwable
から割り当て可能なパラメーターを 1 つだけ受け入れる必要があります。
このメソッドは次のパラメーターも受け入れます。
-
WebSocketConnection
/WebSocketClientConnection
-
HandshakeRequest
-
@PathParam
アノテーションが付けられたString
パラメーター
エンドポイントは、 @io.quarkus.websockets.next.OnError
アノテーションが付与された複数のメソッドを宣言できます。
ただし、各メソッドは異なるエラーパラメーターを宣言する必要があります。
実際の例外の最も具体的なスーパータイプを宣言するメソッドが選択されます。
@io.quarkus.websockets.next.OnError アノテーションは、グローバルエラーハンドラー、つまり WebSocket エンドポイントで宣言されていないメソッドを宣言するためにも使用できます。このようなメソッドは、 @PathParam パラメーターを受け入れない場合があります。エンドポイントで宣言されたエラーハンドラーは、グローバルエラーハンドラーよりも優先されます。
|
エラーが発生しても、エラーハンドラーが失敗を処理できない場合、Quarkus は quarkus.websockets-next.server.unhandled-failure-strategy
で指定されたストラテジーを使用します。
サーバーエンドポイントの場合、エラーメッセージがログに記録され、接続はデフォルトで閉じられます。
クライアントエンドポイントの場合、エラーメッセージはデフォルトでログに記録されます。
5.8. シリアライズとデシリアライズ
WebSocket Next エクステンションは、メッセージの自動シリアライゼーションとデシリアライゼーションをサポートします。
String
、 JsonObject
、 JsonArray
、 Buffer
、および byte[]
タイプのオブジェクトはそのまま送信され、シリアライゼーションとデシリアライゼーションをバイパスします。
コーデックが指定されていない場合、シリアライゼーションとデシリアライゼーションによってメッセージは JSON から、または JSON に自動的に変換されます。
シリアライゼーションとデシリアライゼーションをカスタマイズする必要がある場合は、カスタムコーデックを提供できます。
5.8.1. カスタムコーデック
カスタムコーデックを実装するには、以下を実装する CDI Bean を提供する必要があります。
-
バイナリーメッセージ用の
io.quarkus.websockets.next.BinaryMessageCodec
-
テキストメッセージの
io.quarkus.websockets.next.TextMessageCodec
次の例は、 Item
クラスのカスタムコーデックを実装する方法を示しています。
@Singleton
public class ItemBinaryMessageCodec implements BinaryMessageCodec<Item> {
@Override
public boolean supports(Type type) {
// Allows selecting the right codec for the right type
return type.equals(Item.class);
}
@Override
public Buffer encode(Item value) {
// Serialization
return Buffer.buffer(value.toString());
}
@Override
public Item decode(Type type, Buffer value) {
// Deserialization
return new Item(value.toString());
}
}
OnTextMessage
メソッドと OnBinaryMessage
メソッドでは、どのコーデックを使用するかを明示的に指定することもできます。
@OnTextMessage(codec = MyInputCodec.class) (1)
Item find(Item item) {
//....
}
-
メッセージのデシリアライゼーションとシリアライゼーションの両方に使用するコーデックを指定します。
シリアライゼーションとデシリアライゼーションで異なるコーデックを使用する必要がある場合は、シリアライゼーションとデシリアライゼーションに使用するコーデックを個別に指定できます。
@OnTextMessage(
codec = MyInputCodec.class, (1)
outputCodec = MyOutputCodec.class (2)
Item find(Item item) {
//....
}
-
受信メッセージのデシリアライズに使用するコーデックを指定します
-
送信メッセージのシリアライゼーションに使用するコーデックを指定します。
5.9. Ping/Pong メッセージ
ping メッセージ は、キープアライブとして、またはリモートエンドポイントを確認するために使用できます。 pong メッセージ は、ping メッセージへのレスポンスとして送信され、同一のペイロードを持つ必要があります。
5.9.1. ping メッセージの送信
ping メッセージはオプションであり、デフォルトでは送信されません。ただし、サーバーおよびクライアントのエンドポイントは、一定の間隔で ping メッセージを自動的に送信するように設定できます。
quarkus.websockets-next.server.auto-ping-interval=2 (1)
quarkus.websockets-next.client.auto-ping-interval=10 (2)
1 | サーバーから接続された各クライアントに 2 秒ごとに ping メッセージを送信します。 |
2 | 接続されているすべてのクライアントインスタンスからリモートサーバーに 10 秒ごとに ping メッセージを送信します。 |
サーバーとクライアントは、 WebSocketConnection
または WebSocketClientConnection
を使用して、いつでもプログラムで ping メッセージを送信できます。
ノンブロッキングバリアント (Sender#sendPing(Buffer)
) とブロッキングバリアント (Sender#sendPingAndAwait(Buffer)
) があります。
5.9.2. pong メッセージの送信
サーバーおよびクライアントのエンドポイントは、ping メッセージのアプリケーションデータを使用して、リモート側から送信された ping メッセージに対し、常に対応する pong メッセージでレスポンスします。 この動作は組み込まれており、追加のコードや設定は必要ありません。
サーバーとクライアントは、 WebSocketConnection
または WebSocketClientConnection
を使用して、一方向のハートビートとして機能する可能性のある非要求 pong メッセージを送信できます。ノンブロッキングバリアント (Sender#sendPong(Buffer)
) とブロッキングバリアント (Sender#sendPongAndAwait(Buffer)
) があります。
5.9.3. ping/pong メッセージの処理
ping メッセージは自動的に処理され、pong メッセージはレスポンスを必要としないため、WebSocket プロトコルに準拠するためにこれらのメッセージのハンドラーを記述する必要はありません。 ただし、エンドポイントで ping または pong メッセージがいつ受信されたかを知ることが役立つ場合があります。
@OnPingMessage
および @OnPongMessage
アノテーションを使用して、リモート側から送信された ping または pong メッセージを使用するコールバックを定義できます。
エンドポイントは、最大 1 つの @OnPingMessage
コールバックと最大 1 つの @OnPongMessage
コールバックを宣言できます。
コールバックメソッドは、 void
または Uni<Void>
を返す必要 (または Unit
を返す Kotlin の suspend
関数である必要) があり、 Buffer
タイプの単一のパラメーターを受け入れる必要があります。
@OnPingMessage
void ping(Buffer data) {
// an incoming ping that will automatically receive a pong
}
@OnPongMessage
void pong(Buffer data) {
// an incoming pong in response to the last ping sent
}
5.10. 受信処理モード
WebSocket エンドポイントは、それぞれ @WebSocket#inboundProcessingMode()
と @WebSocketClient.inboundProcessingMode()
を使用して、特定の接続の受信イベントを処理するために使用されるモードを定義できます。
受信イベントは、メッセージ (テキスト、バイナリー、pong)、接続の開始、接続の終了を表すことができます。
デフォルトでは、イベントは順番に処理され、順序が保証されます。
つまり、エンドポイントがイベント A
と B
を (この特定の順序で) 受信した場合、イベント A
のコールバックが完了した後にイベント B
のコールバックが呼び出されます。
ただし、状況によっては、順序の保証はなく、同時実行の制限もない状態でイベントを同時に処理することが望ましい場合があります。
このような場合には、 InboundProcessingMode#CONCURRENT
を使用する必要があります。
6. サーバー API
6.1. HTTP サーバーの設定
このエクステンションは、メイン の HTTP サーバーを再利用します。
したがって、WebSocket サーバーの設定は quarkus.http.
設定セクションで行われます。
アプリケーション内で設定された WebSocket パスは、 quarkus.http.root
(デフォルトは /
) で定義されたルートパスと連結されます。
この連結により、WebSocket エンドポイントがアプリケーションの URL 構造内に適切に配置されるようになります。
詳細は、HTTP ガイド を参照してください。
6.2. sub-websocket エンドポイント
@WebSocket
エンドポイントは、静的なネストされたクラスをカプセル化できます。これらのクラスも @WebSocket
アノテーションが付与され、sub-websocket を表します。
これらの sub-websocket のパスは、外側のクラスとネストされたクラスからのパスを連結したものになります。
この結果得られるパスは、HTTP URL の規則に従って正規化されます。
sub-websocket は、外側のクラスとネストされたクラスの両方の @WebSocket
アノテーションで宣言されたパスパラメーターへのアクセスを継承します。
次の例では、外側のクラス内の consumePrimary
メソッドは version
パラメーターにアクセスできます。
一方、ネストされたクラス内の consumeNested
メソッドは、 version
パラメーターと id
パラメーターの両方にアクセスできます。
@WebSocket(path = "/ws/v{version}")
public class MyPrimaryWebSocket {
@OnTextMessage
void consumePrimary(String s) { ... }
@WebSocket(path = "/products/{id}")
public static class MyNestedWebSocket {
@OnTextMessage
void consumeNested(String s) { ... }
}
}
6.3. WebSocket 接続
io.quarkus.websockets.next.WebSocketConnection
オブジェクトは WebSocket 接続を表します。
Quarkus は、このインターフェイスを実装し、 WebSocket
エンドポイントに注入して、接続されたクライアントと対話するために使用できる @SessionScoped
CDI Bean を提供します。
@OnOpen
、 @OnTextMessage
、 @OnBinaryMessage
、および @OnClose
アノテーションが付与されたメソッドは、注入された WebSocketConnection
オブジェクトにアクセスできます。
@Inject WebSocketConnection connection;
これらのメソッド以外では、 WebSocketConnection オブジェクトは利用できないことに注意してください。ただし、開いているすべての接続をリスト表示 できます.。
|
接続を使用して、クライアントにメッセージを送信したり、パスパラメーターにアクセスしたり、接続されているすべてのクライアントにメッセージをブロードキャストしたりできます。
// Send a message:
connection.sendTextAndAwait("Hello!");
// Broadcast messages:
connection.broadcast().sendTextAndAwait(departure);
// Access path parameters:
String param = connection.pathParam("foo");
WebSocketConnection
は、メッセージを送信するためのブロッキングメソッドとノンブロッキングメソッドの両方のバリアントを提供します。
-
sendTextAndAwait(String message)
: テキストメッセージをクライアントに送信し、メッセージが送信されるのを待ちます。これはブロッキングであり、エグゼキュータースレッドからのみ呼び出す必要があります。 -
sendText(String message)
: テキストメッセージをクライアントに送信します。Uni
を返します。これはノンブロッキングです。メッセージを送信するには、返されたUni
をユーザーまたは Quarkus がサブスクライブしていることを確認してください。 Quarkus によって呼び出されたメソッド (Quarkus REST、Quarkus WebSocket Next、Quarkus Messaging など) からUni
を返すと、それをサブスクライブしてメッセージを送信します。 以下に例を示します。
@POST
public Uni<Void> send() {
return connection.sendText("Hello!"); // Quarkus automatically subscribes to the returned Uni and sends the message.
}
Uni
への登録に関する詳細は、Uni
または Multi
をサブスクライブするタイミング を参照してください。
6.3.1. 開いている接続をリスト表示する
開いているすべての接続をリスト表示することもできます。
Quarkus は、接続にアクセスするための便利なメソッドを宣言する io.quarkus.websockets.next.OpenConnections
タイプの CDI Bean を提供します。
import io.quarkus.logging.Log;
import io.quarkus.websockets.next.OpenConnections;
class MyBean {
@Inject
OpenConnections connections;
void logAllOpenConnections() {
Log.infof("Open connections: %s", connections.listAll()); (1)
}
}
1 | OpenConnections#listAll() は、指定された時点で開いているすべての接続のイミュータブルなスナップショットを返します。 |
他にも便利な方法があります。
たとえば、 OpenConnections#findByEndpointId(String)
を使用すると、特定のエンドポイントの接続を簡単に見つけることができます。
6.3.2. ユーザーデータ
任意のユーザーデータを特定の接続に関連付けることも可能です。
WebSocketConnection#userData()
メソッドによって取得される io.quarkus.websockets.next.UserData
オブジェクトは、接続に関連付けられたミュータブルなユーザーデータを表します。
import io.quarkus.websockets.next.WebSocketConnection;
import io.quarkus.websockets.next.UserData.TypedKey;
@WebSocket(path = "/endpoint/{username}")
class MyEndpoint {
@Inject
CoolService service;
@OnOpen
void open(WebSocketConnection connection) {
connection.userData().put(TypedKey.forBoolean("isCool"), service.isCool(connection.pathParam("username"))); (1)
}
@OnTextMessage
String process(String message) {
if (connection.userData().get(TypedKey.forBoolean("isCool"))) { (2)
return "Cool message processed!";
} else {
return "Message processed!";
}
}
}
1 | CoolService#isCool() は、現在の接続に関連付けられている Boolean を返します。 |
2 | TypedKey.forBoolean("isCool") は、接続の作成時に保存されたデータを取得するために使用されるキーです。 |
6.3.3. CDI イベント
Quarkus は、新しい接続が開かれると、修飾子 @io.quarkus.websockets.next.Open
を持つ io.quarkus.websockets.next.WebSocketConnection
タイプの CDI イベントを非同期的に起動します。
さらに、接続が閉じられると、修飾子 @io.quarkus.websockets.next.Closed
を持つ WebSocketConnection
タイプの CDI イベントが非同期的に起動します。
import jakarta.enterprise.event.ObservesAsync;
import io.quarkus.websockets.next.Open;
import io.quarkus.websockets.next.WebSocketConnection;
class MyBean {
void connectionOpened(@ObservesAsync @Open WebSocketConnection connection) { (1)
// This observer method is called when a connection is opened...
}
}
1 | 非同期オブザーバーメソッドは、デフォルトのブロッキングエグゼキューターサービスを使用して実行されます。 |
6.4. セキュリティー
セキュリティー機能は、Quarkus Security エクステンションによって提供されます。
任意の アイデンティティープロバイダー を使用して、最初の HTTP リクエストの認証情報を SecurityIdentity
インスタンスに変換できます。
次に、 SecurityIdentity
が Websocket 接続に関連付けられます。
認可オプションについては、次のセクションで説明します。
OpenID Connect エクステンション (quarkus-oidc ) が使用され、トークンの有効期限が切れると、Quarkus は自動的に接続を閉じます。
|
6.4.1. セキュア HTTP アップグレード
標準のセキュリティーアノテーションがエンドポイントクラスに配置されるか、HTTP セキュリティーポリシーが定義されている場合、HTTP アップグレードは保護されます。 HTTP アップグレードをセキュリティーで保護する利点は、処理が少なくなり、認可が早期に 1 回だけ実行されることです。 エラー時にアクションを実行する必要がある場合 (セキュアな WebSocket エンドポイントコールバックメソッド 参照) や、ペイロードに基づいてセキュリティチェックを行う必要がある場合 (権限チェッカーでサーバーのエンドポイントを保護する を参照) を除き、常に HTTP アップグレードセキュリティーを優先する必要があります。
package io.quarkus.websockets.next.test.security;
import io.quarkus.security.Authenticated;
import jakarta.inject.Inject;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
@Authenticated (1)
@WebSocket(path = "/end")
public class Endpoint {
@Inject
SecurityIdentity currentIdentity;
@OnOpen
String open() {
return "ready";
}
@OnTextMessage
String echo(String message) {
return message;
}
}
1 | 匿名ユーザーの場合、最初の HTTP ハンドシェイクは 401 ステータスで終了します。
quarkus.websockets-next.server.security.auth-failure-redirect-url 設定プロパティーを使用して、認可失敗時にハンドシェイクリクエストをリダイレクトすることもできます。 |
HTTP アップグレードは、エンドポイントクラスで @WebSocket アノテーションの隣でセキュリティーアノテーションが宣言されている場合にのみ保護されます。
エンドポイント Bean にセキュリティーアノテーションを配置しても、Bean メソッドは保護されず、HTTP アップグレードのみが保護されます。
エンドポイントが意図したとおりに保護されていることを常に確認する必要があります。
|
quarkus.http.auth.permission.http-upgrade.paths=/end
quarkus.http.auth.permission.http-upgrade.policy=authenticated
6.4.2. セキュアな WebSocket エンドポイントコールバックメソッド
WebSocket エンドポイントのコールバックメソッドは、 io.quarkus.security.Authenticated
や
jakarta.annotation.security.RolesAllowed
などのセキュリティーアノテーション、および サポート対象セキュリティーアノテーション ドキュメントに記載されているその他のアノテーションを使用して保護できます。
例:
package io.quarkus.websockets.next.test.security;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import io.quarkus.security.ForbiddenException;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.websockets.next.OnError;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
@WebSocket(path = "/end")
public class Endpoint {
@Inject
SecurityIdentity currentIdentity;
@OnOpen
String open() {
return "ready";
}
@RolesAllowed("admin")
@OnTextMessage
String echo(String message) { (1)
return message;
}
@OnError
String error(ForbiddenException t) { (2)
return "forbidden:" + currentIdentity.getPrincipal().getName();
}
}
1 | エコーコールバックメソッドは、現在のセキュリティーアイデンティティーに admin ロールがある場合にのみ呼び出すことができます。 |
2 | 認可に失敗した場合はエラーハンドラーが呼び出されます。 |
6.4.3. 権限チェッカーでサーバーのエンドポイントを保護する
WebSocket エンドポイントは、パーミッションチェッカー を使用して保護できます。 個々のエンドポイントメソッドではなく、セキュア HTTP アップグレード を推奨します。以下に例を示します。
package io.quarkus.websockets.next.test.security;
import io.quarkus.security.PermissionsAllowed;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
@PermissionsAllowed("product:premium")
@WebSocket(path = "/product/premium")
public class PremiumProductEndpoint {
@OnTextMessage
PremiumProduct getPremiumProduct(int productId) {
return new PremiumProduct(productId);
}
}
package io.quarkus.websockets.next.test.security;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.PermissionChecker;
import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils;
import io.vertx.ext.web.RoutingContext;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class PermissionChecker {
@PermissionChecker("product:premium")
public boolean canGetPremiumProduct(SecurityIdentity securityIdentity) { (1)
String username = currentIdentity.getPrincipal().getName();
RoutingContext routingContext = HttpSecurityUtils.getRoutingContextAttribute(securityIdentity);
String initialHttpUpgradePath = routingContext == null ? null : routingContext.normalizedPath();
if (!isUserAllowedToAccessPath(initialHttpUpgradePath, username)) {
return false;
}
return isPremiumCustomer(username);
}
}
1 | HTTP アップグレードを承認する権限チェッカーは、メソッドパラメーター SecurityIdentity を 1 つだけ宣言する必要があります。 |
すべてのメッセージに対してセキュリティーチェックを実行することもできます。たとえば、メッセージペイロードには次のようにアクセスできます。
package io.quarkus.websockets.next.test.security;
import io.quarkus.security.PermissionChecker;
import io.quarkus.security.PermissionsAllowed;
import jakarta.inject.Inject;
import io.quarkus.security.ForbiddenException;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.websockets.next.OnError;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
@WebSocket(path = "/product")
public class ProductEndpoint {
private record Product(int id, String name) {}
@Inject
SecurityIdentity currentIdentity;
@PermissionsAllowed("product:get")
@OnTextMessage
Product getProduct(int productId) { (1)
return new Product(productId, "Product " + productId);
}
@OnError
String error(ForbiddenException t) { (2)
return "forbidden:" + currentIdentity.getPrincipal().getName();
}
@PermissionChecker("product:get")
boolean canGetProduct(int productId) {
String username = currentIdentity.getPrincipal().getName();
return currentIdentity.hasRole("admin") || canUserGetProduct(productId, username);
}
}
1 | getProduct コールバックメソッドは、現在のセキュリティーアイデンティティーに admin ロールがある場合、またはユーザーが製品の詳細を取得することを許可されている場合にのみ、呼び出すことができます。 |
2 | 認可に失敗した場合はエラーハンドラーが呼び出されます。 |
6.4.4. ベアラートークン認証
OIDC ベアラートークン認証 では、最初の HTTP ハンドシェイク中にベアラートークンが Authorization
ヘッダーで渡されることを期待します。
WebSockets Next Client および Vert.x WebSocketClient などの Java WebSocket クライアントは、WebSocket オープニングハンドシェイクにカスタムヘッダーを追加することをサポートしています。
ただし、 WebSockets API に準拠する JavaScript クライアントは、カスタムヘッダーの追加をサポートしていません。
したがって、JavaScript ベースの WebSocket クライアントでは、カスタム Authorization
ヘッダーを使用してベアラーアクセストークンを渡すことはできません。
JavaScript WebSocket クライアントでは、サブプロトコルをネゴシエートするための HTTP Sec-WebSocket-Protocol リクエストヘッダーのみを設定できます。
絶対に必要な場合は、 WebSockets API の制限を回避するために、 Sec-WebSocket-Protocol
ヘッダーをカスタムヘッダーのキャリアーとして使用できます。
以下は、 Authorization
ヘッダーをサブプロトコル値として伝播する JavaScript クライアントの例です。
const token = getBearerToken()
const quarkusHeaderProtocol = encodeURIComponent("quarkus-http-upgrade#Authorization#Bearer " + token) (1)
const socket = new WebSocket("wss://" + location.host + "/chat/" + username, ["bearer-token-carrier", quarkusHeaderProtocol]) (2)
1 | Quarkus Header サブプロトコルの想定される形式は quarkus-http-upgrade#header-name#header-value です。
エンコードの問題を回避するために、サブプロトコル値を URI コンポーネントとしてエンコードすることを忘れないでください。 |
2 | クライアントでサポートされている 2 つのサブプロトコル (選択したサブプロトコルと Quarkus HTTP アップグレードサブプロトコル) を指定します。 |
WebSocket サーバーがサブプロトコルとして渡された Authorization
を受け入れるには、次の操作を行う必要があります。
-
サポートされているサブプロトコルを使用して、WebSocket サーバーを設定します。WebSocket クライアントが HTTP
Sec-WebSocket-Protocol
リクエストヘッダーでサポートされているサブプロトコルのリストを提供する場合、WebSocket サーバーはそれらのいずれかを使用してコンテンツを提供することに同意する必要があります。 -
開いている WebSocket ハンドシェイクリクエストヘッダーへの Quarkus HTTP アップグレードサブプロトコルマッピングを有効にします。
quarkus.websockets-next.server.supported-subprotocols=bearer-token-carrier
quarkus.websockets-next.server.propagate-subprotocol-headers=true
WebSocket セキュリティーモデルはオリジンベースであり、ヘッダーまたは Cookie を使用したクライアント側認証用に設計されていません。 たとえば、Web ブラウザーは、WebSocket ハンドシェイクリクエストの開始時に、同一オリジンポリシーを強制しません。 WebSocket ハンドシェイクリクエストの開始時に、ベアラーアクセストークンを使用する予定の場合は、セキュリティーリスクを最小限に抑えるために、以下に示す追加のセキュリティー対策に従うことを強く推奨します。
|
6.5. HTTP アップグレードを検査および/または拒否する
HTTP アップグレードを検査するには、 io.quarkus.websockets.next.HttpUpgradeCheck
インターフェイスを実装する CDI Bean を提供する必要があります。
Quarkus は、WebSocket 接続にアップグレードする必要があるすべての HTTP リクエストに対して HttpUpgradeCheck#perform
メソッドを呼び出します。
このメソッド内では、任意のビジネスロジックを実行したり、HTTP アップグレードを拒否したりできます。
package io.quarkus.websockets.next.test;
import io.quarkus.websockets.next.HttpUpgradeCheck;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped (1)
public class ExampleHttpUpgradeCheck implements HttpUpgradeCheck {
@Override
public Uni<CheckResult> perform(HttpUpgradeContext ctx) {
if (rejectUpgrade(ctx)) {
return CheckResult.rejectUpgrade(400); (2)
}
return CheckResult.permitUpgrade();
}
private boolean rejectUpgrade(HttpUpgradeContext ctx) {
var headers = ctx.httpRequest().headers();
// implement your business logic in here
}
}
1 | HttpUpgradeCheck インターフェイスを実装する CDI Bean は、 @ApplicationScoped 、 @Singleton 、または @Dependent Bean のいずれかになりますが、 @RequestScoped Bean になることはできません。 |
2 | HTTP アップグレードを拒否します。最初の HTTP ハンドシェイクは、400 Bad Request レスポンスステータスコードで終了します。 |
HttpUpgradeCheck#appliesTo メソッドを使用して、 HttpUpgradeCheck を適用する WebSocket エンドポイントを選択できます。
|
6.6. TLS
このエクステンションは、メイン の HTTP サーバーを再利用するという事実の直接的な結果として、関連するすべてのサーバー設定が適用されます。詳細は、HTTP ガイド を参照してください。
6.7. Hibernate マルチテナンシー
HTTP アップグレード後、 RoutingContext
は使用できません。ただし、 WebSocketConnection
を注入して、最初の HTTP リクエストのヘッダーにアクセスすることは可能です。
カスタムの TenantResolver
を使用し、REST/HTTP と WebSocket を組み合わせる場合、コードは次のようになります。
@RequestScoped
@PersistenceUnitExtension
public class CustomTenantResolver implements TenantResolver {
@Inject
RoutingContext context;
@Inject
WebSocketConnection connection;
@Override
public String getDefaultTenantId() {
return "public";
}
@Override
public String resolveTenantId() {
String schema;
try {
//Handle WebSocket
schema = connection.handshakeRequest().header("schema");
} catch ( ContextNotActiveException e) {
// Handle REST/HTTP
schema = context.request().getHeader( "schema" );
}
if ( schema == null || schema.equalsIgnoreCase( "public" ) ) {
return "public";
}
return schema;
}
}
Hibernate マルチテナンシーの詳細は、 hibernate ドキュメント を参照してください。
7. クライアント API
7.1. クライアントコネクター
コネクターを使用すると、メッセージの消費と送信に使用されるクライアントエンドポイントによってサポートされる新しいクライアント接続を設定して開くことができます。
Quarkus は、Bean タイプ io.quarkus.websockets.next.WebSocketConnector<CLIENT>
の CDI Bean および他の Bean に注入できるデフォルトの修飾子を提供します。
注入ポイントの実際のタイプ引数は、クライアントエンドポイントを決定するために使用されます。
このタイプはビルド時に検証され、クライアントエンドポイントを表していない場合、ビルドは失敗します。
次のクライアントエンドポイントを考えてみましょう。
@WebSocketClient(path = "/endpoint/{name}")
public class ClientEndpoint {
@OnTextMessage
void onMessage(@PathParam String name, String message, WebSocketClientConnection connection) {
// ...
}
}
このクライアントエンドポイントのコネクターは次のように使用されます。
@Singleton
public class MyBean {
@ConfigProperty(name = "endpoint.uri")
URI myUri;
@Inject
WebSocketConnector<ClientEndpoint> connector; (1)
void openAndSendMessage() {
WebSocketClientConnection connection = connector
.baseUri(uri) (2)
.pathParam("name", "Roxanne") (3)
.connectAndAwait();
connection.sendTextAndAwait("Hi!"); (4)
}
}
1 | ClientEndpoint のコネクターを注入します。 |
2 | ベース URI が指定されていない場合は、設定から値を取得しようとします。キーは、クライアント ID と .base-uri 接尾辞で構成されます。 |
3 | パスパラメーター値を設定します。クライアントエンドポイントパスに指定された名前のパラメーターが含まれていない場合は、 IllegalArgumentException がスローされます。 |
4 | 必要に応じて、接続を使用してメッセージを送信します。 |
アプリケーションが、存在しないエンドポイントのコネクターを注入しようとすると、エラーがスローされます。 |
コネクターはスレッドセーフではないため、同時に使用しないでください。
また、コネクターの再利用もしないでください。
連続して複数の接続を作成する必要がある場合は、 Instance#get()
を使用してプログラムで新しいコネクターインスタンスを取得する必要があります。
import jakarta.enterprise.inject.Instance;
@Singleton
public class MyBean {
@Inject
Instance<WebSocketConnector<MyEndpoint>> connector;
void connect() {
var connection1 = connector.get().baseUri(uri)
.addHeader("Foo", "alpha")
.connectAndAwait();
var connection2 = connector.get().baseUri(uri)
.addHeader("Foo", "bravo")
.connectAndAwait();
}
}
7.1.1. 基本コネクター
アプリケーション開発者がクライアントエンドポイントとコネクターの組み合わせを必要としない場合は、基本コネクター を使用できます。 基本コネクターは、クライアントエンドポイントを定義せずに接続を作成し、メッセージを消費/送信する簡単な方法です。
@Singleton
public class MyBean {
@Inject
BasicWebSocketConnector connector; (1)
void openAndConsume() {
WebSocketClientConnection connection = connector
.baseUri(uri) (2)
.path("/ws") (3)
.executionModel(ExecutionModel.NON_BLOCKING) (4)
.onTextMessage((c, m) -> { (5)
// ...
})
.connectAndAwait();
}
}
1 | コネクターを注入します。 |
2 | ベース URI は常に設定する必要があります。 |
3 | ベース URI に追加する必要がある追加パス。 |
4 | コールバックハンドラーの実行モデルを設定します。デフォルトでは、コールバックは現在のスレッドをブロックする可能性があります。ただし、この場合、コールバックはイベントループで実行され、現在のスレッドをブロックしない可能性があります。 |
5 | lambda は、サーバーから送信されるテキストメッセージごとに呼び出されます。 |
基本コネクターは低レベル API に近いため、上級ユーザー向けに予約されています。 ただし、他の低レベルの WebSocket クライアントとは異なり、これは引き続き CDI Bean であり、他の Bean に注入できます。 また、コールバックの実行モデルを設定する方法も提供し、Quarkus の他の部分との最適なインテグレーションを確保します。
コネクターはスレッドセーフではないため、同時に使用しないでください。
また、コネクターの再利用もしないでください。
連続して複数の接続を作成する必要がある場合は、 Instance#get()
を使用してプログラムで新しいコネクターインスタンスを取得する必要があります。
import jakarta.enterprise.inject.Instance;
@Singleton
public class MyBean {
@Inject
Instance<BasicWebSocketConnector> connector;
void connect() {
var connection1 = connector.get().baseUri(uri)
.addHeader("Foo", "alpha")
.connectAndAwait();
var connection2 = connector.get().baseUri(uri)
.addHeader("Foo", "bravo")
.connectAndAwait();
}
}
7.2. WebSocket クライアント接続
io.quarkus.websockets.next.WebSocketClientConnection
オブジェクトは WebSocket 接続を表します。
Quarkus は、このインターフェイスを実装し、 WebSocketClient
エンドポイントに注入して、接続されたサーバーと対話するために使用できる @SessionScoped
CDI Bean を提供します。
@OnOpen
、 @OnTextMessage
、 @OnBinaryMessage
、および @OnClose
アノテーションが付与されたメソッドは、注入された WebSocketClientConnection
オブジェクトにアクセスできます。
@Inject WebSocketClientConnection connection;
これらのメソッド以外では、 WebSocketClientConnection オブジェクトは利用できないことに注意してください。ただし、開いているすべてのクライアント接続をリスト表示 することは可能です。
|
この接続を使用して、クライアントにメッセージを送信したり、パスパラメーターにアクセスしたりできます。
// Send a message:
connection.sendTextAndAwait("Hello!");
// Broadcast messages:
connection.broadcast().sendTextAndAwait(departure);
// Access path parameters:
String param = connection.pathParam("foo");
WebSocketClientConnection
は、メッセージを送信するためのブロッキングメソッドとノンブロッキングメソッドの両方のバリアントを提供します。
-
sendTextAndAwait(String message)
: テキストメッセージをクライアントに送信し、メッセージが送信されるのを待ちます。これはブロッキングであり、エグゼキュータースレッドからのみ呼び出す必要があります。 -
sendText(String message)
: テキストメッセージをクライアントに送信します。Uni
を返します。これはノンブロッキングです。メッセージを送信するには、返されたUni
をユーザーまたは Quarkus がサブスクライブしていることを確認してください。 Quarkus によって呼び出されたメソッド (Quarkus REST、Quarkus WebSocket Next、Quarkus Messaging など) からUni
を返すと、それをサブスクライブしてメッセージを送信します。 以下に例を示します。
@POST
public Uni<Void> send() {
return connection.sendText("Hello!"); // Quarkus automatically subscribes to the returned Uni and sends the message.
}
7.2.1. 開いているクライアント接続をリスト表示する
開いているすべての接続をリスト表示することもできます。
Quarkus は、接続にアクセスするための便利なメソッドを宣言する io.quarkus.websockets.next.OpenClientConnections
タイプの CDI Bean を提供します。
import io.quarkus.logging.Log;
import io.quarkus.websockets.next.OpenClientConnections;
class MyBean {
@Inject
OpenClientConnections connections;
void logAllOpenClinetConnections() {
Log.infof("Open client connections: %s", connections.listAll()); (1)
}
}
1 | OpenClientConnections#listAll() は、指定された時点で開いているすべての接続のイミュータブルなスナップショットを返します。 |
他にも便利な方法があります。
たとえば、 OpenClientConnections#findByClientId(String)
を使用すると、特定のエンドポイントの接続を簡単に見つけることができます。
7.2.2. ユーザーデータ
任意のユーザーデータを特定の接続に関連付けることも可能です。
WebSocketClientConnection#userData()
メソッドによって取得される io.quarkus.websockets.next.UserData
オブジェクトは、接続に関連付けられたミュータブルなユーザーデータを表します。
import io.quarkus.websockets.next.WebSocketClientConnection;
import io.quarkus.websockets.next.UserData.TypedKey;
@WebSocketClient(path = "/endpoint/{username}")
class MyEndpoint {
@Inject
CoolService service;
@OnOpen
void open(WebSocketClientConnection connection) {
connection.userData().put(TypedKey.forBoolean("isCool"), service.isCool(connection.pathParam("username"))); (1)
}
@OnTextMessage
String process(String message) {
if (connection.userData().get(TypedKey.forBoolean("isCool"))) { (2)
return "Cool message processed!";
} else {
return "Message processed!";
}
}
}
1 | CoolService#isCool() は、現在の接続に関連付けられている Boolean を返します。 |
2 | TypedKey.forBoolean("isCool") は、接続の作成時に保存されたデータを取得するために使用されるキーです。 |
7.2.3. CDI イベント
Quarkus は、新しい接続が開かれると、修飾子 @io.quarkus.websockets.next.Open
を持つ io.quarkus.websockets.next.WebSocketConnection
タイプの CDI イベントを非同期的に起動します。
さらに、接続が閉じられると、修飾子 @io.quarkus.websockets.next.Closed
を持つ WebSocketClientConnection
タイプの CDI イベントが非同期的に起動します。
import jakarta.enterprise.event.ObservesAsync;
import io.quarkus.websockets.next.Open;
import io.quarkus.websockets.next.WebSocketClientConnection;
class MyBean {
void connectionOpened(@ObservesAsync @Open WebSocketClientConnection connection) { (1)
// This observer method is called when a connection is opened...
}
}
1 | 非同期オブザーバーメソッドは、デフォルトのブロッキングエグゼキューターサービスを使用して実行されます。 |
7.3. SSL/TLS の設定
TLS 接続を確立するには、TLS レジストリー を使用して、名前付き の設定を設定する必要があります。
quarkus.tls.my-ws-client.trust-store.p12.path=server-truststore.p12
quarkus.tls.my-ws-client.trust-store.p12.password=secret
quarkus.websockets-next.client.tls-configuration-name=my-ws-client # Reference the named configuration
WebSocket クライアントを使用する場合は、他の TLS 設定との競合を避けるために、名前付き 設定を使用する必要があります。 クライアントはデフォルトの TLS 設定を使用しません。 |
名前付き TLS 設定を行うと、TLS はデフォルトで有効になります。
8. トラフィックロギング
Quarkus は、デバッグの目的で送受信されたメッセージをログに記録できます。
サーバーのトラフィックロギングを有効にするには、 quarkus.websockets-next.server.traffic-logging.enabled
設定プロパティーを true
に設定します。
クライアントのトラフィックロギングを有効にするには、 quarkus.websockets-next.client.traffic-logging.enabled
設定プロパティーを true
に設定します。
テキストメッセージのペイロードも記録されます。
ただし、記録される文字数には制限があります。
デフォルトの制限は 100 ですが、 quarkus.websockets-next.server.traffic-logging.text-payload-limit
および quarkus.websockets-next.client.traffic-logging.text-payload-limit
設定プロパティーをそれぞれ使用してこの制限を変更できます。
メッセージは、ロガー io.quarkus.websockets.next.traffic に対して DEBUG レベルが有効になっている場合にのみ記録されます。
|
quarkus.websockets-next.server.traffic-logging.enabled=true (1)
quarkus.websockets-next.server.traffic-logging.text-payload-limit=50 (2)
quarkus.log.category."io.quarkus.websockets.next.traffic".level=DEBUG (3)
1 | トラフィックロギングを有効にします。 |
2 | ログに記録されるテキストメッセージペイロードの文字数を設定します。 |
3 | ロガー io.quarkus.websockets.next.traffic に対して DEBUG レベルを有効にします。 |
9. Uni
または Multi
をサブスクライブするタイミング
Uni
と Multi
は遅延タイプです。つまり、サブスクライブされるまで処理を開始しません。
Uni
または Multi
を (パラメーターまたは呼び出したメソッドから) 取得する場合、それをサブスクライブするかどうかはコンテキストによって異なります。
-
Quarkus によって呼び出されるメソッド (Quarkus REST、Quarkus WebSocket Next、Quarkus Messaging など) で
Uni
またはMulti
を返すと、Quarkus はそれをサブスクライブし、Multi
によって発行された項目またはUni
によって発行された項目を処理します。
@Incoming("...")
@Outgoing("...")
public Multi<String> process(Multi<String> input) {
// No need to subscribe to the input Multi, the `process` method is called by Quarkus (Messaging).
return input.map(String::toUpperCase);
}
@OnOpen
、 @OnTextMessage
、 @OnBinaryMessage
、または @OnClose
アノテーションが付与されたメソッドから Uni
または Multi
が返されると、Quarkus はそれを自動的にサブスクライブします。
-
Quarkus によって呼び出されるメソッドで
Uni
またはMulti
を返さない場合は、それをサブスクライブする必要があります。
@Incoming("...")
@Outgoing("...")
public void process(Multi<String> input) {
input.map(String::toUpperCase)
.subscribe().with(s -> log(s));
}
10. テレメトリー
OpenTelemetry エクステンションが存在する場合、開かれた WebSocket 接続と閉じられた WebSocket 接続のトレースがデフォルトで収集されます。 WebSocket トレースが必要ない場合は、次の例のようにトレースの収集を無効化できます。
quarkus.websockets-next.server.traces.enabled=false
quarkus.websockets-next.client.traces.enabled=false
Micrometer エクステンションが存在する場合、Quarkus はメッセージ、エラー、転送されたバイトのメトリクスを収集できます。 WebSocket メトリクスが必要な場合は、次の例のようにメトリクスを有効化できます。
quarkus.websockets-next.server.metrics.enabled=true
quarkus.websockets-next.client.metrics.enabled=true
BasicWebSocketConnector のテレメトリーは現在サポートされていません。
|
11. 設定リファレンス
ビルド時に固定された設定プロパティー。その他の設定プロパティーは、すべて実行時にオーバーライド可能です。
Configuration property |
タイプ |
デフォルト |
---|---|---|
Specifies the activation strategy for the CDI request context during endpoint callback invocation. By default, the request context is only activated if needed, i.e. if there is a bean with the given scope, or a bean annotated with a security annotation (such as Environment variable: Show more |
|
|
Specifies the activation strategy for the CDI session context during endpoint callback invocation. By default, the session context is only activated if needed, i.e. if there is a bean with the given scope in the dependency tree of the endpoint. Environment variable: Show more |
|
|
If enabled, the WebSocket opening handshake headers are enhanced with the 'Sec-WebSocket-Protocol' sub-protocol that match format 'quarkus-http-upgrade#header-name#header-value'. If the WebSocket client interface does not support setting headers to the WebSocket opening handshake, this is a way how to set authorization header required to authenticate user. The 'quarkus-http-upgrade' sub-protocol is removed and server selects from the sub-protocol one that is supported (don’t forget to configure the 'quarkus.websockets-next.server.supported-subprotocols' property). IMPORTANT: We strongly recommend to only enable this feature if the HTTP connection is encrypted via TLS, CORS origin check is enabled and custom WebSocket ticket system is in place. Please see the Quarkus WebSockets Next reference for more information. Environment variable: Show more |
ブーリアン |
|
Environment variable: Show more |
list of string |
|
Compression Extensions for WebSocket are supported by default. See also RFC 7692 Environment variable: Show more |
ブーリアン |
|
The compression level must be a value between 0 and 9. The default value is Environment variable: Show more |
int |
|
The maximum size of a message in bytes. The default values is Environment variable: Show more |
int |
|
The maximum size of a frame in bytes. The default values is Environment variable: Show more |
int |
|
The interval after which, when set, the server sends a ping message to a connected client automatically. Ping messages are not sent automatically by default. Environment variable: Show more |
||
The strategy used when an error occurs but no error handler can handle the failure. By default, the error message is logged and the connection is closed when an unhandled failure occurs. Environment variable: Show more |
|
|
Quarkus redirects HTTP handshake request to this URL if an HTTP upgrade is rejected due to the authorization failure. This configuration property takes effect when you secure endpoint with a standard security annotation. For example, the HTTP upgrade is secured if an endpoint class is annotated with the Environment variable: Show more |
string |
|
The limit of messages kept for a Dev UI connection. If less than zero then no messages are stored and sent to the Dev UI view. Environment variable: Show more |
長 |
|
If set to true then binary/text messages received/sent are logged if the Environment variable: Show more |
ブーリアン |
|
The number of characters of a text message which will be logged if traffic logging is enabled. The payload of a binary message is never logged. Environment variable: Show more |
int |
|
If collection of WebSocket traces is enabled. Only applicable when the OpenTelemetry extension is present. Environment variable: Show more |
ブーリアン |
|
If collection of WebSocket metrics is enabled. Only applicable when the Micrometer extension is present. Environment variable: Show more |
ブーリアン |
|
Compression Extensions for WebSocket are supported by default. See also RFC 7692 Environment variable: Show more |
ブーリアン |
|
The compression level must be a value between 0 and 9. The default value is Environment variable: Show more |
int |
|
The maximum size of a message in bytes. The default values is Environment variable: Show more |
int |
|
The maximum size of a frame in bytes. The default values is Environment variable: Show more |
int |
|
The interval after which, when set, the client sends a ping message to a connected server automatically. Ping messages are not sent automatically by default. Environment variable: Show more |
||
The strategy used when an error occurs but no error handler can handle the failure. By default, the error message is logged when an unhandled failure occurs. Note that clients should not close the WebSocket connection arbitrarily. See also RFC-6455 section 7.3. Environment variable: Show more |
|
|
The name of the TLS configuration to use. If a name is configured, it uses the configuration from The default TLS configuration is not used by default. Environment variable: Show more |
string |
|
If set to true then binary/text messages received/sent are logged if the Environment variable: Show more |
ブーリアン |
|
The number of characters of a text message which will be logged if traffic logging is enabled. The payload of a binary message is never logged. Environment variable: Show more |
int |
|
If collection of WebSocket traces is enabled. Only applicable when the OpenTelemetry extension is present. Environment variable: Show more |
ブーリアン |
|
If collection of WebSocket metrics is enabled. Only applicable when the Micrometer extension is present. Environment variable: Show more |
ブーリアン |
|
期間フォーマットについて
期間の値を書くには、標準の 数字で始まる簡略化した書式を使うこともできます:
その他の場合は、簡略化されたフォーマットが解析のために
|