Applicazioni web in tempo reale

Il problema
Le piattaforme web offrono un meccanismo
semplice per offrire servizi a comunità di utenti
◦ Grazie al paradigma client/server che non richiede
l'installazione di software specifico ed offre, in molti casi,
prestazioni sufficienti per le esigenze di computazione
Applicazioni web in tempo reale
Vi sono casi in cui occorre ricevere aggiornamenti
in tempo reale da parte del server
Anno Accademico 2013-14
◦ Lavoro condiviso, giochi, chat, aste on-line, cruscotti per il
monitoraggio di informazioni remote, ...
Il paradigma reuqest/response del protocollo HTTP
è troppo limitativo
◦ Occorre introdurre meccanismi alternativi
Applicazioni Internet
Applicazioni Internet
I possibili approcci
Polling semplice
Storicamente sono stati messi a punto meccanismi
differenti, con vari limiti
◦
◦
◦
◦
I client fanno richiesta di informazioni al server
◦ Questo risponde immediatamente, eventualmente
indicando che non ci sono nuovi dati disponibili
Polling
Long polling (Comet)
Streaming di contenuti
WebSocket
Approccio che non scala al crescere del numero
degli utenti
Tutti condividono il principio che la comunicazione
deve essere iniziata dal lato client
◦ Rispettando il modello architetturale HTTP, con l'eventuale
presenza di proxy/NAT/firewall intermedi
◦ Evitando di richiedere più di due connessioni verso lo
stesso host contemporaneamente
Applicazioni Internet
3
Long polling (1)
Applicazioni Internet
4
Per poter gestire questo tipo di situazioni, lo strato
applicativo sul server mantiene, per ogni possibile client, una
coda di messaggi
◦ Se il server non è in grado di fornire le informazioni
richieste (ad esempio perché non ci sono dati più recenti
di quelli già noti al client) "parcheggia" la richiesta
◦ Quando ci sono informazioni disponibili, utilizza la
richiesta pendente per inviarle
◦ Alla ricezione di una risposta, il client esegue
immediatamente una nuova richiesta
◦ A seguito di eventi a livello applicativo, nuovi messaggi rivolti ad uno
o più client vengono generati lato server ed inseriti nelle
corrispettive code
Quando un client effettua un data richiesta al server, questo
esamina la corrispondente coda e, nel caso, inoltra tutti i
messaggi ad esso destinati, chiudendo la risposta
Se, mentre è in attesa di risposta,, il client deve
inviare dati al server, apre una connessione parallela
◦ Il server può usare l'identificativo di sessione per correlare
tale richiesta a quella pendente
5
Streaming di contenuti
◦ Se la coda è vuota, la comunicazione viene tenuta aperta, in sospeso,
per un dato intervallo di tempo
Se entro il lasso di tempo fissato, si genera un nuovo
messaggio, esso viene inserito nella coda e immediatamente
recapitato usando la connessione in sospeso
◦ Se il tempo scade, la connessione viene chiusa indicando un timeout
Applicazioni Internet
6
Problematiche comuni al long
polling ed allo streaming
Modello simile al long polling, basato sulla codifica
di trasferimento chuncked
◦ I client aprono una connessione verso il server che
configura la risposta nella modalità
Transfer-Encoding: chunked
◦ Tale risposta non viene mai terminata dal server, che si
limita ad inviare, di volta in volta, nuovi chunk effettuando il
flush delle comunicazione
Via via che i dati vengono trasferiti al client questo
può elaborarli
I server web utilizzano, per lo più, un modello di
concorrenza adatto a gestire molte richieste di
breve elaborazione
◦ Allocando un thread estratto da un thread-pool ad ogni
connessione ricevuta o a ciascuna richiesta in esse
contenute
◦ Tale thread rimane impegnato fino a che l'intera risposta
non è stata generata e trasmessa al richiedente
L'utilizzo di richieste che durano a lungo nel tempo
vincola il thread usato per gestire la risposta ad
attendere la chiusura della stessa
◦ Non tutti i browser supportano correttamente tale
meccanismo
Applicazioni Internet
◦ Iniziare una nuova connessione ha un elevato costo in
termini di rete (7 pacchetti IP solo per gestire il ciclo
di vita)
◦ I dati utili sulla connessione sono una frazione minima
(bassa efficienza)
◦ Produce un effetto simile ad un attacco DDOS
Long polling (2)
Meccanismo di richiesta con risposta
potenzialmente ritardata
Applicazioni Internet
2
◦ Portando, rapidamente, all'esaurimento delle risorse
disponibili
7
Applicazioni Internet
8
Servlet asincroni (1)
Servlet asincroni (2)
La specifica JavaEE 6 ha introdotto la possibilità di
dichiarare un servlet in modalità asincrona
Per funzionare in modalità asincrona, la classe
del servlet deve essere annotata con l'attributo
◦ Se, all'arrivo di una richiesta, non ci sono le informazioni
necessarie per generare la risposta, è possibile metterla in attesa,
lasciando libero il thread che la sta servendo di fare altro
◦ asynchSupported=true
All'atto della ricezione di una richiesta, se ne
può sospendere la valutazione invocando il
metodo
La richiesta viene incapsulata in un oggetto di tipo
AsyncContext
◦ Tale oggetto potrà essere utilizzato in seguito per generare la
risposta
◦ AsyncContext ctx = request.startAsync();
◦ L'oggetto "ctx" può essere archiviato in una struttura
dati in attesa delle informazioni da inviare al client
E' possibile fissare anche un tempo limite di
elaborazione oltre il quale la connessione deve essere
chiusa
◦ Registrando un ascoltatore sull'oggetto AsynContext
Applicazioni Internet
Applicazioni Internet
9
Servlet asincroni (3)
10
Implementare la logica
lato server (1)
Gestire richieste asincrone presuppone che il
server disponga di un meccanismo per organizzare
e distribuire i messaggi
@WebServlet(urlPatterns="/test", asyncSupported=true)
public class TestServlet extends HttpServlet {
protected void doPost(HttpServletRequest req,
HttpServletResponse res)
throws IOException, ServletException {
◦ Poiché le richieste possono arrivare da fonti diverse ed
essere elaborate da thread differenti, occorre adottare
politiche di sincronizzazione che evitino le interferenze
senza penalizzare le prestazioni
◦ Occorre in particolare fare attenzione ad evitare lock
globali che serializzano l'accesso da parte di tutti i client
AsyncContext ac = req.startAsync();
ac.addListener(new AsyncListener() {
public void onComplete(AsyncEvent event) throws IOException {
event.getSuppliedResponse()
.getOutputStream()
.print("Complete");
}
public void onError(AsyncEvent event) { }
public void onStartAsync(AsyncEvent event) { }
public void onTimeout(AsyncEvent event) { }
});
queueContextForFurtherProcessing(ac);
Poiché le comunicazioni coinvolgono spesso
sessioni (ed utenti) diverse, occorre gestire tali
strutture dati a livello di servletContext
◦ Iniettando gli opportuni oggetti tramite un
ServletContextListener
}
}
Applicazioni Internet
Applicazioni Internet
11
Implementare la logica
lato server (2)
12
Implementare la logica
lato server (3)
@WebServletContextListener
public class SampleWebService implements ServletContextListener {
public class TestServlet extends HttpServlet {
//..altri metodi
public void contextInitialized(ServletContextEvent sce) {
ExecutorService executor = Executors.newFixedThreadPool(10);
sce.getServletContext().setAttribute("service", executor);
}
private void queueContextForFurtherProcessing(final AsyncContext ac) {
ExecutorService service (ExecutorService) getServletContext()
.getAttribute("service");
service.submit(new Runnable() {
public void run() {
ServletRequest req= ac.getRequest();
public void contextDestroyed(ServletContextEvent sce) {
ExecutorService executor = (ExecutorService)
sce.getServletContext().getAttribute("service");
service.shutdown(); // Disable new tasks from being submitted
try { // Wait a while for existing tasks to terminate
if (! service.awaitTermination(60, TimeUnit.SECONDS))
service.shutdownNow(); // Cancel currently executing tasks
} catch (InterruptedException ie) {
service.shutdownNow();
Thread.currentThread().interrupt();
}
}
//elabora la richiesta
//al termine chiudi la richiesta
ac.complete();
}
});
}
}
}
Applicazioni Internet
13
Diagramma temporale
Client
Executor
Il long polling trasferisce, per ogni connessione,
l'insieme completo di intestazioni per richiesta e
risposta
doPost()
Async
Context
Network
getRequest()
Runnable
submit()
◦ Dando origine ad un livello di efficienza molto limitato
L'eventuale presenza di proxy (trasparenti o meno)
può condizionare l'esito della transazione
run()
◦ Spesso, infatti, i proxy introducono un limite massimo di
attesa, oltre al quale chiudono la connessione
dichiarandola fallita
◦ Per evitare questo, occorre anticipare il timeout lato
server, penalizzando ulteriormente l'efficienza del servizio
complete()
Applicazioni Internet
14
I limiti di questo approccio
TestServlet
Container
Applicazioni Internet
15
Applicazioni Internet
16
Web Socket (1)
WebSocket (2)
Protocollo di comunicazione bidirezioanle
asincrono definito dal documento RFC 6455 (2011)
L'uso di una connessione permanente ottimizza
l'utilizzo delle risorse di rete
◦ Instaurato tra un client ed un server con una richiesta
HTTP/Upgrade
◦ Offre ai client basati su browser un meccanismo
alternativo al long polling
◦ Una volta stabilita tale connessione, non occorre trasferire
intestazioni né indirizzare in modo esplicito la sessione
La comunicazione è full-duplex
◦ I messaggi possono essere mandati in modo indipendente
dal client al server e viceversa
◦ I messaggi possono contenere dati arbitrari, sia testuali che
in formato binario
Il protocollo è basato su una connessione TCP che
viene tenuta aperta per tutta la durata della
sessione di lavoro
◦ Presuppone, lato client, l'adozione di un modello a pagina
singola, all'interno del quale è presento codice JavaScript
che instaura/sovraintende tale connessione
Applicazioni Internet
È possibile instaurare una connessione crittografata
basata sul protocollo TLS
17
WebSocket (3)
Una connessione web socket attraversa due fasi
◦ Pressoché tutti i browser desktop supportano tale protocollo
(InternetExplorer solo dalla versione 10 !!!)
◦ Disponibile su iOS a partire dalla versione 6 del sistema
operativo (parzialmente supportato anche nelle versioni 5.x)
◦ Disponibile per il browser nativo Android solo dalla versione 4.4
(in parte supportato da Chrome, Firefox e OperaMobile nelle
precedenti versioni)
Supporto lato server
◦ Standard per le implementazioni JavaEE7 (JSR356)
◦ Implementazioni proprietarie per Tomcat /Glassfish /...
◦ Moltissimi altri framework basati su JavaScript (Socket.IO), Ruby
(FireHose.IO), Perl (Mojolicio.us,Web::Hippie), ...
19
L'interfaccia applicativa lato client
◦ Occorre dapprima verificarne il supporto da parte del
browser
◦ Si crea un istanza dell'oggetto WebSocket
◦ Si inizia la connessione al server
◦ Si registrano le callback per gli eventi
◦ Si inviano/ricevono i messaggi
◦ Si chiude la connessione
21
Metodi e proprietà dell'oggetto
WebSocket
La negoziazione inizia con una richiesta del client di tipo GET alla
URL del servizio
◦ Indicando l'intestazione "Upgrade: websocket", la versione del protocollo
richiesto (13), una chiave di sessione (Sec-WebSocket-Key)
◦ Il server risponde con lo status-code 101 (Switching protocols) ed include
un'intestazione di tipo Sec-WebSocket-Accept il cui valore è derivato
algoritmicamente dalla chiave inviata nella richiesta
Lo scambio di dati consente l'invio di contenuti testuali (utf-8),
binari e di controllo
◦ Questi ultimi servono per la gestione della chiusura della connessione
I dati scambiati possono avere dimensioni arbitrarie
◦ Il protocollo internamente frammenta e ricombina tali dati per facilitare
l'opera degli eventuali host intermedi e ridurre le dimensioni dei buffer
Applicazioni Internet
20
//verifica la compatibilità del browser
if ( window.WebSocket ) {
// creo la connessione verso l'host
var socket = new WebSocket("ws://echo.websocket.org");
socket.onopen = function (event) {
//azioni da eseguire all'atto della connessione
}
socket.onmessage = function (event) {
var data=event.data;
//azioni da eseguire alla ricezione di un messaggio
}
socket.onclose = function(event) {
//azioni da eseguire alla chiusura del socket
}
socket.onerror = function (event) {
//azioni da eseguire in caso di errore
}
if (socket.readyState===WebSocket.OPEN) socket.send("Hello,WebSocket!");
} else {
alert("WebSocket non supportati");
}
Applicazioni Internet
22
La componente server in Java
In base al contenitore adottato, possono essere
richieste implementazioni differenti
send(data)
◦ Invia il parametro al server
◦ Questo metodo deve essere invocato solo se lo stato della
connessione è OPEN
◦ Le versioni più recenti di Tomcat, Wildfly, Jetty e GlassFish
supportano la specifica JSR 356
◦ In contenitori più vecchi, è possibile fare affidamento su
framework specifici, come Atmosphere
(https://github.com/Atmosphere), che aggregano altre
forme di trasporto
close(code, reason)
◦ Chiude la connessione, eventualmente indicando al server un
valore numerico (code) e la ragione della terminazione (stringa)
url – indica la URL del server
protocol – indica il protocollo adottato dal server
readyState – Indica lo stato della connessione
E' possibile creare implementazioni non basate su
container
◦ OPEN, CLOSED, CONNETTING e CLOSING
bufferedAmount – indica il numero di byte non ancora inviati
binaryType – indica il formato dei dati ricevuti
Applicazioni Internet
◦ La negoziazione iniziale (handshake)
◦ Lo scambio di dati
Accesso lato client
Si instaura una connessione WebSocket
programmaticamente, via JavaScript, secondo i
seguenti passi:
Applicazioni Internet
18
Struttura del protocollo
Supporto lato client
Applicazioni Internet
Applicazioni Internet
23
◦ Netty (http://netty.io)
◦ Play! Framework (www.playframework.com)
◦ Vertx (http://vertx.io)
Applicazioni Internet
24
La specifica JSR356
Inviare e ricevere messaggi (1)
Prevede che le sessioni di lavoro di un websocket
siano gestite da istanze di POJO la cui classe è
preceduta dall'annotazione
La classe Session modella il canale di comunicazione
◦ Un istanza di tale oggetto è sempre passata come
parametro ai metodi che ne riportano gli eventi legati al
ciclo di vita
◦ @ServerEndpoint(value="/relative/url")
◦ Dove "/relative/url" costituisce la URL relativa al server
corrente presso cui il websocket è contattabile
Offre i metodi getBasicRemote() e
getAsyncRemote(), mediante i quali è possibile
interagire con il client remoto
I metodi di tale classe annotati con @OnOpen,
@OnClose, @OnMessage, @OnError
◦
◦
◦
◦
void open(Session s, EndpointConfig cfg)
void close(Session s)
void message(Session s, String msg)
void error(Session s, Throwable t)
Applicazioni Internet
◦ Il primo offre un meccanismo di comunicazione bloccante,
il secondo offre metodi che restituiscono oggetti di tipo
Future che si avverano nel momento in cui il messaggio è
stato trasmesso
25
Inviare e ricevere messaggi (2)
Applicazioni Internet
26
Gestire lo stato di una connessione
La ricezione dei messaggi è regolata dalle
annotazioni poste sui metodi della classe che
implementa il web socket
Ogni volta che si instaura una nuova
connessione, il contenitore crea una nuova
istanza della classe annotata come endpoint
◦ Ci possono essere al massimo tre metodi con
l'annotazione @OnMessage, distinti in base ai
parametri ricevuti
◦ Si possono utilizzare gli attributi di tale classe per
memorizzare informazioni legate allo stato della
connessione
@ServerEndpoint("/mutlipleReceivers")
public class Receiver {
L'oggetto Session offre anche una mappa
(String->Object), accessibile tramite il metodo
getUserProperties(), in cui si possono salvare
ulteriori informazioni
@OnMessage
public void textMessage(Session session, String msg) {}
@OnMessage
public void binaryMessage(Session session, ByteBuffer msg) {}
@OnMessage
public void pongMessage(Session session, PongMessage msg) {}
}
Applicazioni Internet
27
Comunicare con tutti i client
connessi
28
Interagire con il server http
Sebbene il ciclo di vita di un web socket sia
sostanzialmente indipendente da quello della
pagina che ne ospita la componente client, è
possibile creare un punto di contatto nel
momento in cui si instaura la comunicazione
Gli oggetti di classe Session offrono anche
accesso a tutte le sessioni attualmente in corso
◦ Si può sfruttare questa informazione per gestire
comunicazioni di gruppo, come chat e aste on line
@ServerEndpoint("/echoall")
public class EchoAllEndpoint {
@OnMessage
public void onMessage(Session session, String msg) {
try {
for (Session sess : session.getOpenSessions()) {
if (sess.isOpen())
sess.getBasicRemote().sendText(msg);
}
} catch (IOException e) { ... }
}
}
Applicazioni Internet
Applicazioni Internet
◦ Il parametro di tipo EndpointConfig permette di
accedere all'interfaccia HandshakeRequest attraverso
la quale si può richiedere l'oggetto HttpSession o le
intestazioni ed i parametri della richiesta di apertura
@OnOpen
public void onOpen(Session s, EndpointConfig cfg) {
HandshakeRequest req=(HandshakeRequest)cfg.getUserProperties()
.get("handshakereq");
HttpSession session=(HttpSession)req.getHttpSession();
}
29
Applicazioni Internet
30