Springbootシリーズ記事の統合WebSocketによるブロードキャストメッセージプッシュ


前言
springbootがwebsocketを統合する前に、websocketの基本概念と、それに関連するsockjs、stompが何なのかを簡単に説明します.
WebSocketの概要
WebSocketプロトコルは、HTML 5に新たに追加された単一TCP接続上でフルデュプレクス通信を行うプロトコルであり、WebSocket APIではブラウザとサーバが握手の動作をするだけで、ブラウザとサーバの間に高速チャネルが形成され、両者の間にデータが直接転送される.
WebSocketとHTTPの違いは、
WebSocketはフルデュプレクス通信プロトコルで、接続が確立されると、WebSocketサーバとブラウザ側がSocketのように積極的に相手にメッセージを送信することができます.HTTPはクライアントからのみ要求を開始し、サーバはクエリー結果を返し、サーバがクライアントに要求を自発的に送信することはできない.下図のように
WebSocketの特徴
ここでWebSocketの特徴をまとめます:-WebSocketサーバーとブラウザはすべて自発的に相手にメッセージを送ることができます-TCPプロトコルの上で創立して、サーバーの実現は比較的に容易です-HTTPプロトコルと良好な互換性を持って、デフォルトのポートも80と443で、しかも握手の段階はHTTPプロトコルを採用して、HTTPエージェント-データフォーマットを通じて比較的に軽くて、性能のオーバーヘッドは小さくて、通信効率-テキストを送信することもできるし、バイナリデータを送信することもできる-同源制限はなく、クライアントは任意のサーバと通信することができる-プロトコル識別子はws(暗号化されている場合はwss)、サーバURLはURLである
SockJS
SockJSはブラウザ上で実行されるJavaScriptライブラリであり、ブラウザがWebSocketをサポートしていない場合、このライブラリはWebSocketのサポートをシミュレートすることができ、ブラウザとWebサーバ間の低遅延、全二重、ドメイン間の通信チャネルを実現する.
STOMP
STOMP(STOMP)すなわちSimple(or Streaming)Text Oriented Messaging Protocolの略称である単純(ストリーム)テキスト指向メッセージプロトコルは、STOMPクライアントが任意のSTOMPメッセージエージェント(Broker)と対話できるように、ユーザ操作可能な接続フォーマットを提供し、STOMPプロトコルは設計が簡単で、クライアントの開発が容易であるため、多くの言語と多くのプラットフォームで広く応用されている.
以前の紹介ではWebSocketはTCPプロトコルに基づいていると述べていましたが、WebSocket(またはSockJS)を直接使用してプログラミングするのはTCPソケットを直接使用してWebアプリケーションをプログラミングするのと似ています.これは非常につらいです.上層プロトコルがないため、アプリケーション間で送信される消息の意味を定義する必要があります.また、接続の両端がこれらの意味に従うことを確保する必要があります.
STOMPは、HTTPがTCPソケットに要求応答モデル層を追加するのと同様に、WebSocketの上にフレームベースの回線フォーマット層を提供し、メッセージの意味を定義するために使用される.
STOMPフレーム
STOMPフレームは、コマンド、1つまたは複数のヘッダメッセージ、および負荷からなり、以下に示すようにデータを送信するSTOMPフレームである.
   SEND
destination:/app/room-message
content-length:20

{\"message\":\"Hello!\"}

以下のように分析します.-SEND:STOMPコマンドは、いくつかのコンテンツ-destination:ヘッダメッセージが送信されることを示します.メッセージがどこに送信されるかを示すために使用されます.content-length:ヘッダ情報は、負荷コンテンツのサイズ-空行-フレームコンテンツ(負荷)コンテンツを表すために使用されます.
WebSocket、SockJS、STOMPの関係
簡単に言えば、WebSocketはTCPベースの下位プロトコルであり、SockJSはWebSocketの代替案であり、WebSocketをサポートしないブラウザに用いられ、下位プロトコルでもあるが、STOMPはWebSocketの上位プロトコルであり、高級プロトコルである
SpringBoot統合WebSocket
前にいくつかの基礎知識を敷き詰めた後、次はこの文章のテーマに入って、SpringBoot+WebSocket+SockJS+STOMPを使ってラジオ式のWebSocketを構築します.
依存のインポート
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-websocketartifactId>
dependency>

WebSocket構成
@Configuration
@EnableWebSocketMessageBroker //  STOMP  
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //  STOMP  , WebSocket        WebSocket    
        //      ,                  ,      
        registry.addEndpoint("/point")
                //    
                .setAllowedOrigins("*")
                //  SockJS  
                .withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //      ,        "/topic","/queue"        STOMP   
        registry.enableSimpleBroker("/topic", "/queue");
        //             "/app",                       @MessageMapping        
        registry.setApplicationDestinationPrefixes("/app");
    }
}

上記のプログラムを解析する:-@EnableWebSocketMessageBroker注記WebSocketだけでなく、エージェントベースのSTOMPメッセージ-registerStompEndpointsをリロードする方法も構成されており、"/point"をSTOMPエンドポイントとして登録するには、クライアントが先にこのエンドポイント-リロードconfigureMessageBrokerに接続してメッセージエージェントを構成するとともに、アプリケーションの宛先プレフィックスを設定する必要がある.アプリケーションを宛先とするメッセージが@MessageMapoping注釈付きコントローラメソッドに直接ルーティングされる場合
下図はspring-websocketの公式ドキュメントから来て、websocketの通信モデル図として表します
モデル図を解読します.
同じターゲット:/aの場合、その接頭辞はメッセージの処理方法を決定し、2つに分けられる:/app/a/topic/a/topic/aであれば、メッセージボディを単純なエージェントメッセージプロセッサに直接送信することができ、/app/aであれば、アプリケーション内の@MessageMapping注釈を有するコントローラメソッドにメッセージをルーティングする.コントローラ・メソッドで処理を行い、その後、処理結果をbrokeChannelに送信し、最後に単純エージェント・メッセージ・プロセッサにメッセージを送信し、両方の場合、最後にエージェントを介してクライアントの宛先に再送信する.
リクエストメッセージクラス
public class RequestMessage {
    private String name;

    public String getName() {
        return name;
    }
}

レスポンスメッセージクラス
public class ResponseMessage {
    private String responseMessage;

    public ResponseMessage(String responseMessage) {
        this.responseMessage = responseMessage;
    }

    public ResponseMessage() {
    }

    public String getResponseMessage() {
        return responseMessage;
    }

    public void setResponseMessage(String responseMessage) {
        this.responseMessage = responseMessage;
    }
}

クライアントからのSTOMPメッセージの処理
@MessageMapping注記を使用して、STOMPメッセージをコントローラで処理します.コードは次のとおりです.
@Controller
public class GreetingController {
    /**
     *      /app/greeting      
     *
     * @param greeting
     * @return
     */
    @MessageMapping("/greeting")
//    @SendTo("/topic/say")
    public ResponseMessage handle(RequestMessage greeting) {
        //Spring           STOMP         RequestMessage  
        System.out.println(greeting.getName());
        return new ResponseMessage("welcome," + greeting.getName());
    }
}

コード解析:-handleメソッドは、クライアントが宛先/app/greetingに送信したメッセージを処理し、/appは、構成クラスでアプリケーションの宛先接頭辞に設定されているため、隠されている.このメソッドにはRequestMessageパラメータがある.実際にSpringはメッセージ変換器を用いてメッセージ負荷をRequestMessageオブジェクトに変換する-この方法はResponseMessageエンティティを返し、Springはメッセージ変換器を用いてこの返されたResponseMessageオブジェクトをメッセージ負荷に変換する-デフォルトでは、メッセージを返す宛先とクライアントがメッセージを送信する宛先は/topicを追加するだけで、もちろん@SendTo注釈を使用して、メッセージを返す宛先を再ロードすることもできます.
注釈の購読@SubcribeMapping
クライアントがアドレスを購読する場合、@SubcribeMapping注釈を使用して、購読の応答としてメッセージを送信することもできます.
   @SubscribeMapping("/subscribe")
    public ResponseMessage subscribe() {
        ResponseMessage responseMessage = new ResponseMessage();
        responseMessage.setResponseMessage("    ");
        return responseMessage;
    }

ここでの注記@SubcribeMapping注記は、クライアントが/app/subscribe(/appがアプリケーション宛先のプレフィックス)宛先を購読すると、subscribe()メソッドが呼び出され、ResponseMessageオブジェクトが返されることを示している
SimpMessagingTemplateの利用
SimpMessagingTemplate,SpringのSimpMessagingTemplateを使用すると、アプリケーションのどこでもメッセージを送信することができ、最初にメッセージを受信する必要もありません.
クライアント
クライアント作成にはstompを追加する必要がある.jsとsock.js、次は特定のクライアントコードです.
<html>
<head>
    <meta charset="UTF-8"/>
    <title>   WebSockettitle>
    <script src="js/sockjs.min.js">script>
    <script src="js/stomp.js">script>
    <script src="js/jquery-3.1.1.js">script>
head>
<body onload="disconnect()">
<noscript><h2 style="color: #e80b0a;">Sorry,      WebSocketh2>noscript>
<div>
    <div>

        <button id="connect" onclick="connect();">  button>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">    button>
    div>

    <div id="conversationDiv">
        <label>      label><input type="text" id="name"/>
        <button id="sendName" onclick="sendName();">  button>
        <p id="response">p>
        <p id="callback">p>
    div>
div>
<script type="text/javascript">
    var stompClient = null;

    function setConnected(connected) {
        document.getElementById("connect").disabled = connected;
        document.getElementById("disconnect").disabled = !connected;
        document.getElementById("conversationDiv").style.visibility = connected ? 'visible' : 'hidden';
        $("#response").html();
        $("#callback").html();
    }

    function connect() {
        
        var socket = new SockJS('http://localhost:9999/point');
        stompClient = Stomp.over(socket);
        stompClient.connect({}, function (frame) {
            setConnected(true);
            console.log('Connected:' + frame);
            
            stompClient.subscribe('/topic/greeting', function (response) {
                showResponse(JSON.parse(response.body).responseMessage);
            });
            
            stompClient.subscribe('/app/subscribe', function (response) {
                showResponse(JSON.parse(response.body).responseMessage);
            });
        });
    }

    function disconnect() {
        if (stompClient != null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log('Disconnected');
    }

    function sendName() {
        var name = $('#name').val();
        console.log('name:' + name);
        
        stompClient.send("/app/greeting", {}, JSON.stringify({'name': name}));
    }

    function showResponse(message) {
        $("#response").html(message);
    }
    function showCallback(message) {
        $("#callback").html(message);
    }
script>
body>
html>

テスト結果
ページで接続をクリックすると、/pointのエンドポイントに接続し、/topic/greeting/app/subscribeを同時に購読し、名前を入力して送信をクリックすると、/greetingのURLにメッセージが送信され、サーバは/topic/greetingにメッセージに応答します.
参考資料&お礼
  • WebSocketチュートリアル
  • スプリング実戦第4版
  • SpringBootシリーズ-統合WebSocketリアルタイム通信