Spring WebSocket @SendToSession: wyślij wiadomość do konkretnej sesji

Czy można wysłać wiadomość do konkretnej sesji?

Mam nieautoryzowany websocket pomiędzy klientami i Spring servlet. Muszę wysłać niechcianą wiadomość do określonego połączenia, gdy kończy się zadanie asynchroniczne.

@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?
    }
}

Jak widać w tym kodzie, klient może rozpocząć zadanie asynchroniczne, a kiedy się zakończy, potrzebuje wiadomości końcowej. Oczywiście muszę wysłać wiadomość tylko do wnioskodawcy, a nie do wszystkich. Byłoby wspaniale mieć @SendToSession adnotację lub messagingTemplate.convertAndSendToSession metoda.

UPDATE

Próbowałem tego:

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

Ale to nadaje do wszystkich sesji, nie tylko tej określonej.

UPDATE 2

Przetestuj metodą convertAndSendToUser (). Ten test jest i hack z oficjalnego Spring tutorial: https://spring.io/guides/gs/messaging-stomp-websocket/

To jest kod serwera:

@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;
}

A to jest kod klienta:

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));
                });
            });
        }

Niestety klient nie otrzymuje odpowiedzi na sesję co 5000ms zgodnie z oczekiwaniami. Jestem pewien, że" 1 " jest poprawnym identyfikatorem sesji dla drugiego podłączonego klienta, ponieważ widzę go w trybie debugowania z SimpMessageHeaderAccessor.getSessionId()

SCENARIUSZ PODSTAWOWY

Chcę utworzyć pasek postępu dla zdalnego zadania, klient pyta serwer o zadanie asynchroniczne i sprawdza swój postęp przez wiadomość websocket wysłaną z serwera. Nie jest to przesyłanie plików, ale zdalne obliczenia, więc tylko serwer zna postępy każdego zadania. Muszę wysłać wiadomość do konkretnej sesji, ponieważ każde zadanie jest uruchamiane przez sesję. Klient prosi o zdalne obliczenia Serwer uruchamia tę pracę i na każdy krok zadania odpowiada klientowi aplikującemu ze statusem postępu pracy. Klient otrzymuje wiadomości o swoim zadaniu i tworzy pasek postępu/stanu. Dlatego potrzebuję wiadomości na sesję. Mogę również użyć wiadomości dla każdego użytkownika, ale Spring nie zapewnia dla każdego użytkownika niechcianych wiadomości. (nie można wysłać wiadomości użytkownika za pomocą Springa Websocket )

ROZWIĄZANIE ROBOCZE

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

Począwszy od rozwiązania UPDATE2 musiałem wykonać metodę convertAndSendToUser z last param (MessageHeaders):

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

Gdzie createHeaders() jest tą metodą:

private MessageHeaders createHeaders(String sessionId) {
        SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
        headerAccessor.setSessionId(sessionId);
        headerAccessor.setLeaveMutable(true);
        return headerAccessor.getMessageHeaders();
    }
Author: Community, 2016-01-21

3 answers

Nie ma potrzeby tworzenia konkretnych miejsc docelowych, jest to już zrobione po wyjęciu z pudełka od wiosny 4.1 (patrz SPR-11309).

Użytkownicy zapisują się do kolejki /user/queue/something, możesz wysłać wiadomość do jednej sesji za pomocą:

Jak podano w Simpmessagesendingoperations Javadoc , ponieważ Twoja nazwa użytkownika jest w rzeczywistości identyfikatorem sessionId, musisz ustawić go również jako nagłówek, w przeciwnym razie DefaultUserDestinationResolver nie będzie w stanie przekierować wiadomości i ją upuści.

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

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

Nie potrzebujesz użytkowników być uwierzytelnione w tym celu.

 25
Author: Brian Clozel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-27 10:20:16

To bardzo skomplikowane i moim zdaniem nie jest tego warte. Musisz utworzyć subskrypcję dla każdego użytkownika (nawet nieautoryzowanego) według identyfikatora sesji.

Załóżmy, że każdy użytkownik subskrybuje unikalną kolejkę tylko dla niego:

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

Na serwerze, zanim użytkownik się zapisze musisz powiadomić i wysłać wiadomość dla konkretnej sesji i zapisać na mapie:

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

Po tym, gdy chcesz wysłać wiadomość do użytkownika, będziesz potrzebować do:

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

Ale tak naprawdę Nie wiem, co próbujesz zrobić i jak znajdziesz konkretną sesję (kto jest anonimowy).

 3
Author: Aviad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-25 01:57:14

Musisz po prostu dodać identyfikator sesji w

  • Strona Serwera

    ConvertAndSendToUser (sessionId,apiName,responseObject);

  • Strona Klienta

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

Uwaga:
Nie zapomnij dodać '/user' w punkcie końcowym po stronie serwera.

 1
Author: Sandy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-27 19:07:26