Jetty & HTML 5 : les WebSockets sont là !

le 09/01/2010 par Xavier Vaccari
Tags: Software Engineering

HTML 5 arrive peu à peu dans nos navigateurs et cherche à standardiser certains aspects du Web : vidéo, offline… et push serveur. Autrement formulé, la possibilité de faire des communications bi-directionnelles entre un navigateur et un serveur.

Il existe plusieurs manières de monter une architecture push mais HTML 5 définit les WebSocket dont le principe est simple : le navigateur maintient un lien permanent avec le serveur (grâce à une WebSocket). On parle alors de long polling request (Comet).

Les WebSocket standardisent les API JavaScript mises à disposition par le navigateur pour rendre disponible le push. Ainsi HTML 5 définit un objet de type WebSocket sur lequel on définit trois callbacks que sont, bien évidemment, onopen, onmessage, onclose.

La boîte à outil du push

Côté client, on s'appuiera sur des implémentations d'HTML 5 des navigateurs comme Google Chrome.

Côté serveur, la dernière version de Jetty (7.0.1.v20091125) propose une implémentation des servlets capable de gérer les WebSocket mais on peut noter que la norme Servlet 3 amènera son lot d'évolutions sur cet aspect en incluant un mécanisme d'exécution asynchrone (défini par annotation sur la classe d'implémentation de la servlet)

Voici un exemple (avec Jetty) pour s'en convaincre: une interface de chat permettant à plusieurs utilisateurs d'envoyer des messages aux autres participants.

Configuration serveur

Si vous utilisez Maven, un module dédié est à intégrer pour bénéficier des WebSocket:

org.eclipse.jetty
			jetty-websocket
			${jetty.version}

Le code serveur se représente sous la forme de servlets. La déclaration se fait dans le descripteur de déploiement (web.xml) de votre webapp.

Notre servlet de chat doit hériter d'une classe WebSocketServlet fournie par Jetty. Cette classe diffère d'une servlet "classique" par la présence d'une méthode invoquée à l'ouverture de la connexion:

protected WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)

Cette méthode permet de créer l'objet WebSocket qui sera le pivot d'échange entre le code JavaScript et le code serveur. Dans le cas du chat, le message reçu par un des membres connecté devra être envoyé à l'ensemble des utilisateurs du chat:

protected WebSocket doWebSocketConnect(HttpServletRequest request,
			String protocol) {
		return new ChatWebSocket();
	}
	class ChatWebSocket implements WebSocket {
		private Outbound channel;

		public void onConnect(Outbound outbound) {
			channel = outbound;
			membersWS.add(this);
			LOG.info("New chat client connected");
		}
		public void onMessage(byte frame, byte[] data, int offset, int length) {
			//skipped
		}
		public void onMessage(byte frame, String data) {
			for (ChatWebSocket memberWS : membersWS) {
				try {
					memberWS.channel.sendMessage(frame, data);
				} catch (IOException e) {
					LOG.error("Erro when sending chat message",e);
				}
			}
		}
		public void onDisconnect() {
			membersWS.remove(this);
		}
	}

Configuration client

Dans Google Chrome, pas besoin de framework JavaScript pour coder notre chat. Il reste cependant à utiliser les API fournies par le navigateur. Dans l'exemple ci-dessous, un objet de type WebSocket est créé et les callbacks implémentées

var room = {
        join: function(name) {
          this._username=name;
          var location = "ws://localhost:8080/chat/chat";
          this._ws=new WebSocket(location);
          this._ws.onopen=this._onopen;
          this._ws.onmessage=this._onmessage;
          this._ws.onclose=this._onclose;
        },
            
        send: function(user,message){
          ...
          this._ws.send(user+':'+message);
        },
              
        _onmessage: function(m) {
          if (m.data){
            var c=m.data.indexOf(':');
            var from=m.data.substring(0,c).replace('< ','<').replace('>','>');
            var text=m.data.substring(c+1).replace('< ','<').replace('>','>');
            
            //ajoute les différents éléments à la page HTML
            elementHTML.innerHTML=text;
            ...
          }
        },
...
        
      };

Enfin, on définit l'interaction utilisateur

$('sendB').onclick = function(event) { 
	room.send(//passe le texte saisi par l'utilisateur dans le formulaire ); 
	...
};

Le push est donc de plus en plus accessible. D'autres évolutions sont à prévoir notamment pour contourner la limitation de la Same Origin Policy. Des spécifications W3C sont en cours et ont pour but de pouvoir effectuer des appels Ajax cross domain. Jetty quant à lui propose des implémentations de ce draft.

Sources