2016-01-21 33 views
23

Czy można wysłać wiadomość do określonej sesji?Spring WebSocket @SendToSession: wyślij wiadomość do konkretnej sesji

Mam nieuwierzytelnionego websocket między klientami a serwletem Spring. Muszę wysłać niezamawianą wiadomość do określonego połączenia, gdy zakoń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 po jego zakończeniu potrzebuje komunikatu końcowego. Oczywiście, muszę wysłać tylko wnioskodawcę i nie przekazywać go wszystkim. Byłoby wspaniale mieć metodę adnotacji @SendToSession lub .

UPDATE

Próbowałem to:

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

Ale ten nadaje się do wszystkich sesji, a nie tylko jeden określonych. Sposób

UPDATE 2

testu z convertAndSendToUser(). Ten test jest i siekać oficjalnego tutoriala wiosny: https://spring.io/guides/gs/messaging-stomp-websocket/

Jest to 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; 
} 

i jest to 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 otrzyma odpowiedź na sesję co 5000 ms zgodnie z oczekiwaniami. Jestem pewien, że „1” jest poprawnym sessionId na 2. klienta podłączonego bo widzę go w trybie debugowania z SimpMessageHeaderAccessor.getSessionId()

TŁO SCENARIUSZ

Chcę utworzyć pasek postępu dla zdalnej pracy, klient prosi serwer o zadanie asynchroniczne i sprawdza jego postęp za pomocą wiadomości websocket wysłanej z serwera. To NIE jest przesyłanie pliku, ale zdalne obliczenia, więc tylko serwer zna postęp każdego zadania. Muszę wysłać wiadomość do określonej sesji, ponieważ każde zadanie jest uruchamiane przez sesję. Klient prosi o zdalne obliczenia Serwer uruchamia to zlecenie i dla każdego kroku zadania odpowiada klientowi składającemu wniosek o statusie postępu pracy. Klient otrzymuje komunikaty o swojej pracy i tworzy pasek postępu/stanu. Dlatego potrzebuję wiadomości na sesję. Mogę również używać wiadomości dla poszczególnych użytkowników, ale Wiosna nie dostarcza dla niezamawianych wiadomości.(Cannot send user message with Spring Websocket)

roztwór roboczy

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

Wychodząc z roztworu Update2 miałem z Metody convertAndSendToUser z ostatniego param (MessageHeaders)

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

gdzie createHeaders() Jest to metoda:

private MessageHeaders createHeaders(String sessionId) { 
     SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); 
     headerAccessor.setSessionId(sessionId); 
     headerAccessor.setLeaveMutable(true); 
     return headerAccessor.getMessageHeaders(); 
    } 
+0

Znalazłem to: https://jira.spring.io/browse/SPR-12143 – Tobia

+0

Napisałem ci przykład. Jak określić, do kogo jest określona sesja, do której chcesz wysłać wiadomość? – Aviad

+0

To jest zdalne zarządzanie zadaniami, gdy kontroler odbiera zdalne zlecenie zadania, powinien przechowywać sesję wnioskodawcy, aby przekierować odpowiedzi o przyszłym stanie pracy. – Tobia

Odpowiedz

18

Nie trzeba tworzyć konkretnych miejsc docelowych, jest to już gotowe do pracy od wiosny 4.1 (patrz SPR-11309).

Podane użytkowników subskrybować /user/queue/something kolejce, możesz wysłać wiadomość do jednej sesji z:

Jak stated in the SimpMessageSendingOperations Javadoc, ponieważ nazwa użytkownika jest rzeczywiście sessionId należy ustawić, że w nagłówku, jak również inaczej DefaultUserDestinationResolver nie będzie mógł skierować 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 do uwierzytelnienia w tym celu.

+0

Byłoby fantastycznie. Jutro spróbuję. – Tobia

+0

To nie działa dla mnie ... Próbowałem zaplanować każde 5000ms w tym uruchomieniu: messagingTemplate.convertAndSendToUser ("1", "/ queue/something", ładunek); Ale klient nie otrzymuje wiadomości. Jestem pewien, że jest to sesja "1" (drugi klient został połączony), ponieważ dostaję ją w trybie debugowania. – Tobia

+0

Dodałem aktualizację do mojego pytania z tym testem. – Tobia

3

Jest to bardzo skomplikowane i moim zdaniem nie jest tego warte. Musisz utworzyć subskrypcję dla każdego użytkownika (nawet nieuwierzytelnionego) przez jego identyfikator sesji.

Załóżmy, że każdy użytkownik subskrybuje wyjątkowej kolejce tylko dla niego:

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

Na serwerze, zanim użytkownik subskrybuje trzeba będzie zawiadomić i wysłać wiadomość do konkretnej sesji i zapisać do mapa:

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

Po tym, kiedy chcesz wysłać wiadomość do użytkownika należy:

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

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

+1

To jest moje rzeczywiste rozwiązanie i wygląda na obejście tego problemu. Spojrzenie na źródła Spring Websocket wydaje się być w stanie uzyskać identyfikator sesji dla każdego aktywnego gniazda, a ponadto wydaje się, teoretycznie, możliwość przekierowania wiadomości do określonego połączenia. Zaktualizuję moje pytanie scenariuszem tła, aby powiedzieć, co chcę osiągnąć za pomocą stron internetowych. – Tobia

+0

Problem polega na tym, że nigdy nie wiesz, do jakiej dokładnie sesji chcesz wysłać ...W takim przypadku wystarczy zasubskrybować klienta do określonego kanału. – Aviad

+0

Może to zrobić, ponieważ zadanie zaczyna się od komunikatu klienta, następnie serwer powinien rozpocząć to zadanie, przechowując identyfikator sesji wnioskodawcy, a kiedy to zadanie zostanie wysłane, wiadomość powinna zostać wysłana z serwera do klient z zapisanym w sesji identyfikatorem sesji – Tobia