2016-01-21 49 views
23

是否可以將消息發送到特定會話?Spring WebSocket @SendToSession:將消息發送到特定會話

我有一個客戶端和Spring servlet之間的未經身份驗證的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)); 

但這廣播到,不僅是一個指定的所有會話。

UPDATE 2

測試與convertAndSendToUser()方法。 這個測試是和官員,在春天教程黑客: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回覆一次。我敢肯定,「1」是一個有效的sessionId的連接,因爲我看到它在調試模式SimpMessageHeaderAccessor.getSessionId()

背景情景

我想創建一個進度條進行遠程作業的第二客戶端,客戶端向服務器請求一個異步作業,並通過服務器發送的websocket消息檢查其進度。這不是文件上傳,而是遠程計算,所以只有服務器知道每個作業的進度。 我需要發送消息到特定的會話,因爲每個作業都是按會話啓動的。 客戶端要求進行遠程計算 服務器啓動此作業,併爲每個作業步驟回覆申請人客戶端的作業進度狀態。 客戶端收到有關其作業的消息並建立進度/狀態欄。 這就是爲什麼我需要每會話消息。 我也可以使用每個用戶的消息,但是Spring 不會爲每個用戶提供未經請求的消息。(Cannot send user message with Spring Websocket

工作液

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

從UPDATE2解決方案開始,我必須完成convertAndSendToUser方法與去年參數(MessageHeaders):

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(); 
    } 
+0

我發現這個:https://jira.spring.io/browse/SPR-12143 – Tobia

+0

我寫過你的例子。你怎麼知道誰是你想發送消息的特定會話? – Aviad

+0

這是一個遠程作業管理,當控制器收到一個遠程作業請求時,它應該存儲申請人的會話以傳送未來作業狀態回覆 – Tobia

回答

18

不需要創建特定的目的地,從Spring 4.1開始已經開箱即用(請參閱SPR-11309)。

鑑於用戶訂閱/user/queue/something隊列,你可以發送郵件到單個會話中將:

由於stated in the SimpMessageSendingOperations Javadoc,因爲你的用戶名實際上是一個的sessionId,你必須設置爲標題,以及否則, DefaultUserDestinationResolver將無法​​路由該郵件並將其放置。

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

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

不需要用戶進行身份驗證這一點。

+0

會太棒了。明天我會試試看。 – Tobia

+0

這對我不起作用...我嘗試安排每5000ms運行一次:messagingTemplate.convertAndSendToUser(「1」,「/ queue/something」,payload);但客戶端沒有收到該消息。我確定這是「1」會話(第二個客戶端連接),因爲我在調試模式下獲得它。 – Tobia

+0

我添加了一個更新到我的問題與此測試 – Tobia

3

這是非常複雜的,在我看來,是不值得的。 您需要通過其會話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); 

但我不知道你在做什麼以及你會如何找到特定的會話(誰是匿名的)。

+1

這是我的實際解決方案,似乎是一種解決方法。研究Spring Websocket資源似乎有可能爲每個活動套接字獲取會話標識,而且,似乎在理論上可能將消息路由到特定連接。我會用場景背景更新我的問題,告訴你我想用websockets實現什麼。 – Tobia

+0

問題是,你永遠不知道你想發送到什麼確切的會議。在這種情況下,您只需要將客戶端訂閱到特定頻道 – Aviad

+0

它可以這樣做,因爲工作是以客戶端消息開始的,然後服務器應該開始存儲申請者會話ID的這項工作,並且當此作業推進時應該從服務器發送到客戶端與每個作業存儲會話ID – Tobia

相關問題