web-dev-qa-db-ja.com

Spring WebSocket @SendToSession:特定のセッションにメッセージを送信する

特定のセッションにメッセージを送信することは可能ですか?

クライアントとSpringサーブレットの間に認証されていないWebsocketがあります。非同期ジョブが終了したときに、未承諾メッセージを特定の接続に送信する必要があります。

_@Controller
public class WebsocketTest {


     @Autowired
    public SimpMessageSendingOperations messagingTemplate;

    ExecutorService executor = Executors.newSingleThreadExecutor();

    @MessageMapping("/start")
    public void start(SimpMessageHeaderAccessor accessor) throws Exception {
        String applicantId=accessor.getSessionId();        
        executor.submit(() -> {
            //... slow job
            jobEnd(applicantId);
        });
    }

    public void jobEnd(String sessionId){
        messagingTemplate.convertAndSend("/queue/jobend"); //how to send only to that session?
    }
}
_

このコードでわかるように、クライアントは非同期ジョブを開始でき、終了したら終了メッセージが必要です。明らかに、応募者のみにメッセージを送る必要があり、全員にブロードキャストする必要はありません。 _@SendToSession_アノテーションまたは_messagingTemplate.convertAndSendToSession_メソッドがあると素晴らしいでしょう。

[〜#〜] update [〜#〜]

私はこれを試しました:

_messagingTemplate.convertAndSend("/queue/jobend", true, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId));
_

ただし、これは指定されたセッションだけでなく、すべてのセッションにブロードキャストします。

更新2

ConvertAndSendToUser()メソッドでテストします。このテストは、公式のSpringチュートリアルのハックです。 https://spring.io/guides/gs/messaging-stomp-websocket/

これはサーバーコードです。

_@Controller
public class WebsocketTest {

    @PostConstruct
    public void init(){
        ScheduledExecutorService statusTimerExecutor=Executors.newSingleThreadScheduledExecutor();
        statusTimerExecutor.scheduleAtFixedRate(new Runnable() {                
            @Override
            public void run() {
                messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"));
            }
        }, 5000,5000, TimeUnit.MILLISECONDS);
    } 

     @Autowired
        public SimpMessageSendingOperations messagingTemplate;
}
_

そして、これはクライアントコードです:

_function connect() {
            var socket = new WebSocket('ws://localhost:8080/hello');
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function(frame) {
                setConnected(true);
                console.log('Connected: ' + frame);
                stompClient.subscribe('/user/queue/test', function(greeting){
                    console.log(JSON.parse(greeting.body));
                });
            });
        }
_

残念ながら、クライアントは予想どおり5000msごとにセッションごとの応答を受信しません。 SimpMessageHeaderAccessor.getSessionId()のデバッグモードで表示されるため、接続された2番目のクライアントの「1」が有効なsessionIdであると確信しています。

背景シナリオ

リモートジョブの進行状況バーを作成し、クライアントがサーバーに非同期ジョブを要求し、サーバーから送信されたwebsocketメッセージによって進行状況を確認します。これはファイルのアップロードではなく、リモート計算であるため、サーバーのみが各ジョブの進行状況を認識します。各ジョブはセッションごとに開始されるため、特定のセッションにメッセージを送信する必要があります。クライアントは、リモート計算を要求しますサーバーはこのジョブを開始し、ジョブステップごとに、ジョブの進行状況と共に応募者クライアントに応答します。クライアントは、ジョブに関するメッセージを取得し、進行状況/ステータスバーを作成します。これが、セッションごとのメッセージが必要な理由です。ユーザーごとのメッセージを使用することもできますが、Springはユーザーごとの未承諾メッセージごとにを提供しません。 ( Spring Websocketでユーザーメッセージを送信できません

作業ソリューション

_ __      __ ___   ___  _  __ ___  _  _   ___      ___   ___   _    _   _  _____  ___  ___   _  _ 
 \ \    / // _ \ | _ \| |/ /|_ _|| \| | / __|    / __| / _ \ | |  | | | ||_   _||_ _|/ _ \ | \| |
  \ \/\/ /| (_) ||   /| ' <  | | | .` || (_ |    \__ \| (_) || |__| |_| |  | |   | || (_) || .` |
   \_/\_/  \___/ |_|_\|_|\_\|___||_|\_| \___|    |___/ \___/ |____|\___/   |_|  |___|\___/ |_|\_|
_

UPDATE2ソリューションから始めて、最後のパラメーター(MessageHeaders)でconvertAndSendToUserメソッドを完了する必要がありました。

_messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"), createHeaders("1"));
_

ここで、createHeaders()はこのメソッドです。

_private MessageHeaders createHeaders(String sessionId) {
        SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
        headerAccessor.setSessionId(sessionId);
        headerAccessor.setLeaveMutable(true);
        return headerAccessor.getMessageHeaders();
    }
_
35
Tobia

特定の宛先を作成する必要はありません。Spring4.1の時点ですでに設定されています( SPR-11309 を参照)。

特定のユーザーが/user/queue/somethingキューでは、次のコマンドを使用して単一のセッションにメッセージを送信できます。

SimpMessageSendingOperations Javadocに記載 のように、ユーザー名は実際にはsessionIdであるため、ヘッダーとしても設定する必要があります。そうしないと、DefaultUserDestinationResolverはメッセージをルーティングできず、それをドロップします。

SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor
    .create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);

messagingTemplate.convertAndSendToUser(sessionId,"/queue/something", payload, 
    headerAccessor.getMessageHeaders());

このためにユーザーを認証する必要はありません。

33
Brian Clozel

それは非常に複雑で、私の意見では、価値がありません。セッションIDごとに、すべてのユーザー(認証されていないユーザーも含む)のサブスクリプションを作成する必要があります。

すべてのユーザーが自分専用の一意のキューにサブスクライブするとします。

stompClient.subscribe('/session/specific' + uuid, handler);

サーバーでは、ユーザーがサブスクライブする前に、特定のセッションのメッセージを通知して送信し、マップに保存する必要があります。

    @MessageMapping("/putAnonymousSession/{sessionId}")
    public void start(@DestinationVariable sessionId) throws Exception {
        anonymousUserSession.put(key, sessionId);
    }

その後、ユーザーにメッセージを送信する場合、次のことを行う必要があります。

messagingTemplate.convertAndSend("/session/specific" + key); 

しかし、私はあなたが何をしようとしているのか、特定のセッション(匿名)をどのように見つけるのか本当にわかりません。

4
Aviad

単にセッションIDを追加する必要があります

  • サーバ側

    convertAndSendToUser(sessionId、apiName、responseObject);

  • クライアント側

    $ stomp.subscribe( '/ user/+ sessionId +'/apiName '、handler);

注:
サーバー側のエンドポイントに'/user'を追加することを忘れないでください。

2
Sandy