Accès rapide :
Introduction aux WebSockets
Le côté serveur
Le côté client (HTML+JavaScript)
Les Websockets ont été introduites avec HTML 5. Côté Java, c'est Java EE 7.0 qui a proposé la première API dédiée à ce sujet : cette API est décrite dans la JSR 356. Une Web socket est un canal de communication bidirectionnel entre une page Web et un serveur Web. Cette socket restera ouverture durant l'affichage de la page et permettra notamment d'envisager des notifications « Push » : le client n'a plus à périodiquement soliciter le serveur pour savoir si quelque chose s'est passé. Au contraire le serveur notifie le client dès que cela est nécessaire.
Je vous propose d'introduire l'API des WebSockets via un exemple concret : nous allons chercher à mettre en oeuvre un logiciel de chat de communication. Le côté serveur sera bien entendu codé en Java et le client en HTML5/JavaScript.
Il est a savoir que par défaut une instance Java de WebSocket est produite sur le serveur pour chaque client.
Dans notre exemple, nous préférons n'avoir qu'une unique instance : la classe de WebSocket est donc transformée en un Singleton et le moteur
de WebSocket configuré (attribut configurator
de l'annotation principale) pour passer par la méthode getInstance
.
En Java, l'API des WebSockets repose principalement sur l'utilisation d'annotations.
Ces annotations sont disponibles dans le package javax.websocket
.
Voici le code de notre WebSocket.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
package fr.koor.samples; import java.util.Hashtable; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; @ServerEndpoint(value="/chatroom/{pseudo}", configurator=ChatRoom.EndpointConfigurator.class) public class ChatRoom { private static ChatRoom singleton = new ChatRoom(); private ChatRoom() { } /** * Acquisition de notre unique instance ChatRoom */ public static ChatRoom getInstance() { return ChatRoom.singleton; } /** * On maintient toutes les sessions utilisateurs dans une collection. */ private Hashtable<String, Session> sessions = new Hashtable<>(); /** * Cette méthode est déclenchée à chaque connexion d'un utilisateur. */ @OnOpen public void open(Session session, @PathParam("pseudo") String pseudo ) { sendMessage( "Admin >>> Connection established for " + pseudo ); session.getUserProperties().put( "pseudo", pseudo ); sessions.put( session.getId(), session ); } /** * Cette méthode est déclenchée à chaque déconnexion d'un utilisateur. */ @OnClose public void close(Session session) { String pseudo = (String) session.getUserProperties().get( "pseudo" ); sessions.remove( session.getId() ); sendMessage( "Admin >>> Connection closed for " + pseudo ); } /** * Cette méthode est déclenchée en cas d'erreur de communication. */ @OnError public void onError(Throwable error) { System.out.println( "Error: " + error.getMessage() ); } /** * Cette méthode est déclenchée à chaque réception d'un message utilisateur. */ @OnMessage public void handleMessage(String message, Session session) { String pseudo = (String) session.getUserProperties().get( "pseudo" ); String fullMessage = pseudo + " >>> " + message; sendMessage( fullMessage ); } /** * Une méthode privée, spécifique à notre exemple. * Elle permet l'envoie d'un message aux participants de la discussion. */ private void sendMessage( String fullMessage ) { // Affichage sur la console du server Web. System.out.println( fullMessage ); // On envoie le message à tout le monde. for( Session session : sessions.values() ) { try { session.getBasicRemote().sendText( fullMessage ); } catch( Exception exception ) { System.out.println( "ERROR: cannot send message to " + session.getId() ); } } } /** * Permet de ne pas avoir une instance différente par client. * ChatRoom est donc gérer en "singleton" et le configurateur utilise ce singleton. */ public static class EndpointConfigurator extends ServerEndpointConfig.Configurator { @Override @SuppressWarnings("unchecked") public <T> T getEndpointInstance(Class<T> endpointClass) { return (T) ChatRoom.getInstance(); } } } |
Premièrement, voici une page HTML présentant différents éléments de saisie afin de permettre la visualisation et l'échange de messages. Cette page possède quelques définitions CSS, directement embarquées en elle. Elle charge surtout un fichier JavaScript définissant le code côté client.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <script type="text/javascript" src="Client.js"></script> <style> #history { display: block; width: 500px; height: 300px; } #txtMessage { display: inline-block; width: 300px; } #btnSend { display: inline-block; width: 180px; } #btnClose { display: block; width: 500px; } </style> </head> <body> <textarea id="history" readonly></textarea> <input id="txtMessage" type="text" /> <button id="btnSend">Send message</button> <button id="btnClose">Close connection</button> </body> </html> |
Et maintenant, voici le code Javascript permettant la discussion sur la Web Socket.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
window.addEventListener( "load", function( event ) { let pseudo = prompt( "Veuillez saisir votre pseudo :" ); let ws = new WebSocket( "ws://localhost:8080/WebSocket/chatroom/" + pseudo ); let txtHistory = document.getElementById( "history" ); let txtMessage = document.getElementById( "txtMessage" ); txtMessage.focus(); ws.addEventListener( "open", function( evt ) { console.log( "Connection established" ); }); ws.addEventListener( "message", function( evt ) { let message = evt.data; console.log( "Receive new message: " + message ); txtHistory.value += message + "\n"; }); ws.addEventListener( "close", function( evt ) { console.log( "Connection closed" ); }); let btnSend = document.getElementById( "btnSend" ); btnSend.addEventListener( "click", function( clickEvent ) { ws.send( txtMessage.value ); txtMessage.value = ""; txtMessage.focus(); }); let btnClose = document.getElementById( "btnClose" ); btnClose.addEventListener( "click", function( clickEvent ) { ws.close(); }); }); |
Et voici une capture d'écran observée durant une disussion entre deux illustres inconnus ;-).
Améliorations / Corrections
Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.
Emplacement :
Description des améliorations :