WebSocket in SpringBoot

Nell’articolo WebSocket in Java abbiamo introdotto la specifica JSR 356, che definisce le API disponibili in Java per implementare una WebSocket ed il relativo client. In questo post vediamo invece come Spring supporta il protocollo delle WebSocket (definito nella specifica RFC 6455), ed in particolare lo faremo convertendo l’esempio del Bingo, introdotto nell’articolo citato sopra, in una applicazione Spring Boot.

HTTP vs WebSocket

Sebbene il protocollo WebSocket sia progettato per essere compatibile con quello HTTP e di fatto ha inizio con una richiesta HTTP, è importante comprendere che i due protocolli implicano modelli di programmazione molto differenti.

In HTTP (e REST), le applicazioni sono strutturate esponendo diversi URL, tipicamente una per ciascuna operazione implementata dall’applicazione. I client accendono a tali URL, inviando una richiesta ed attendendo una risposta, quindi la connessione viene chiusa ed una nuova richiesta deve essere inviata per invocare una nuova operazione.

Al contrario, una applicazione che utilizza le WebSocket, di solito espongono una solo un URL per la connessione iniziale. Successivamente, tutti i messaggi dell’applicazione transitano sulla stessa connessione TCP. Ciò indica un’architettura di messaggistica asincrona, guidata dagli eventi, completamente diversa.

WebSocket inoltre è un protocollo di trasporto di basso livello che, a differenza di HTTP, non specifica alcuna semantica al contenuto dei messaggi. Ciò significa che non è possibile instradare o elaborare un messaggio a meno che il client e il server non concordino sulla semantica del messaggio. I client e i server possono però negoziare l’uso di un protocollo di messaggistica di livello superiore (come ad esempio, STOMP), tramite l’intestazione Sec-WebSocket-Protocol sulla richiesta di handshake HTTP. In mancanza di ciò, devono elaborare le proprie convenzioni.

Il Progetto

Come anticipato, nell’articolo eseguiremo il porting dell’applicazione Bingo presentata nel post WebSocket in Java. La figura seguente riepilogo le componenti che sono coinvolte nel progetto.

La componente Bingo Server è un’applicazione Spring Boot che espone la WebSocket all’url ws://localhost:8080/bingo/host/websocket. Si noti che il suffisso websocket alla URL base è inserito da framework Spring. Il tabellone riepilogativo dei numeri estratti è invece un semplice file index.html che utilizza la classe WebSocket offerta da HTML5 per ascoltare gli eventi del server di gioco. Infine i client rappresentano i giocatori che generano le proprie cartelle di gioco, formate da 5 numeri, e restano in ascolto delle estrazioni comunicando l’eventuale evento BINGO al server.

Nel seguito tralasceremo la componente index.html, sufficientemente descritta in WebSocket in Java.

Bingo Server

Come anticipato il server è un’applicazione Spring Boot che possiamo generare utilizzando lo Spring Boot Initializr, o semplicemente creando una applicazione Maven ed inserendo le necessarie dipendenze, che sono: spring-boot-starter-websockettomcat-embed-websocket. Il file pom.xml risulta quindi molto snello:

Gestione del Tablou

Il tablou di gioco è gestito attraverso un oggetto Tablou che estende ArrayList ridefinendo il metodo next():

La classe TablouService è responsabile invece della gestione delle estrazioni, che sono eseguite al ritmo di una al minuto. Per la schedulazione sono utilizzate le API disponibili in Spring, in particolare il framework mette a disposizione l’annotazione @Scheduled con cui è possibile annotare un qualsiasi bean gestito dal container IoC. Per abilitare l’utilizzo dello scheduler di Spring è innanzitutto necessario definire una classe di configurazione ed annotarla con @EnableScheduling.

Di fatto l’implementazione dell’interfaccia SchedulingConfigurer e la ridefinizione del metodo configureTasks(), non sarebbe necessaria, se non fosse che in qualche modo lo sheduler di default sembra andare in conflitto con qualche altro componente, generando l’eccezione:

Bean named 'defaultSockJsTaskScheduler' is expected to be of type 'org.springframework.scheduling.TaskScheduler' but was actually of type 'org.springframework.beans.factory.support.NullBean'

Altra particolarità della classe TablouService è che la comunicazione degli eventi di estrazione è eseguita in modo asincrono. Nell’articolo originale (WebSocket in Java) questi sono gestiti utilizzando la gestione degli eventi disponibili in CDI. In questo porting si utilizzeranno gli eventi di Spring, il cui utilizzo prevede poche semplici linee guida:

  1. gli eventi sono oggetti che estendono la classe ApplicationEvent.
  2. il publisher emette gli eventi attraverso la classe ApplicationEventPublisher disponibile nel contesto di Spring ed iniettabile attraverso l’annotazione @Autowired.
  3. l listener degli eventi devono implementare l’interfaccia ApplicationListener.

Procediamo quindi definendo le tre classi che rappresentano i tre eventi gestiti dall’applicazione ovvero: l’avvio di una sessione di gioco, l’estrazione di un numero e il bingo da parte di uno dei partecipanti.

In particolare la classe TablouService emette gli eventi StartEvent e DrawEvent ed è in ascolto dell’evento BingoEvent al fine di interrompere l’estrazione. In ragione di ciò la classe implementa l’interfaccia ApplicationListener<BingoEvent> che definisce il metodo onApplicationEvent(BingoEvent event) invocato dal container quando l’evento bingo è emesso. La figura seguente mostra il flusso degli eventi nell’applicazione.

La classe TablouService ha quindi il seguente aspetto:

Gestione delle Connessioni

Veniamo all’argomento centrale di questo articolo, la gestione di una WebSocket. La prima cosa che è necessario fare è abilitare nel progetto l’utilizzo delle WebSocket di Spring e definire l’handler che gestirà il ciclo di vita della comunicazione con i client. A tale scopo è sufficiente definire una classe di configurazione che implementa l’interfaccia WebSocketConfigurer e che è annotata con @EnableWebSocket:

Nel metodo registerWebSocketHandlers() viene registrato l’handler WebSocketHandler associandolo alla path /host. Questo comporta che la  URL della socket sarà ws://localhost:8080/bingo/host/websocket, dove il contesto /bingo è definito nel file application.properties letto da Spring Boot, mentre /websocket è inserito dalle WebSochet API di Spring. Inoltre il metodo setAllowedOrigins() valorizzato con * consente l’invocazione della websocket da parte di una qualsiasi origine, molto importante per la nostra applicazione di esempio al fine di evitare un errore HTTP 403 Forbidden, che sarebbe restituito dal server essendo i due client non pubblicati su web.

Infine definire l’handler di una WebSocket significa implementare l’interfaccia WebSocketHandler che definisce i seguenti metodi:

afterConnectionEstablished() Invocato quando la connessione WebSocket aperta e pronto per l’uso.
afterConnectionClosed() Invocata quando la connessione WebSocket è stata chiusa da entrambi i lati o dopo che si è verificato un errore di trasporto.
handleMessage() Invocata quando arriva un nuovo messaggio dalla connessione WebSocket.
handleTransportError() Gestisce un eventuale errore di trasporto del messaggio nella WebSocket sottostante.
supportsPartialMessages() indica se l’handler è in grado di gestire messaggi parziali.

Molto più probabilmente però l’handler andrà ad estendere le classi  BinaryWebSocketHandler o TextWebSocketHandler, che sono classi che ammettono esclusivamente messaggi di tipo binario, nel primo caso, o testo, nel secondo. In particolare  l’handler BingoWebSocketHandler utilizzato nella nostra applicazione di esempio estende TextWebSocketHandler, e si presenta quindi nel seguente modo:

Si noti che, come anticipato precedentemente, la classe riceve e pubblica diversi eventi, per tale motivo:

  • implementa l’interfaccia ApplicationListener ed il metodo onApplicationEvent() dove gestisce i due eventi DrawEvent e StartEvent;
  • si inietta la classe ApplicationEventPublisher attraverso la quale pubblica l’evento BingoEvent.

Client Java

E’ interessante notare che Spring utilizza la stessa gerarchia di classi: WebSocketHandlerBinaryWebSocketHandler e TextWebSocketHandler, per la implementazione l’handler di una connessione WebSocket  lato client. Nel caso però di una applicazione Java standard è necessario utilizare un oggetto WebSocketConnectionManager che, a partire dall’handler e da un client WebSocket standard, gestisce la comunicazione su canale.

Implementiamo quindi la classe BingoClient, che estende TextWebSocketHandler, la quale genera una cartella di gioco ad ogni avvio di una nuova estrazione. Quindi verifica i numeri ricevuti dalla WebSocket  fino all’eventuale ottenimento del bingo che comunica tempestivamente al server.

Infine la classe BingoClient espone il metodo main che utilizza il WebSocketConnectionManager per connettere il giocatore al server di gioco:

Esecuzione

Il server di gioco, essendo una applicazione Spring Boot, si avvia con il comando Maven: mvn clean spring-boot:run. Il client è un’applicazione java quindi eseguibile nel modo usuale. Il tabellone è un file html quindi è sufficiente aprirlo sul browser.

Le due immagini seguenti mostrano un round di gioco con due giocatori ed il tablou colorato con i numeri estratti sino al bingo.

bingo player tablou

Il Codice Sorgente

Il codice sorgente del progetto è scaricabile qui jbwsocket.

How useful was this post?

Click on a star to rate it!

Average rating 5 / 5. Vote count: 1

No votes so far! Be the first to rate this post.

As you found this post useful...

Follow us on social media!