mar 19 marzo 2024 - Lo Sviluppatore  anno VI

I WebSocket : comunicazione asincrona full-duplex per il web

Condividi

E’ un fatto ben noto che l’ HTTP (Hypertext Transfer Protocol) è un protocollo di tipo request-response senza stato (stateless). La semplicità del protocollo HTTP lo rende molto scalabile, ma inefficiente e non adatto per le applicazioni web altamente interattive o in ​​tempo reale, oggi sempre più diffuse. D’altronde l’ HTTP è stato progettato per la condivisione di documenti e non per la creazione di questo tipo di applicazioni.

Prima della versione 1.1 del protocollo, ogni richiesta al server comportava una nuova connessione mentre dalla versione 1.1 la cosa è stata migliorata con l’introduzione delle connessioni persistenti che permettono al  browser web di riutilizzare la stessa connessione per il caricamento di  immagini, script, ecc

L’HTTP, inoltre,  è stato progettato per essere half-duplex il che significa che la trasmissione dei dati è consentita in una sola direzione alla volta . Un walkie-talkie è un esempio di un dispositivo half-duplex dove una sola una persona alla volta può parlare. Per superare queste lacune dell’HTTP gli sviluppatori hanno creato alcune soluzioni o hack  tra questi il polling , il long polling (aka comet), e lo streaming .

Con il polling, il client effettua chiamate sincrone per ottenere informazioni dal server . Se il server dispone di nuove informazioni disponibili invierà i dati nella risposta . In caso contrario , nessuna informazione verrà inviata al client e il client farà una nuova connessione dopo un certo lasso di tempo per riverificare la disponibilità di nuovi dati. Questo meccanismo è molto inefficiente ma è un modo molto semplice per ottenere un comportamento simil real-time . Il long polling invece è un’altra soluzione in cui il client effettua una connessione al server e il server mantine aperta la connessione fino a quando i dati sono disponibili oppure viene raggiunto un prederminato timeout. A causa dello squilibrio tra HTTP sincrono e queste applicazioni asincrone, anche questa soluzione tende ad essere complicata , non standard e inefficiente .

Per colmare questa sempre maggiore esigenza delle applicazioni web di poter effettuare comunicazioni bidirezionali in maniera standard è stato introdotto il concetto di WebSocket. Ogni linguaggio di programmazione ha la propria implementazione, in questo articolo, vedremo in particolare l’implementazione nella piattaforma java (JSR 356 API) e un client in javascript.

Prima di vedere l’utilizzo pratico dei WebSockets è bene prima capire cosa sono e come funzionano.

Cosa sono i WebSocket?

I WebSocket sono un protocollo di messaggistica che permette una comunicazione asincrona e full-duplex su connessione TCP. I WebSockets non sono connessioni HTTP anche se usano l’HTTP per avviare la connessione.

Un sistema full-duplex permette la comunicazione in entrambe le direzioni in maniera contemporanea, tipico esempio di questo tipo di comunicazione è quella telefonica dove gli interlocutori possono  parlare ed essere ascoltati allo stesso tempo. I WebSocket sono stati inizialmente proposti come parte della specifica HTML5 , che promette di portare la facilità di sviluppo e di efficienza della rete alle applicazioni web moderne , interattive , ma è stato successivamente spostato in un documento standard separato per mantenere la specifica focalizzata solo su WebSocket (RFC 6455 e WebSocket API JavaScript).

 

Come funziona un WebSocket ?

Ogni connessione WebSocket inizia la sua vita come una richiesta HTTP. Tale richiesta HTTP è molto simile a tutte le altre richieste salvo che nell’intestazione viene specificata un operazione di tipo Upgrade che indica che il client vuole aggiornare la connessione ad un protocollo diverso, in questo caso a WebSocket. Ad aggiornamento avvenuto viene stabilita la connessione WebSocket tra client e server sfruttando la stessa connessione sottostante usata durante la fase iniziale della comunicazione (handshake) e la comunicazione in entrambe le direzioni può cominciare.

Sotto un esempio di handshake lato client:

GET /path/to/websocket/endpoint HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://localhost
Sec-WebSocket-Version: 13

mentre, lato server, in risposta ad una richiesta del tipo visto sopra, abbiamo una cosa del genere:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

Il server applica una operazione nota (sia al cliente che al server) al valore della chiave SecWebSocketKey presente nell header della chiamata per generare il valore della SecWebSocketAccept . Il client fa la stessa operazione sempre sul valore della stessa chiave SecWebSocketKeye, se il valore ricevuto dal server coincide con il valore calcolato dal client la connessione può considerarsi come stabilita con successo e client e server possono cominciare a comunicare. Tramite i WS è possibile scambiare sia messaggi di testo (codificati come UTF8) sia messaggi in formato binario.

Un end-pint WebSocket è rappresentato da URI nel seguetne formato:

ws://host:port/path?query
wss://host:port/path?query

Lo schema ws rappresenta le comunicazioni in chiaro mentre lo schema wss rapresenta le comunicazioni criptate. La porta è un dato facoltativo, si consideri che per le comunicazioni in chiaro si usa la porta 80 mentre per le comunicazione criptate la 443, analogamente al protocollo l’http. La componente path rappresenta il path del server dove si trova l’end-point e la parte query è opzionale.

I browser moderni implementano il protocollo WebSocket e forniscono una API JavaScript per la connessione agli endpoint, inviare messaggi, e assegnare i metodi di callback per gli eventi websocket (quali: connessioni aperte, messaggi ricevute, connessioni chiuse, ecc.).

Per avere informazioni sulla situazione di compatibilità dei vari browser seguire il seguente link:

http://caniuse.com/#feat=websockets

Vantaggi nell’uso dei  WebSockets

  1. II WebSocket sono più efficienti e performanti rispetto ad altre soluzioni , come il polling .

  2. Richiedono meno banda e riducono la latenza .

  3. Semplificano le architetture applicative in real-time

  4. I WebSocket non richiedono intestazione per inviare messaggi riducendo quindi la larghezza di banda richiesta .

 

A cosa mi possono servire i WebSocket?

Alcuni dei possibili casi d’uso di WebSocket sono :

  •    applicazioni di chat
  •    giochi multiplayer
  •    Stock trading o applicazioni finanziarie
  •    Editing cooperativo di documenti
  •    Applicazioni di social networking

 

WebSockets in Java

La JSR 356 è la specifica di riferimento dei WebSocket per la piattaforma Java che permette di creare, configurare e distribuire endpoint Websocket in una web application, nonchè implementare client remoti per poter accedere a tali endpoint da qualsiasi applicazione java. L’API Java per WebSocket si compone dei seguenti package.

  • javax.websocket.server che contiene le annotazioni, le classi e le interfacce per creare e configurare gli endpoint del server.
  •  javax.websocket che contiene annotazioni, classi, interfacce, e le eccezioni che sono comuni a client e server endpoint.

Gli Endpoint WebSocket sono istanze della classe javax.websocket.Endpoint. L’API Java per WebSocket consente di creare gli endpoint sia in maniera programmatica sia mediante l’uso di annotation. Per creare un endpoint in maniera programmatica, si estende la classe Endpoint e si fa l’override dei vari metodi che gestiscono il ciclo di vita di un serve websocket. Per creare un endpoint mediante annotazioni, basta decorare una classe Java e alcuni dei suoi metodi con le annotazioni specifiche. Dopo aver creato l’endpoint, questo viene distribuito esponendo un URI specifico che i client remoti possono utilizzare per connettersi ad esso. L’API WebSocket è una libreria puramente event driven .

Un esempio

Le comunicazioni via websocket sono naturalmente indipendenti dalle varie implementazioni e consente la comunicazione tra entità etereogenee implementate in un qualsiasi linguaggio di programmazione. Come esempio vogliamo mostare un caso del genere in cui abbiamo un server endpoint implemetato in java e un client endpoint implementato in javascript che permette di inviare e ricevere mesasggi da una comunissima pagina html.

WebSocket Java Server Endpoint

package myfirstws;

import java.io.IOException;
 
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
 
/** 
 * @ServerEndpoint da un nome all'end point
 * Questo può essere acceduto via ws://localhost:8080/myfirstws/echo
 * "localhost" è l'indirizzo dell'host dove è deployato il server ws,
 * "myfirstws" è il nome del package
 * ed "echo" è l'indirizzo specifico di questo endpoint
 */
@ServerEndpoint("/echo") 
public class EchoServer {
    /**
     * @OnOpen questo metodo ci permette di intercettare la creazione di una nuova sessione.
     * La classe session permette di inviare messaggi ai client connessi.
     * Nel metodo onOpen, faremo sapere all'utente che le operazioni di handskake 
     * sono state completate con successo ed è quindi possibile iniziare le comunicazioni.
     */
    @OnOpen
    public void onOpen(Session session){
        System.out.println(session.getId() + " ha aperto una connessione"); 
        try {
            session.getBasicRemote().sendText("Connessione Stabilita!");
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
 
    /**
     * Quando un client invia un messaggio al server questo metodo intercetterà tale messaggio
     * e compierà le azioni di conseguenza. In questo caso l'azione è rimandare una eco del messaggi indietro.
     */
    @OnMessage
    public void onMessage(String message, Session session){
        System.out.println("Ricevuto messaggio da: " + session.getId() + ": " + message);
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
 
    /**
     * Metodo che intercetta la chiusura di una connessine da parte di un client
     * 
     * Nota: non si possono inviare messaggi al client da questo metodo
     */
    @OnClose
    public void onClose(Session session){
        System.out.println("Session " +session.getId()+" terminata");
    }
}

 WebSocket Javascript client

<!DOCTYPE html>
    <html>
        <head>
            <title>Echo Web Socket</title>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width">
        </head>
        <body> 
            <div>
                <input type="text" id="messageinput"/>
            </div>
            <div>
                <button type="button" onclick="openSocket();" >Open</button>
                <button type="button" onclick="send();" >Send</button>
                <button type="button" onclick="closeSocket();" >Close</button>
            </div>
            <!-- la risposta del server viene scritta quì -->
            <div id="messages"></div>
           
            <!-- Script che utilizza i WebSocket -->
            <script type="text/javascript">
                           
                var webSocket;
                var messages = document.getElementById("messages");
               
               
                function openSocket(){
                    // Assicura che sia aperta un unica connessione
                    if(webSocket !== undefined && webSocket.readyState !== WebSocket.CLOSED){
                       writeResponse("Connesione WebSocket già stabilita");
                        return;
                    }
                    // Creiamo una nuova istanza websocket
                    webSocket = new WebSocket("ws://localhost:8080/EchoChamber/echo");
                     
                    /**
                     * Facciamo il bind delle funzioni con gli eventi dei websocket
                     */
                    webSocket.onopen = function(event){
                       // quando viene aperta la connession inviamo un messagio di OK
                       // al server
                        // scrivo il messagio nella textbox e lo invio
                        writeResponse("OK");
                        send();
                    };
     
                    webSocket.onmessage = function(event){
                        writeResponse(event.data);
                    };
     
                    webSocket.onclose = function(event){
                        writeResponse("Connection closed");
                    };
                }
               
                /**
                 * Invia il contenuto della text input al server
                 */
                function send(){
                    var text = document.getElementById("messageinput").value;
                    webSocket.send(text);
                }
               
                function closeSocket(){
                    webSocket.close();
                }
     
                function writeResponse(text){
                    messages.innerHTML += "<br/>" + text;
                }
               
            </script>
           
        </body>
    </html>

 Conclusioni

Abiamo visto come i websocket ci possono venire in aiuto quando abbiamo l’esigenza di creare applicazioni in realtime che si devono cambiare messagi. Per chi volesse approfondire segnalo alcune risorse utili sull’argomento:

 

Lascia un commento

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.

Top