STOMPを使用したWebSocket実装のためのSpringBootの使用
公開: 2022-03-11WebSocketプロトコルは、アプリケーションにリアルタイムメッセージを処理させる方法の1つです。 最も一般的な代替手段は、長いポーリングとサーバー送信イベントです。 これらのソリューションにはそれぞれ長所と短所があります。 この記事では、SpringBootFrameworkを使用してWebSocketを実装する方法を紹介します。 サーバー側とクライアント側の両方のセットアップについて説明し、STOMPoverWebSocketプロトコルを使用して相互に通信します。
サーバー側は純粋にJavaでコーディングされます。 ただし、クライアントの場合、通常、WebSocketクライアントはフロントエンドアプリケーションに埋め込まれているため、JavaとJavaScript(SockJS)の両方で記述されたスニペットを示します。 コード例は、pub-subモデルを使用して複数のユーザーにメッセージをブロードキャストする方法と、単一のユーザーにのみメッセージを送信する方法を示しています。 記事のさらなる部分では、WebSocketの保護と、環境がWebSocketプロトコルをサポートしていない場合でもWebSocketベースのソリューションが動作し続けることを保証する方法について簡単に説明します。
WebSocketの保護に関するトピックは、別の記事としては十分に複雑なトピックであるため、ここでは簡単に触れられるだけであることに注意してください。 これと、本番環境のWebSocketで触れている他のいくつかの要因が原因ですか? 最後のセクションでは、このセットアップを本番環境で使用する前に変更を加えることをお勧めします。セキュリティ対策を講じた本番環境に対応したセットアップについては、最後までお読みください。
WebSocketおよびSTOMPプロトコル
WebSocketプロトコルを使用すると、アプリケーション間の双方向通信を実装できます。 HTTPは最初のハンドシェイクにのみ使用されることを知っておくことが重要です。 それが起こった後、HTTP接続はWebSocketによって使用される新しく開かれたTCP/IP接続にアップグレードされます。
WebSocketプロトコルは、かなり低レベルのプロトコルです。 これは、バイトのストリームがフレームに変換される方法を定義します。 フレームには、テキストまたはバイナリメッセージを含めることができます。 メッセージ自体は、メッセージのルーティングまたは処理方法に関する追加情報を提供しないため、追加のコードを記述せずに、より複雑なアプリケーションを実装することは困難です。 幸い、WebSocket仕様では、より高いアプリケーションレベルで動作するサブプロトコルを使用できます。 Spring Frameworkでサポートされているものの1つは、STOMPです。
STOMPは、Ruby、Python、Perlなどのスクリプト言語がエンタープライズメッセージブローカーに接続するために最初に作成された、単純なテキストベースのメッセージングプロトコルです。 STOMPのおかげで、異なる言語で開発されたクライアントとブローカーは、相互にメッセージを送受信できます。 WebSocketプロトコルは、TCPforWebと呼ばれることもあります。 同様に、STOMPはHTTPforWebと呼ばれます。 これは、WebSocketフレームにマップされる少数のフレームタイプを定義します。たとえば、 CONNECT
、 SUBSCRIBE
、 UNSUBSCRIBE
、 ACK
、またはSEND
です。 これらのコマンドは、通信を管理するのに非常に便利ですが、メッセージ確認などのより高度な機能を備えたソリューションを実装することもできます。
サーバー側:SpringBootとWebSocket
WebSocketサーバー側を構築するために、JavaでのスタンドアロンおよびWebアプリケーションの開発を大幅に高速化するSpringBootフレームワークを利用します。 Spring Bootには、Java WebSocket API標準(JSR-356)と互換性のあるspring-WebSocket
モジュールが含まれています。
Spring Bootを使用したWebSocketサーバー側の実装は、それほど複雑なタスクではなく、いくつかの手順のみが含まれています。これらの手順を1つずつ説明します。
ステップ1.最初に、WebSocketライブラリの依存関係を追加する必要があります。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
送信されるメッセージにJSON形式を使用する場合は、GSONまたはJacksonの依存関係も含めることができます。 おそらく、SpringSecurityなどのセキュリティフレームワークがさらに必要になる場合があります。
ステップ2.次に、WebSocketおよびSTOMPメッセージングを有効にするようにSpringを構成できます。
Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/mywebsockets") .setAllowedOrigins("mydomain.com").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry config){ config.enableSimpleBroker("/topic/", "/queue/"); config.setApplicationDestinationPrefixes("/app"); } }
configureMessageBroker
メソッドは2つのことを行います。
- メッセージを送受信するための1つ以上の宛先を持つメモリ内メッセージブローカーを作成します。 上記の例では、
topic
とqueue
の2つの宛先プレフィックスが定義されています。 これらは、pub-subモデルを介してサブスクライブされたすべてのクライアントに送信されるメッセージの宛先の前にtopic
を付ける必要があるという規則に従います。 一方、プライベートメッセージの宛先には、通常、queue
のプレフィックスが付いています。 - コントローラに実装する
@MessageMapping
アノテーションが付けられたメソッドによって処理される宛先をフィルタリングするために使用されるプレフィックスapp
を定義します。 コントローラは、メッセージを処理した後、メッセージをブローカーに送信します。
上記のスニペットに戻ると(おそらくwithSockJS()
を使用したメソッドの呼び出しに気付いたでしょう)、SockJSフォールバックオプションが有効になります。 簡潔にするために、WebSocketプロトコルがインターネットブラウザーでサポートされていない場合でも、WebSocketを機能させることができます。 このトピックについては、もう少し詳しく説明します。
明確にする必要があるもう1つのことがあります。それは、エンドポイントでsetAllowedOrigins()
メソッドを呼び出す理由です。 WebSocketとSockJSのデフォルトの動作は、同一生成元のリクエストのみを受け入れることであるため、これが必要になることがよくあります。 したがって、クライアントとサーバー側が異なるドメインを使用している場合、それらの間の通信を可能にするためにこのメソッドを呼び出す必要があります。
ステップ3 。 ユーザー要求を処理するコントローラーを実装します。 特定のトピックにサブスクライブしているすべてのユーザーに受信メッセージをブロードキャストします。
これは、宛先/topic/news
にメッセージを送信するサンプルメソッドです。
@MessageMapping("/news") @SendTo("/topic/news") public void broadcastNews(@Payload String message) { return message; }
アノテーション@SendTo
の代わりに、コントローラー内で自動配線できるSimpMessagingTemplate
を使用することもできます。
@MessageMapping("/news") public void broadcastNews(@Payload String message) { this.simpMessagingTemplate.convertAndSend("/topic/news", message) }
後の手順で、Spring SecurityフレームワークのResourceServerConfigurerAdapter
やWebSecurityConfigurerAdapter
など、エンドポイントを保護するためのクラスを追加することをお勧めします。 また、送信されたJSONをオブジェクトにマッピングできるように、メッセージモデルを実装すると便利なことがよくあります。

WebSocketクライアントの構築
クライアントの実装はさらに簡単な作業です。
手順1.SpringSTOMPクライアントを自動配線します。
@Autowired private WebSocketStompClient stompClient;
手順2.接続を開きます。
StompSessionHandler sessionHandler = new CustmStompSessionHandler(); StompSession stompSession = stompClient.connect(loggerServerQueueUrl, sessionHandler).get();
これが完了すると、宛先にメッセージを送信することが可能になります。 メッセージは、トピックにサブスクライブしているすべてのユーザーに送信されます。
stompSession.send("topic/greetings", "Hello new user");
メッセージを購読することも可能です。
session.subscribe("topic/greetings", this); @Override public void handleFrame(StompHeaders headers, Object payload) { Message msg = (Message) payload; logger.info("Received : " + msg.getText()+ " from : " + msg.getFrom()); }
専用ユーザーにのみメッセージを送信する必要がある場合があります(たとえば、チャットを実装する場合)。 次に、クライアント側とサーバー側は、このプライベート会話専用の個別の宛先を使用する必要があります。 宛先の名前は、一般的な宛先名に一意の識別子を追加することで作成できます(例: /queue/chat-user123
。 この目的には、HTTPセッションまたはSTOMPセッション識別子を利用できます。
Springを使用すると、プライベートメッセージの送信がはるかに簡単になります。 コントローラのメソッドに@SendToUser
アノテーションを付けるだけで済みます。 次に、この宛先は、セッション識別子に依存するUserDestinationMessageHandler
によって処理されます。 クライアント側では、クライアントが/user
というプレフィックスが付いた宛先にサブスクライブすると、この宛先はこのユーザーに固有の宛先に変換されます。 サーバー側では、ユーザーの宛先はユーザーのPrincipal
に基づいて解決されます。
@SendToUser
アノテーションが付いたサーバー側コードのサンプル:
@MessageMapping("/greetings") @SendToUser("/queue/greetings") public String reply(@Payload String message, Principal user) { return "Hello " + message; }
または、 SimpMessagingTemplate
を使用できます。
String username = ... this.simpMessagingTemplate.convertAndSendToUser(); username, "/queue/greetings", "Hello " + username);
次に、上記の例のJavaコードで送信できるプライベートメッセージを受信できるJavaScript(SockJS)クライアントを実装する方法を見てみましょう。 WebSocketはHTML5仕様の一部であり、最新のブラウザーのほとんどでサポートされていることを知っておく価値があります(Internet Explorerはバージョン10以降でサポートされています)。
function connect() { var socket = new SockJS('/greetings'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { stompClient.subscribe('/user/queue/greetings', function (greeting) { showGreeting(JSON.parse(greeting.body).name); }); }); } function sendName() { stompClient.send("/app/greetings", {}, $("#name").val()); }
おそらくお気づきかもしれませんが、プライベートメッセージを受信するには、クライアントは/user
のプレフィックスが付いた一般的な宛先/queue/greetings
にサブスクライブする必要があります。 一意の識別子を気にする必要はありません。 ただし、クライアントは事前にアプリケーションにログインする必要があるため、サーバー側のPrincipal
オブジェクトが初期化されます。
WebSocketの保護
多くのWebアプリケーションは、Cookieベースの認証を使用しています。 たとえば、Spring Securityを使用して、特定のページまたはコントローラーへのアクセスをログに記録されたユーザーのみに制限できます。 その後、ユーザーのセキュリティコンテキストは、そのユーザー用に作成されたWebSocketまたはSockJSセッションに後で関連付けられるCookieベースのHTTPセッションを通じて維持されます。 WebSocketエンドポイントは、SpringのWebSecurityConfigurerAdapter
など、他のリクエストと同様に保護できます。
現在、Webアプリケーションは、バックエンドとしてREST APIを使用し、ユーザーの認証と承認のためにOAuth/JWTトークンを使用することがよくあります。 WebSocketプロトコルは、サーバーがHTTPハンドシェイク中にクライアントを認証する方法を記述していません。 実際には、この目的のために標準のHTTPヘッダー(承認など)が使用されます。 残念ながら、すべてのSTOMPクライアントでサポートされているわけではありません。 Spring JavaのSTOMPクライアントでは、ハンドシェイクのヘッダーを設定できます。
WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders(); handshakeHeaders.add(principalRequestHeader, principalRequestValue);
ただし、SockJS JavaScriptクライアントは、SockJSリクエストでの認証ヘッダーの送信をサポートしていません。 ただし、トークンの受け渡しに使用できるクエリパラメータを送信することはできます。 このアプローチでは、クエリパラメータからトークンを読み取り、それを検証するカスタムコードをサーバー側で作成する必要があります。 トークンがリクエストと一緒にログに記録されないようにする(またはログが十分に保護される)ことを確認することも重要です。これにより、重大なセキュリティ違反が発生する可能性があります。
SockJSフォールバックオプション
WebSocketとの統合は必ずしもスムーズに進むとは限りません。 一部のブラウザー(IE 9など)はWebSocketをサポートしていません。 さらに、制限付きプロキシを使用すると、HTTPアップグレードを実行できなくなったり、開いている接続が長すぎたりする可能性があります。 そのような場合、SockJSが助けになります。
SockJSトランスポートは、WebSocket、HTTPストリーミング、およびHTTPロングポーリングの3つの一般的なカテゴリに分類されます。 通信は、SockJSがサーバーから基本情報を取得するためにGET /info
を送信することから始まります。 応答に基づいて、SockJSは使用するトランスポートを決定します。 最初の選択肢はWebSocketです。 それらがサポートされていない場合は、可能であれば、ストリーミングが使用されます。 このオプションも不可能な場合は、転送方法としてポーリングが選択されます。
WebSocketは本番環境にありますか?
この設定は機能しますが、「最良」ではありません。 Spring Bootを使用すると、STOMPプロトコル(ActiveMQ、RabbitMQなど)で本格的なメッセージングシステムを使用できます。外部ブローカーは、使用した単純なブローカーよりも多くのSTOMP操作(確認応答、受信など)をサポートする場合があります。 STOMP Over WebSocketは、WebSocketとSTOMPプロトコルに関する興味深い情報を提供します。 STOMPプロトコルを処理するメッセージングシステムがリストされており、本番環境で使用するためのより優れたソリューションになる可能性があります。 特に、要求の数が多いために、メッセージブローカーをクラスター化する必要がある場合。 (Springの単純なメッセージブローカーはクラスタリングには適していません。)次に、 WebSocketConfig
で単純なブローカーを有効にする代わりに、外部メッセージブローカーとの間でメッセージを転送するStompブローカーリレーを有効にする必要があります。 要約すると、外部メッセージブローカーは、よりスケーラブルで堅牢なソリューションの構築に役立つ場合があります。
SpringBootを探索するJavaDeveloperの旅を続ける準備ができている場合は、次に「OAuth2とJWTREST保護のためのSpringBootの使用」を読んでみてください。