2015-05-04 7 views
15

Nota: vedere l'aggiornamento in fondo alla domanda per ciò che alla fine ho concluso.Risposta a più volte tramite socket Web senza autenticazione a molla

Ho bisogno di inviare più risposte a una richiesta tramite il socket Web che ha inviato il messaggio di richiesta, il primo rapidamente e gli altri dopo che i dati sono stati verificati (da qualche parte tra 10 e 60 secondi dopo, da più thread paralleli) .

Ho problemi a ottenere le risposte successive per interrompere la trasmissione su tutte le prese web aperte. Come posso far sì che vengano inviati solo alla presa Web iniziale? O dovrei usare qualcosa oltre a Spring STOMP (perché, a dire il vero, tutto quello che voglio è il routing del messaggio a varie funzioni, non ho bisogno o voglio la possibilità di trasmettere ad altre prese, quindi sospetto di poter scrivere il messaggio distributore me stesso, anche se sta reinventando la ruota).

Non sto utilizzando l'autenticazione Spring (questo è stato retrofittato in un codice legacy).

Sul messaggio di ritorno iniziale, posso utilizzare @SendToUser e, anche se non abbiamo un utente, Spring invia solo il valore di ritorno al websocket che ha inviato il messaggio. (vedi this question).

Con le risposte più lente, tuttavia, penso di aver bisogno di usare SimpMessagingTemplate.convertAndSendToUser (utente, destinazione, messaggio), ma non posso, perché devo passare l'utente, e non riesco a capire quale utente è stato utilizzato @SendToUser. Ho provato a seguire i passaggi in this question, ma non è riuscito a farlo funzionare non autenticato (principal.getName() restituisce null in questo caso).

L'ho semplificato notevolmente per il prototipo, quindi non preoccuparti di sincronizzare i thread o altro. Voglio solo che le prese sul web funzionino correttamente.

Ecco il mio controller:

@Controller 
public class TestSocketController 
{ 
    private SimpMessagingTemplate template; 

    @Autowired 
    public TestSocketController(SimpMessagingTemplate template) 
    { 
    this.template = template; 
    } 

    // This doesn't work because I need to pass something for the first parameter. 
    // If I just use convertAndSend, it broacasts the response to all browsers 
    void setResults(String ret) 
    { 
    template.convertAndSendToUser("", "/user/topic/testwsresponse", ret); 
    } 

    // this only sends "Testing Return" to the browser tab hooked to this websocket 
    @MessageMapping(value="/testws") 
    @SendToUser("/topic/testwsresponse") 
    public String handleTestWS(String msg) throws InterruptedException 
    { 
    (new Thread(new Later(this))).start(); 
    return "Testing Return"; 
    } 

    public class Later implements Runnable 
    { 
    TestSocketController Controller; 
    public Later(TestSocketController controller) 
    { 
     Controller = controller; 
    } 

    public void run() 
    { 
     try 
     { 
      java.lang.Thread.sleep(2000); 

      Controller.setResults("Testing Later Return"); 
     } 
     catch (Exception e) 
     { 
     } 
    } 
    } 
} 

Per la cronaca, qui è il lato del browser:

var client = null; 
function sendMessage() 
{ 
    client.send('/app/testws', {}, 'Test'); 
} 

// hooked to a button 
function test() 
{ 
    if (client != null) 
    { 
     sendMessage(); 
     return; 
    } 

    var socket = new SockJS('/application-name/sendws/'); 
    client = Stomp.over(socket); 
    client.connect({}, function(frame) 
    { 
     client.subscribe('/user/topic/testwsresponse', function(message) 
     { 
      alert(message); 
     }); 

     sendMessage(); 
    }); 
}); 

E qui è il config:

@Configuration 
@EnableWebSocketMessageBroker 
public class TestSocketConfig extends AbstractWebSocketMessageBrokerConfigurer 
{ 
    @Override 
    public void configureMessageBroker(MessageBrokerRegistry config) 
    { 
     config.setApplicationDestinationPrefixes("/app"); 
     config.enableSimpleBroker("/queue", "/topic"); 
     config.setUserDestinationPrefix("/user"); 
    } 

    @Override 
    public void registerStompEndpoints(StompEndpointRegistry registry) 
    { 
     registry.addEndpoint("/sendws").withSockJS(); 
    } 
} 

UPDATE: A causa i problemi di sicurezza legati alla possibilità di inviare informazioni su altre web socket rispetto al socket originario , Ho finito col consigliare al mio gruppo che non usiamo l'implementazione Spring 4.0 di STOMP su Web Sockets. Capisco perché il team di Spring ha fatto ciò che hanno fatto, ed è più potere di cui avevamo bisogno, ma le restrizioni di sicurezza sul nostro progetto erano abbastanza severe, e le reali esigenze erano abbastanza semplici, che abbiamo deciso di andare in un modo diverso . Ciò non invalida le risposte di seguito, quindi prendi la tua decisione in base alle esigenze dei tuoi progetti. Almeno abbiamo sperato che tutti abbiano imparato i limiti della tecnologia, nel bene e nel male.

risposta

6

Perché non usi un argomento separato per ogni cliente?

  1. Il client genera un ID di sessione.

    var sessionId = Math.random().toString(36).substring(7);

  2. cliente sottoscrive/topic/testwsresponse/{} sessionId, quindi invia un messaggio a '/ app/testws/{} sessionId'.

  3. Nel controller si utilizza @MessageMapping (value = "/ testws/{sessionId}") e rimuovere @SendToUser. Puoi usare @DestinationVariable per accedere a sessionId nel tuo metodo.
  4. Il controllore invia ulteriori risposte a/topic/testwsresponse/{sessionId}.

In sostanza Spring esegue un'operazione simile internamente quando si utilizzano le destinazioni utente. Poiché non si utilizza l'autenticazione Spring, non è possibile fare affidamento su questo meccanismo, ma è possibile implementarlo facilmente come descritto sopra.

var client = null; 
var sessionId = Math.random().toString(36).substring(7); 
function sendMessage() 
{ 
    client.send('/app/testws/' + sessionId, {}, 'Test'); 
} 

// hooked to a button 
function test() 
{ 
    if (client != null) 
    { 
     sendMessage(); 
     return; 
    } 

    var socket = new SockJS('/application-name/sendws/'); 
    client = Stomp.over(socket); 
    client.connect({}, function(frame) 
    { 
     client.subscribe('/topic/testwsresponse/' + sessionId, function(message) 
     { 
      alert(message); 
     }); 

     // Need to wait until subscription is complete 
     setTimeout(sendMessage, 1000); 
    }); 
}); 

Controller:

@Controller 
public class TestSocketController 
{ 
    private SimpMessagingTemplate template; 

    @Autowired 
    public TestSocketController(SimpMessagingTemplate template) 
    { 
     this.template = template; 
    } 

    void setResults(String ret, String sessionId) 
    { 
     template.convertAndSend("/topic/testwsresponse/" + sessionId, ret); 
    } 

    @MessageMapping(value="/testws/{sessionId}") 
    public void handleTestWS(@DestinationVariable String sessionId, @Payload String msg) throws InterruptedException 
    { 
     (new Thread(new Later(this, sessionId))).start(); 
     setResults("Testing Return", sessionId); 
    } 

    public class Later implements Runnable 
    { 
     TestSocketController Controller; 
     String sessionId; 
     public Later(TestSocketController controller, String sessionId) 
     { 
      Controller = controller; 
      this.sessionId = sessionId; 
     } 

     public void run() 
     { 
      try 
      { 
       java.lang.Thread.sleep(2000); 

       Controller.setResults("Testing Later Return", sessionId); 
      } 
      catch (Exception e) 
      { 
      } 
     } 
    } 
} 

appena provato, funziona come previsto.

+0

Ho alcuni problemi di sicurezza con questo. Il primo è che qualcuno potrebbe scoprire quale stringa casuale sta usando qualcun altro e ascoltare la conversazione. Il secondo è che qualcuno potrebbe registrarsi per alcune (o tutte, forse) delle potenziali stringhe e ascoltare tutte le risposte. Anche avere fortuna e ascoltare la risposta di un'altra persona non è abbastanza sicuro per questo progetto. Ho davvero bisogno di inviare il messaggio solo su un singolo socket web e sto iniziando a pensare che l'implementazione Spring di STOMP non lo farà .. –

+0

Non dovresti preoccuparti della sicurezza. Basta usare una stringa casuale sufficientemente lunga o usare l'UUID per l'id della sessione. http://en.wikipedia.org/wiki/Universally_unique_identifier#Random_UUID_probability_of_duplicates Fondamentalmente l'hacker avrà bisogno in media di 17 miliardi di anni per rompere il sistema. – medvedev1088

+0

È più probabile che indovinassi la tua password di Gmail che l'hacker indovina l'id della sessione casuale. – medvedev1088

0

Questa non è una risposta completa. Solo considerazione generale e suggerimento. Non è possibile fare cose o tipi di connessione diversi attraverso lo stesso socket. Perché non hai prese diverse per lavori diversi? Alcuni con autenticazione e altri senza. Alcuni per l'attività rapida e alcuni per l'esecuzione lunga.

+0

Perché dietro le quinte, la richiesta avvia i thread separati per eseguire i lavori più duraturi e non potrò più facilmente parlare con quei thread se chiudo la prima connessione. Quello che ho ora sono i thread che scrivono cose nel database e chiamate ajax che tirano uno qualsiasi dei server Web nella farm ogni pochi secondi, che leggono il database e vedono se qualcosa è finito. Usando un socket web che dice open, lo blocco su un server (per un po 'di tempo) e do ai thread la possibilità di rispondere direttamente, senza che il database sia coinvolto. –

Problemi correlati