Le WebSocket rappresentano una tecnologia che consente una comunicazione bidirezionale e full-duplex tra il client e il server.
In particolare, consente ad applicazioni browser-based di comunicare con un host remoto senza la necessità di aprire connessioni multiple utilizzando AJAX o IFrame, in combinazione con il long polling.

Lato client, le WebSocket sono implementate nella maggior parte dei browser di ultima generazione. La tabella riportata sul sito caniuse.com può essere d’aiuto.
Lato server, esistono diverse implementazioni di WebSocket. In questo articolo prenderemo in considerazione l’implementazione Java fornita da Apache Tomcat 7. Come indicato nella documentazione, l’API potrebbe essere soggetta a cambiamenti.

Nel nostro progetto di esempio simuleremo un feed di prezzi per dei titoli della borsa.
Lato server ci sarà una servlet per stabilire la comunicazione con il client e un simulatore di prezzi che genererà valori pseudorandomici e li manderà, in push, al client.
Lato client si utilizzeranno le WebSocket per aprire il canale di comunicazione, ricevere i prezzi e mostrali a video.

Per utilizzare gli strumenti di Tomcat, dovremo mettere in classpath tre jar:

catalina.jar
servlet-api.jar
tomcat-coyote.jar

che, sotto una classica installazione su Windows, risiedono in TOMCAT_HOME/lib.

L’implementazione delle WebSocket di Tomcat prevede che si crei una servlet che estende la classe astratta WebSocketServlet. La nostra servlet dovrà poi implementare il metodo astratto

protected abstract StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request)

e, di conseguenza, dovremo creare un’estensione di StreamInbound per gestire il canale di comunicazione.
Nel nostro progetto, l’inbound sarà molto semplice, in quanto non deve preoccuparsi dei messaggi ricevuti dal client (in sostanza, solo il server invia messaggi):

package com.stockws;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;

import org.apache.catalina.websocket.MessageInbound;

public class PushInbound extends MessageInbound {
  @Override
  protected void onBinaryMessage(ByteBuffer buff) throws IOException { }

  @Override
  protected void onTextMessage(CharBuffer buff) throws IOException { }
}

Il simulatore dei prezzi è un Runnable che ha come compito quello di generare dei prezzi random, produrre una messaggio (una stringa a che rappresenta un oggetto JSON) e inviarlo al client. Quest’ultima parte si riduce a quest’istruzione (client è un’istanza di PushInbound):

client.getWsOutbound().writeTextMessage(CharBuffer.wrap(message));

Veniamo alla servlet. Il file web.xml ha questa struttura:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <servlet>
    <servlet-name>StockServlet</servlet-name>
    <servlet-class>com.stockws.StockServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>StockServlet</servlet-name>
    <url-pattern>/push</url-pattern>
  </servlet-mapping>
</web-app>

L’implementazione della servlet è questa:

package com.stockws;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;

public class StockServlet extends WebSocketServlet {
  private static final long serialVersionUID = -7169379397366552458L;
  private Simulator sim;

  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    sim = new Simulator();
    new Thread(sim).start();
  }

  @Override
  protected StreamInbound createWebSocketInbound(String subProtocol,HttpServletRequest request) {
    PushInbound client = new PushInbound();
    sim.register(client);
    return client;
  }

  public void destroy() {
    super.destroy();
    sim.stopSim();
  }
}

come si nota, estende WebSocketServlet. Il suo compito è quello di accettare una connessione in ingresso (in corrispondenza di ciò, viene invocato il metodo createWebSocketInbound) e segnalare al simulatore che un nuovo client si è connesso.

Lato client, una web socket viene creata utilizzando l’oggetto WebSocket, il cui ha un costruttore che accetta l’URI (ed eventualmente la versione del protocollo):

window.websocket = new WebSocket("ws://192.168.1.140:8080/stockws/push");

Nel nostro esempio, stockws è la directory dove è installata la webapplication e push è il pattern a cui risponde la nostra servlet.

Successivamente, vanno specificate le funzioni che verranno chiamate in risposta ai vari eventi:

websocket.onopen = function(evt) {
  //
};
websocket.onclose = function(evt) {
  //
};
websocket.onmessage = function(evt) {
  //
};
websocket.onerror = function(evt) {
  //
};

Nel nostro progetto, utilizzeremo le funzioni onopen/onclose per settare lo stato della websocket (ad esempio, per cambiare il campo dello stato a video da Disconnected a Connected) e la funzione onmessage per parsare il messaggio ricevuto (trasformandola in oggetto JSON) e aggiornare la tabella.

Sono stati implementati due front-end. Uno standard, con una tabella e un tasto per connettersi/disconnettersi (index.html):

e un front-end per device mobili, utilizzando JQuery Mobile (mobile.html):

Per scaricare il progetto si possono usare i link in fondo alla pagina.
Per avviare la webapp è necessario scaricare Tomcat 7 e scompattare il file stockws-webapp.7z in TOMCAT_HOME/webapps.
Per modificare il codice del progetto con Eclipse (o con un qualsiasi altro editor) si deve scompattare il file stockws-eclipse.7z ed essere certi di aver messo in classpath i jar citati all’inizio dell’articolo.

Web app
Progetto Eclipse