Pourquoi les Websockets ?

le 15/07/2010 par David Rousselie
Tags: Software Engineering

Après la démocratisation d'Ajax (ie. requêtes HTTP asynchrones en Javascript), plusieurs techniques ont été élaborées afin de permettre le push de données depuis le serveur toujours en utilisant HTTP. C'est grâce à ces techniques que l'on reçoit nos mails dans une application web sans avoir à cliquer sur le bouton "Refresh", que les applications de chat sont possibles sans plugin tierce (Flash, Java, ...), etc. Le W3C et l'IETF ont spécifié une API Javascript et un protocole nommé Websocket. Ce protocole connecté est adapté à l'envoi de données dans les deux sens et évite de détourner HTTP de son usage initial (ie. un protocole déconnecté sans état).

Le protocole Websocket, HTTP et les proxys

Websocket n'est donc pas une spécification des techniques de push sur HTTP (long polling, HTTP Streaming, ...), ni une surcouche à HTTP mais bien un protocole à part entière. Il reste pourtant lié à HTTP en réutilisant l'architecture réseau de celui-ci. En effet, l'ouverture d'une connexion Websocket s'effectue avec une requête HTTP qui demande au serveur "de mettre à jour la connexion" en connexion Websocket.

GET /demo HTTP/1.1 Host: example.com Connection: Upgrade Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 Sec-WebSocket-Protocol: sample Upgrade: WebSocket Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 Origin: http://example.com

^n:ds[4U

Le serveur, s'il supporte le protocole Websocket, peut ainsi terminer l'ouverture de la connexion et la suite du dialogue entre le client et le serveur s'effectuera avec le protocole Websocket.

HTTP/1.1 101 WebSocket Protocol Handshake Upgrade: WebSocket Connection: Upgrade Sec-WebSocket-Origin: http://example.com Sec-WebSocket-Location: ws://example.com/demo Sec-WebSocket-Protocol: sample

8jKS'y:G*Co,Wxa-

Ce type d'ouverture de connexion permet de réutiliser le port HTTP (ou HTTPS) classiquement utilisé et de passer les proxys comme une simple requête HTTP. Cependant, certains proxy n'autorisant pas les connexions longues risquent de poser problème en coupant les connexions Websocket. Avec la démocratisation du protocole Websocket, on peut cependant espérer que ce type de proxy disparaîtra peu à peu. L'utilisation de Websocket sécurisé (wss:// dans l'URL au lieu de ws://) permet d'augmenter les chances de passer à travers ces proxys peu coopératifs. En effet, si un proxy est explicitement configuré dans le navigateur, les implémentations actuelles de Websocket utilisent des tunnels via HTTP CONNECT de la même façon que HTTPS. La connexion n'est ainsi plus différentiable d'une connexion HTTPS du point de vue des proxys.

Le support des Websockets

Le support par les navigateurs

Comme pour les autres fonctionnalités qui viennent avec HTML5, les Websockets ne sont pas supportées par tous les navigateurs :

  • Chrome les supporte depuis sa version 4.0.429
  • Safari depuis la version 5.0

Quant aux autres navigateurs, ils ne les supportent tout simplement pas encore.

Pour pallier à ce manque temporaire, deux approches ont été implémentées :

  • un wrapper Javascript client qui bascule sur une implémentation Flash (qui est capable d'ouvrir des sockets TCP). Cette solution permet de garder tous les avantages des Websockets mais ajoute une dépendance vers le Player Flash,
  • un wrapper Javascript client qui bascule sur du long polling ou HTTP Streaming. Cette fois-ci, plus de dépendance vers le Player Flash mais on revient vers une émulation du push avec HTTP.

Le support côté serveur

Côté serveur, il faut distinguer deux types de composants (dans le monde Java) :

  • les implémentations du protocole Websocket comme Netty, Grizzly, Jetty 7 (l'article de Xavier montre comment utiliser les Websockets avec Jetty 7), JWebSocket (et son implémentation TCP), Kaazing, ... Ces implémentations proposent une API plus (Jetty) ou moins (Kaazing) bas niveau pour exploiter les fonctionnalités du push serveur.
  • les frameworks qui proposent une API de plus haut niveau et qui s'appuient sur les implémentations précédentes. On retrouve ainsi Atmoshpere, Cometd (en version 2) ou encore JWebSocket. Ces frameworks proposent donc une API indépendante de l'implémentation WebSocket sous-jacente (d'ailleurs, Atmosphere était avant tout une implémentation de Comet).

Cette distinction ne veut cependant pas dire que les frameworks implémentant le protocole Websocket sont forcément de plus bas niveau que Atmosphere ou JWebSocket. Par exemple, Kaazing propose des fonctionnalités et une API d'au moins aussi haut niveau que celle de JWebSocket.

Pourquoi (pas) les Websockets ?

Au-dela de pouvoir pousser des données du serveur vers le client, encore faut-il pouvoir l'exploiter sans passer son temps dans la plomberie. C'est bien de pouvoir réagir à une connexion, un message, une déconnexion, ... mais il faut gérer les clients connectés, les timeouts des connexions (le client ne connait pas le timeout après lequel le serveur coupe la connexion), savoir quels messages renvoyer lorsqu'une coupure de connexion survient et autres limitations du fait de la jeunesse du protocole Websocket.

Là encore les frameworks comme Kaazing, JWebsocket ou Cometd sont bien utiles pour s'occuper de ces problématiques bas niveau mais aussi pour proposer des fonctionnalités utilisant tout le potentiel des Websockets.

Parmi celles-ci, on peut trouver :

  • du publish subscribe
  • du RPC (Remote Procedure Call) et RRPC (Reverse Remote Procedure Call) pour que le serveur puisse appeler des services du client
  • des passerelles vers des protocoles connectés comme JMS, JDBC, Jabber, IMAP, ...
  • bus événementiel

Enfin, il est légitime de se dire que toutes ces fonctionnalités sont envisageables avec des techniques comme Comet sur HTTP. Alors pourquoi préférer les Websockets alors que leur support par les navigateurs est encore limité et que les proxys risquent de ne pas toujours jouer le jeu ?

Tout d'abord, lorsqu'on utilise du polling ou tout autre technique sur HTTP, on utilise forcément une connexion dédiée pour recevoir des données poussées du serveur vers le client et une autre connexion pour envoyer des données depuis le client (via une requête HTTP classique). Une connexion Websocket, quant à elle, est full duplex. C'est à dire que la même connexion est utilisée pour recevoir et envoyer des données. Le serveur gère donc moins de connexions ouvertes et moins d'ouvertures et fermetures de connexions ponctuelles. Il faut cependant souligner que les implémentations serveur doivent être adaptées à la gestion d'un grand nombre de connexions simultanées et continues (utilisation d'un pool de threads plutôt qu'un thread par socket ouverte, entrées-sorties non bloquantes, notifications par l'OS de l'activité des sockets, ...), que ce soit en utilisant Comet ou les Websockets.

Ensuite, en terme de bande passante, la taille du message minimum du protocole WebSocket est de 2 octets (0x00 pour le début du message et 0xFF pour la fin). Avec HTTP, il faut compter au moins 200 octets pour un header. Quand il s'agit d'envoyer des messages de maintient de connexions (ou de réouverture de connexion avec Comet), avec un nombre important de clients connectés, l'occupation de la bande passante avec Comet devient non négligeable.

Conclusion

Le protocole Websocket est donc un moyen de répondre au besoin de push de données depuis le serveur qui était jusque-là implémenté en détournant HTTP de son usage prévu initialement. Il permet ainsi d'optimiser l'usage des resources côté serveur et de faire le pont entre une architecture web (ie. centrée autour de HTTP et des navigateurs Internet) et les autres protocoles connectés (JMS, IMAP, Jabber, ...) ouvrant ainsi la porte au web "temp réel".