Il paradigma delle web application si basa necessariamente su un sistema di connessioni che siano all‘altezza della responsività richiesta a una applicazione moderna. La sempre maggiore complessità delle web app e il quantitativo sostanzioso di dati che vengono scambiati impongono di risolvere il problema con nuove specifiche. Vediamo in questo articolo a che punto siamo con le WebSocket.
Introduzione
La qualità di servizio delle connessioni a Internet ha tristemente diffuso nel senso comune l’idea che le applicazioni web siano lente, poco responsive, inaffidabili e a tratti inattendibili. Sebbene questa convinzione, per qualche oscura legge di Murphy, venga sempre confermata da cattive pratiche di programmazione o cattiva qualità di connessione, l’estensione delle web app nei più disparati settori è un dato di per se’ eloquente: operando attraverso la rete, le applicazioni web godono di tutti i vantaggi offerti dal mezzo di comunicazione, in primo piano la logistica e il forte disaccoppiamento spaziale, temporale e di carico.
Non essendo il mondo fatto solo di vantaggi, esse ricevono in dote anche tutte le criticità strutturali delle tecnologie utilizzate. All’aumentare della complessità, aumentano anche le aspettative e così i requisiti, fino ad arrivare al punto che le tecnologie in uso non sono più in grado di fornire strumenti capaci di soddisfare queste mutevoli esigenze. Un fattore critico nel quale applicazioni desktop e web si sono sempre differenziate è la responsività.
Le applicazioni odierne risentono, in determinate circostanze, dell’esigenza di poter usfruire di comunicazioni di tipo event-driven che le infrastrutture attuali non sono in grado di offrire se non attraverso particolari espedienti. Le tecnologie attuali non sono in grado di garantire trasmissioni dati che abbiano un certo grado di affidabilità e che siano soggette a una latenza più bassa possibile, in sostanta, abbastanza bassa da poter essere definita, molto sommariamente, tempo reale. Il rispetto di vincoli temporali così stringenti è una esigenza nata in tempi recenti con l’esplosione di nuovi settori di mercato come i giochi online, i social network e applicazioni time-critical, come le applicazioni finanziarie o transazionali.
Il sistema di comunicazione attuale, basato su protocollo HTTP, dopo anni di inaspettata gloria si è, improvvisamente, rivelato in buona parte inadatto e in parte inefficiente a gestire questi particolari scenari: da qui la necessità di nuovo strumento capace di sopperire a questa mancanza. La comunicazione HTTP è stata concepita per funzionare in modalità half duplex, ovvero utilizzando un modello di comunicazione che permette il transito delle informazione in una sola direzione per volta; ogni pacchetto è altresì appesantito da una prolissa intestazione HTTP che non solo è fonte di overhead in termini traffico, ma anche una possibile fonte di latenza. Ovviamente il progetto iniziale prevedeva poche variazioni al classico Request-Response: l’evolversi di diversi modelli, dall’information pushing al modello a eventi, hanno costretto gli sviluppatori ad arrangiarsi e “forzare” il sistema con un po’ inventiva.
La soluzione AJAX
Attualmente, per ottenere qualcosa di vagamente somigliante a una interazione Publish-Subscribe, si utilizzano due tecniche basate sull’ormai onnipresente AJAX: Short Polling e Long Polling.
Lo Short Polling, denominato comunemente AJAX Polling, è la forma più semplice di polling, nel quale il client si incarica di effettuare regolarmente delle chiamate al server, per ottenere dati aggiornati. Convenzionalmente, se non vi sono dati aggiornati, il server risponderà con un messaggio vuoto. Come tutte le declinazioni di polling, questo metodo, sebbene sia il più semplice e il più diffuso, implica uno spreco di risorse computazionali, sia sul server che sul client, e un notevole utilizzo della banda per l’invio di mesaggi request-response il cui contenuto sarà, per la maggior parte, irrilevante (messaggio di risposta vuoto). Se concettualmente possiamo affermare che con questo approccio l’attesa è delegata al client, esiste anche una metodologia di comunicazione denominata polling lungo, nella quale idealmente l’attesa viene delegata invece al server.
Il Long Polling, familiarmente conosciuto come Reverse AJAX, è un modello di comunicazione web nel quale, a fronte di una singola richiesta di inizializzazione, si cerca di mantenere una connessione HTTP costantemente aperta fra client e server. Quando il server dovrà notificare ai client, non farà altro che inviare dei chunk di dati sulla connessione persistente. Nella sua forma più diffusa il Long Polling è implementato mediante l’utilizzo di un IFRAME nascosto, nel quale il client richiede una determinata pagina (azione di subscribe) e il server risponde servendo una pagina HTML a chunk distruibuiti nel tempo. Questa implementazione è nota come Comet, ed è quella che possiamo facilmente incontrare per gestire, ad esempio, gli instant messaging HTTP che si possono trovare sui vari social network. Ogni qualvolta il server desidera inviare un evento, aggiunge un chunk alla pagina Comet contente un blocco
HTML5 WebSocket Echo Test
Lo script
Ci teniamo solo due variabili globali, per non inquinare troppo il global scope, uno che punterà alla nostra WebSocket e uno contenente tutte i metodi che ci servono, in questo caso una funzione di inizializzazione init(), una funzione di setup degli handler setHandler() e una funzione di log per visualizzare i messaggi nella div#output.var websocket; var test = { init: function(){ ... }, setHandler: function(){ ... }, log: function(message){ ... } };
La funzione di log() non è nulla di estremamente evoluto:
... log: function(message){ var p = document.createElement("p"); p.innerHTML = message; document.getElementById("output").appendChild(p); } ...
Creiamo un elemento p, lo riempiamo con la stringa passata come parametro e ne facciamo un append al div#output.
Nella funzione di init() dobbiamo assicurarci che il browser che stiamo utilizzando supporti la tecnologia WebSocket e per far ciò controlliamo che "WebSocket" sia una proprietà di window.
... init: function(){ if("WebSocket" in window){ test.log("WebSocket Supportate :)"); test.setHandler(); }else{ test.log("Spiacente, WebSocket non Supportate"); } }, ...
Avremmo potuto controllare anche con una più tradizionale:
if (window.WebSocket){ ... }
Se il browser ha questa capability, possiamo invocare il setup degli handler, altrimenti logghiamo dispiaciuti.
L'oggetto WebSocket
L'oggetto WebSocket è relativamente semplice da istanziare: ha i due metodi principali, per aprire e chiudere la connessione, una variabile di stato e tre handler che vengono invocati allo scatenarsi del rispettivo evento.
// Connessione a un server var websocket = new WebSocket("ws://example.com:8080/resource"); // handler invocato ad apertura ultimata websocket.onopen = function(evt) { ... }; // handler invocato alla ricezione di un messaggio websocket.onmessage = function(evt) { ... }; // handler invocato in caso di errore websocket.onerror = function(evt) { ... }; // handler invocato sulla chiusura da ambo le parte della socket websocket.onclose = function() { ... }; // metodo di invio dati websocket.send(data); // chiusura della connessione websocket.close(); // stato della socket // 0 == In connessione // 1 == Aperta // 2 == Chiusa var state = websocket.readyState;
Da qui, possiamo costruirci la nostra funzione setHandler() nella quale facciamo il grosso del lavoro:
... setHandler: function(){ //binding delle funzioni ai 3 pulsanti dell'interfaccia document.getElementById("connetti").onclick = function() { var url = document.getElementById("serverUrl").value; try { // Connessione alla socket websocket = new WebSocket(url); test.log("Websocket Status: "+websocket.readyState+ " ( In connessione )"); //setup degli handler websocket.onopen = function(evt) { test.log("Websocket Status: " +websocket.readyState +" ( Connesso a: "+url+")"); }; websocket.onclose = function(evt) { test.log("Websocket Status: " +websocket.readyState +" ( Disconnesso )"); }; websocket.onmessage = function(evt) { test.log("Ricevuto: " + evt.data); }; websocket.onerror = function(evt) { test.log("Errore: " + evt.data); }; } catch(ex) { test.log("Errore: "+ex); } }; document.getElementById("disconnetti").onclick = function() { websocket.close(); }; document.getElementById("invia").onclick = function(){ var messaggio = document.getElementById("messaggio").value; try { websocket.send(messaggio); test.log("Inviato: "+messaggio+" ..."); } catch(ex){ test.log("Errore: "+ex); } }; }, ...
Non ci resta ora che aggiungere l'esecuzione dello script dell'init() all'evento di onload della pagina usando l'addEventListener(), occupandoci anche delle versioni di Internet Explorer < 9 che non supportano questo metodo di binding.
if (window.addEventListener){ window.addEventListener("load", test.init, false); //binding per tutti } else { window.attachEvent( "onload" , test.init); // binding per IE }
Lo script completo
HTML5 WebSocket Echo Test
color: #FFF; background-color: #000;" >
E se le Web Socket non sono disponibili?
Come abbiamo visto, un utilizzo ragionato delle WebSocket non è lineare come ci si potrebbe aspettare e spesso è richiesto allo sviluppatore di implementare obbligatoriamente un set minimo di funzionalità ripetutamente attraverso progetti diversi. Inoltre l'eterogeneità delle piattaforme rischia, in un progetto nel quale è necessario raggiungere una audience relativamente estesa, di rendere la vostra applicazione inacessibile.
In questi casi è abbastanza consigliabile prendere in considerazione anche soluzioni di ripiego nel caso le WebSocket non siano disponibili, come tecnologie di long polling mediate da framework come Atmosphere [6] o framework JavaScript come socket.io [7] che automaticamente rileva le capability del browser e impiega la tecnologia migliore disponibile esponendo comunque un set uniforme di API.
Conclusioni: lo stato dell'arte
Attualmente come abbiamo accennato, la specifica delle WebSocket è in fase di revisione in quanto ancora soggetta a vulnerabilità strutturali, e non è ancora stata esplicitamente delineata tutta una serie di casistiche e di problematiche che potrebbero portare al fallimento della comunicazione. Ad esempio non sono ancora gestite corse critiche nelle quali entrambe gli estremi inviino contemporaneamente grandi flussi di dati, caso che negli attuali browser porta ad un deadlock.
Nei prossimi articoli della serie, subito dopo l‘estate, verranno affrontate le tematiche relative alle interazioni/comportamenti, e gli importanti aspetti legati alle funzioni e ai formati multimediali in HTML5
Riferimenti
[1] Il servizio di echo lato server cui appoggiarsi per provare il codice
http://websocket.org/echo.html
[2] La specifica WebSocket API, ancora in versione draft
http://dev.w3.org/html5/websockets/
[3] Il nuovo protocollo ws
http://www.whatwg.org/specs/web-socket-protocol/
[4] La precedente specifica sul nuovo protocollo websocket
http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
[5] jWebSocket
[6] Il framework Atmosphere per il long polling
[7] Il framework JavaScript socket.io
Consulente IT Freelance in ambito User Experience, Usability e Information Architecture. Laureato in Ingegneria Informatica, mastica Design e Nuove Tecnologie da quando, fin da giovane, ha compreso che il lato client della forza è più rapido, più facile, più seducente. Da allora è impegnato attivamente nel dimostrare ai propri clienti che creare un prodotto funzionale, accessibile, usabile e anche piacevole è, a volte, possibile.