Share
Embed
Streaming HTTP : mise en oeuvre avec le langage Java
Ce support de cours présente les bibliothèques Tyrus et JAX-RS pour développer en Java la partie serveur et cliente de WebSocket et de Server-Sent Event. Pour les deux technologies un plan similaire sera suivi : généralités, développement serveur, développement de la couche cliente et problématiques de déploiement.
Mickael BARON
April 19, 2022
Transcript
99 slides
-
SOA – Streaming et Messages Mickaël BARON – Mai 2020
(Rev. Novembre 2025) mailto:[email protected] ou mailto:[email protected] mickael-baron.fr mickaelbaron mickael-baron.fr mickaelbaron Mise en œuvre -
Streaming et Messages - Mise en œuvre - M. Baron
- Page 2 Creative Commons Contrat Paternité Partage des Conditions Initiales à l'Identique 2.0 France creativecommons.org/licenses/by-sa/2.0/fr Licence -
Streaming et Messages - Mise en œuvre - M. Baron
- Page 3 À propos de l’auteur … Mickaël BARON Ingénieur de Recherche au LIAS https://www.lias-lab.fr Equipe : Ingénierie des Données et des Modèles Responsable des plateformes logicielles, « coach » technique Ancien responsable Java de Developpez.com (2011-2021) Communauté Francophone dédiée au développement informatique https://java.developpez.com 4 millions de visiteurs uniques et 12 millions de pages vues par mois 750 00 membres, 2000 forums et jusqu'à 5000 messages par jour mickael-baron.fr mickaelbaron -
Streaming et Messages - Mise en œuvre - M. Baron
- Page 4 Plan du cours Technologies Push avec le langage Java WebSocket (Tyrus) SSE (JAX-RS) gRPC (io.grpc) WebRTC (webrtc-java) Technologie basée sur l’envoi de messages RabbitMQ (AMQP) -
Streaming et Messages - Mise en œuvre - M. Baron
- Page 5 Déroulement du cours Pédagogie du cours Des bulles d’aide tout au long du cours Survol des principaux concepts en évitant une présentation exhaustive Logiciels utilisés Navigateur Web Pré-requis JAX-RS Exemples WebSocket : github.com/mickaelbaron/ws-examples SSE : github.com/mickaelbaron/sse-examples -
Streaming et Messages - Mise en œuvre - M. Baron
- Page 6 Ressources : liens sur le Web WebSocket www.baeldung.com/java-websockets github.com/eugenp/tutorials/tree/master/java-websocket fr.slideshare.net/arungupta1/nuts-and-bolts-of-websocket-devoxx-2014 github.com/arun-gupta/nuts-and-bolts-of-websocket fr.slideshare.net/ouadielahdioui/realtime-applications-avec-la-spcification-java-jsr-356-et-le-protocole- websocket-rfc-6455 Server-Sent Event www.baeldung.com/java-ee-jax-rs-sse eclipse-ee4j.github.io/jersey.github.io/documentation/latest/sse.html RabbitMQ www.rabbitmq.com/getstarted.html www.baeldung.com/rabbitmq www.cloudamqp.com/blog/index.html -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Version Java supportée par ce support de cours 7 q Pas de gestion des modules dans les exemples (utilisation du classpath) -
SOA – Streaming et Messages Mickaël BARON – Mai 2020
(Rev. Novembre 2025) mailto:[email protected] ou mailto:[email protected] mickael-baron.fr mickaelbaron mickael-baron.fr mickaelbaron WebSocket avec Java (Tyrus) -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Plan du cours : WebSocket avec Java (Tyrus) 9 Généralités WebSocket avec Java Premier WebSocket avec Java et Tyrus Développement de WebSocket (vue serveur) Cycle de vie Envoyer et recevoir des messages Personnaliser les messages Développement de WebSocket (vue client) Déploiement -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités WebSocket avec Java 10 Ce cours s’intéresse au développement de WebSocket en différenciant Côté serveur : publie un WebSocket qui est en attente de connexions Côté client : initialise une connexion vers un WebSocket La majorité des langages de programmation orientés web supportent le développement de WebSocket Java, JavaScript, PHP, C#, Python… Ce cours se limite au langage Java Différents frameworks de développement de services web Ceux qui respectent la spécification Jakarta (détailler après) Autres … -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités WebSocket avec Java : la spécification 11 Cette spécification décrit la mise en œuvre de WebSocket côté client et serveur Le développement de WebSocket repose sur l’utilisation de classes Java Soit en utilisant des annotations Soit en utilisant une approche API (~ héritage de classes) Version courante est la 2.x WebSocket : jakarta.ee/specifications/websocket Code source API : github.com/jakartaee/websocket Site projet : projects.eclipse.org/projects/ee4j.websocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités WebSocket avec Java : l’implémentation 12 Tyrus : implémentation de référence fournie par Glassfish Site projet : eclipse-ee4j.github.io/tyrus Tomcat : serveur web de la fondation Apache Site projet : tomcat.apache.org Doc : tomcat.apache.org/tomcat-10.0-doc/web-socket-howto.html Jetty : serveur web de la fondation Eclipse Site projet : www.eclipse.org/jetty Doc : www.eclipse.org/jetty/documentation/jetty-11/programming- guide/index.html#pg-server-websocket Undertow : serveur web derrière Wildfly (Redhat) Site projet : undertow.io Doc : undertow.io/undertow-docs/undertow-docs-2.1.0/index.html#websockets -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités WebSocket avec Java : l’implémentation 13 Dans la suite de ce support de cours nous présenterons l’implémentation de référence Tyrus Respectant la spécification JSR 356 et Jakarta WebSocket Implémentation intégrée dans le serveur d’application Glassfish Fournit des connecteurs pour s’intégrer avec d’autres serveurs d’application Site projet : eclipse-ee4j.github.io/tyrus Code source : github.com/eclipse-ee4j/tyrus -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités WebSocket avec Tyrus : Maven 14 Dépendance pour l’API WebSocket Dépendance pour les besoins API serveur Dépendance pour les besoins API client <dependency> <groupId>jakarta.websocket</groupId> <artifactId>jakarta.websocket-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> Nécessaire pour l’accès à l’API (pour la compilation) <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-server</artifactId> <version>${tyrus.version}</version> </dependency> <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-client</artifactId> <version>${tyrus.version}</version> </dependency> Différentes implémentations existent (voir section Cliente et Déploiement) -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités WebSocket avec Tyrus : développement 15 Le développement de WebSocket est basé sur l’utilisation de classes Java En utilisant des annotations spécifiques En héritant de la classe Endpoint Quand utiliser Annotation ou héritage ? Annotation pour séparer le code spécifique pour le WebSocket Héritage pour s’intégrer dans un code existant Pas de description requise dans des fichiers de configuration Pas de configuration spécifique pour le déploiement (dernière partie du cours) Un WebSocket est déployé dans une application web -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Plan du cours : WebSocket avec Java (Tyrus) 16 Généralités WebSocket avec Java Premier WebSocket avec Java et Tyrus Développement de WebSocket (vue serveur) Cycle de vie Envoyer et recevoir des messages Personnaliser les messages Développement de WebSocket (vue client) Déploiement -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Premier WebSocket avec Java et Tyrus 17 Exemple : reçoit un message et diffuse à tous les clients @ServerEndpoint(value = "/hello") public class HelloworldEndpoint { @OnMessage public void onMessage(String message, Session session) { for (Session currentSession : session.getOpenSessions()) { currentSession.getBasicRemote().sendText( System.currentTimeMillis() + ":" + message); } } } HelloWorldEndpoint.java du projet ws-helloworldwebsocket Méthode invoquée à chaque fois qu’un message est envoyé à ce WebSocket serveur Envoi un message à tous les clients WebSocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Premier WebSocket avec Java et Tyrus 18 Exemple (suite) : reçoit un message et diffuse à tous les clients var ws; function connect() { var host = document.location.host; var pathname = document.location.pathname; ws = new WebSocket(document.getElementById("wsURI").value); ws.onmessage = function (evt) { console.log(evt); writeLog(evt.data); }; ... } function send() { ws.send(document.getElementById("wsMessage").value); } websocket.js du projet ws-helloworldwebsocket Fonction invoquée à chaque fois qu’un message est publié par le WebSocket serveur -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Exemple file rouge : le classique « Chat » 19 Utilisation d’un exemple représentatif pour la présentation des subtilités techniques de l’API WebSocket Un utilisateur précise un pseudo et nom de salon Un utilisateur ne peut pas se connecter plusieurs fois (refus de connexion) Le WebSocket (côté serveur) publie le message d’un utilisateur à tous les utilisateurs d’un salon Les messages peuvent être textuels (texte), binaires (des images) ou personnalisés (ChatMessage) Le format du message String peuvent être « sans » ou JSON -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : généralités 20 Une classe Java doit être annotée avec @ServerEndpoint pour être considérée comme un WebSocket (serveur) Chaque connexion vers un WebSocket crée une instance unique de la classe annotée @ServerEndpoint (autant d’instance que de connexion) Cette instance existe pendant toute la durée de vie de la connexion du WebSocket En d’autres termes : s’il existe n connexions (soit n client) alors n instances d’une classe @ServerEndPoint devront être créées -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : définir l’URI 21 L’annotation @ServerEndpoint permet de renseigner l’URI d’accès au WebSocket sur le serveur (paramètre value) La valeur donnée à value correspond à une expression URI relative au contexte de l’application web Possibilité également d’utiliser des paramètres de requêtes /hello?param1=value1¶m2=value2 @ServerEndpoint(value = "/hello") public class HelloworldEndpoint { ... } ws://localhost:8025/helloworldwebsocket/hello Protocole pour WebSocket ws ou wss Port Contexte de l'application WEB URI du WebSocket Adresse du serveur HelloworldEndpoint.java du projet ws-helloworldwebsocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : définir l’URI 22 La valeur définie de value dans @ServerEndpoint ne se limite pas seulement aux expressions constantes Possibilité de définir des expressions complexes appelées URI-template (= Template Parameters de JAX-RS) Pour distinguer une expression complexe dans value son contenu est délimité par { … } Possibilité également de mixer dans la valeur de value des expressions constantes et des expressions complexes Exemple : @ServerEndpoint(value = "/chat/{chatroom}/{username}") public class ChatEndpoint { ... } ChatEndpoint.java du projet ws-chatwebsocket ws://localhost:8025/chatwebsocket/chat/java/mbaron -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Plan du cours : WebSocket avec Java (Tyrus) 23 Généralités WebSocket avec Java Premier WebSocket avec Java et Tyrus Développement de WebSocket (vue serveur) Cycle de vie Envoyer et recevoir des messages Personnaliser les messages Développement de WebSocket (vue client) Déploiement -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : cycle de vie 24 L’API WebSocket fournit quatre annotations pour gérer le cycle de vie d’un WebSocket @OnOpen : à la création de la connexion @OnMessage : lors de la réception de message @OnError : si une erreur se produit @OnClose : à la fermeture de la connexion Ces annotations sont rattachées à des méthodes Java -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : cycle de vie 25 En fonction des annotations utilisées, différents paramètres sont disponibles (pas forcément les mêmes) Pour récupérer les valeurs des URI-template (définis au niveau de @ServerEndpoint) utilisation de 0 à n paramètres de type String annotés avec @PathParam @ServerEndpoint(value = "/chat/{chatroom}/{username}") public class ChatEndpoint { @OnOpen public void onOpen(Session session, @PathParam("chatroom") String chatRoom, @PathParam("username") String userName) { ... } ChatEndpoint.java du projet ws-chatwebsocket Récupération des valeurs de chatroom et username via l’annotation @PathParam -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : cycle de vie - @OnOpen 26 La méthode annotée @OnOpen est invoquée à chaque fois qu’une connexion à un WebSocket est réalisée Paramètres autorisés (facultatif) Session : décrit la conversation entre le WebSocket client et serveur (sera détaillée plus tard) EndpointConfig : les informations définies lors de la définition du WebSocket serveur (URI, encodeur, décodeur…) Comment paramétrer une connexion ? Pas de possibilité de transmettre des messages lors de la connexion Via l’utilisation des paramètres de chemin (@PathParam) Via l’utilisation des paramètres de requête -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : cycle de vie - @OnOpen 27 Exemple : création de la connexion @ServerEndpoint(value = "/chat/{chatroom}/{username}") public class ChatEndpoint { // Key = username private static Map<String, Session> allSessions = new HashMap<>(); // Key = session id private static Map<String, String> allUsers = new HashMap<>(); // Key = session id private static Map<String, String> allChatRooms = new HashMap<>(); @OnOpen public void onOpen(Session session, @PathParam("chatroom") String chatRoom, @PathParam("username") String userName) throws IOException { if (allSessions.containsKey(userName)) { session.close(); } else { allChatRooms.put(session.getId(), chatRoom); allUsers.put(session.getId(), userName); allSessions.put(userName, session); this.broadcast(userName + " connected!", session, chatRoom); } } } ChatEndpoint.java du projet ws-chatwebsocket Récupération des valeurs des URI-templates Toutes les autres connexions du même salon recevront un message indiquant qu’un nouvel utilisateur s’est connecté -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : cycle de vie - @OnMessage 28 La méthode annotée @OnMessage est invoquée à chaque fois que le WebSocket serveur reçoit des messages Selon le type du message (String, Byte[], classe Java) les paramètres de la méthode annotée peuvent être différents Paramètres autorisés Session : décrit la conversation entre le WebSocket client et serveur String ou Byte[] ou classe Java : le contenu du message reçu -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : cycle de vie - @OnMessage 29 La méthode annotée @OnMessage peut avoir un type de retour non void correspondant au type du message String, Byte[], classe Java… Dans quel cas utiliser une méthode avec un type non void ? non void : pour retourner à un seul WebSocket client et avec une obligation de retour de message void : pour retourner à plusieurs WebSocket clients et sans obligation de retour (voir section « Envoyer des messages ») @ServerEndpoint(value = "/hellosinglereturn") public class HelloworldSingleEndpoint { ... @OnMessage public String onMessage(String message, Session session) { return System.currentTimeMillis() + ":" + message; } } HelloworldSingleReturnEndpoint.java du projet ws-helloworldwebsocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : cycle de vie - @OnMessage 30 Exemple : traitement des messages @ServerEndpoint(value = "/chat/{chatroom}/{username}") public class ChatEndpoint { // Key = session id private static Map<String, String> allUsers = new HashMap<>(); // Key = session id private static Map<String, String> allChatRooms = new HashMap<>(); ... @OnMessage public void onMessage(String message, Session session) { this.broadcast(message + " (from: " + allUsers.get(session.getId()) + ")", null, allChatRooms.get(session.getId())); } } ChatEndpoint.java du projet ws-chatwebsocket La récupération des valeurs des URI-templates n’est pas nécessaire Toutes les autres connexions du même salon recevront le message envoyé -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : cycle de vie - @OnClose 31 La méthode annotée @OnClose est invoquée à chaque fois qu’une connexion WebSocket se termine Aucune information ne peut être transmise vers le WebSocket Paramètres autorisés (facultatif) Session : décrit la conversation entre le WebSocket client et serveur (sera détaillée) CloseReason : la raison pour laquelle la connexion s’est terminée Quoi faire lors après une déconnexion ? Libérer d’éventuelles ressources ou avertir les autres connexions -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : cycle de vie - @OnClose 32 Exemple : fermeture de la connexion @ServerEndpoint(value = "/chat/{chatroom}/{username}") public class ChatEndpoint { // Key = session id private static Map<String, String> allUsers = new HashMap<>(); // Key = session id private static Map<String, String> allChatRooms = new HashMap<>(); ... @OnClose public void onClose(Session session) { String currentUsername = allUsers.get(session.getId()); allSessions.remove(currentUsername); allUsers.remove(session.getId()); this.broadcast(currentUsername + " disconnected!", session, allChatRooms.get(session.getId())); } } ChatEndpoint.java du projet ws-chatwebsocket La récupération des valeurs des URI-templates n’est pas nécessaire Toutes les autres connexions du même salon recevront un message indiquant qu’un nouvel utilisateur s’est déconnecté -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : par API 33 Pour s’intégrer dans un code existant possibilité d’utiliser une approche API au lieu de passer par des annotations Nécessite d’hériter de la classe Endpoint qui fournit abstract void onOpen(Session, EndpointConfig) void onClose(Session, CloseReason) void onError(Session, Throwable) La méthode onMessage n’existe pas et doit être traitée dans le corps de la méthode onOpen -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : par API 34 Exemple : WebSocket du « Chat » via API public class ChatEndpointUsingAPI extends Endpoint { ... @Override public void onOpen(Session session, EndpointConfig config) { String chatRoom = session.getPathParameters().get("chatroom"); String userName = session.getPathParameters().get("userName"); if (allSessions.containsKey(userName)) { try { session.close(); } catch (IOException e) { e.printStackTrace(); } } else { allChatRooms.put(session.getId(), chatRoom); allUsers.put(session.getId(), userName); allSessions.put(userName, session); session.addMessageHandler(String.class, new MessageHandler.Whole<String>() { public void onMessage(String message) { ChatEndpointUsingAPI.this.broadcast(message + ...); } }); this.broadcast(userName + " connected!", session, chatRoom); } } } ChatEndpointUsingAPI.java du projet ws-chatwebsocket Récupération des valeurs de paramètres de chemin Le code est très semblable au code réalisé avec les annotations -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebSocket (serveur) : par API 35 Exemple (suite) : WebSocket du « Chat » via API public class ChatEndpointUsingAPI extends Endpoint { ... @Override public void onClose(Session session, CloseReason closeReason) { String currentUsername = allUsers.get(session.getId()); allSessions.remove(currentUsername); allUsers.remove(session.getId()); this.broadcast(currentUsername + " disconnected!", session, allChatRooms.get(session.getId())); } } ChatEndpointUsingAPI.java du projet ws-chatwebsocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Accès aux informations de la connexion (Session) 36 L’objet Session permet de récupérer des informations sur la connexion du WebSocket Map<String, String> getPathParameters() : paramètres de chemin String getQueryString() : les paramètres de requêtes Map<String, List<String>> getRequestParameterMap() : tous les paramètres confondus Session getOpenSessions() : toutes les sessions ouvertes close() : pour fermer explicitement la session Basic getBasicRemote() : pour envoyer un message synchrone Async getAsyncRemote() : pour envoyer un message en mode asynchrone -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Accès aux informations de la connexion (Session) 37 Exemple : utilité de l’objet Session @ServerEndpoint(value = "/chat/{chatroom}/{username}") public class ChatEndpoint { ... @OnOpen public void onOpen(Session session, @PathParam("chatroom") String chatRoom, @PathParam("username") String userName) throws IOException { System.out.println("PathParameters:"); session.getPathParameters().forEach((c, v) -> { System.out.println(c + " " + v); }); System.out.println("QueryParameters:"); System.out.println(session.getQueryString()); System.out.println("Path and Query Parameters:"); session.getRequestParameterMap().forEach((k,v) -> { System.out.println(k + " " + v); }); System.out.println("Connection number:" + session.getOpenSessions().size()); if (allSessions.containsKey(userName)) { session.close(); } ... } } ChatEndpoint.java du projet ws-chatwebsocket Affichage des paramètres de chemin Affichage des paramètres de requêtes Affichage de tous les paramètres (chemin et requête) Fermeture explicite de la session courante ws://localhost:8025/chatwebsocket/chat/java/mbaron?chatroomqp=javaqp&usernameqp=mickaelbaronqp -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Plan du cours : WebSocket avec Java (Tyrus) 38 Généralités WebSocket avec Java Premier WebSocket avec Java et Tyrus Développement de WebSocket (vue serveur) Cycle de vie Envoyer et recevoir des messages Personnaliser les messages Développement de WebSocket (vue client) Déploiement -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Recevoir et envoyer des messages 39 Les messages reçus et envoyés peuvent être de format Texte = String ou Reader (comme vu précédemment) Binaire = byte[] ou ByteBuffer (transmettre une image par exemple) La réception d’un message peut être complète ou partielle Si partielle, l’envoi du message est morcelé Appel plusieurs fois la méthode annotée par @OnMessage Possibilité d’utiliser des encodeurs et décodeurs afin de manipuler des types personnalisés Recevoir et envoyer un objet de type Message -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Recevoir des messages 40 Pour recevoir un message annoter une méthode par @OnMessage choisir un format texte (String ou Reader) ou binaire (byte[] ou ByteBuffer) pour le paramètre désignant le message Le format du message est dépendant du format utilisé par le WebSocket client Possibilité d’utiliser au moins deux fois l’annotation @OnMessage pour gérer des messages textes et binaires Pas de possibilité de surcharger @OnMessage sur des types de format équivalent (byte[] <=> ByteBuffer) -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Recevoir des messages 41 Exemple : envoyer une image pendant une discussion @ServerEndpoint(value = "/chat/{chatroom}/{username}") public class ChatBinaryEndpoint { ... @OnMessage public void onMessage(byte[] message, Session session) { this.broadcastBinaryMessage(ByteBuffer.wrap(message), null, allChatRooms.get(session.getId())); } @OnMessage public void onMessage(String message, Session session) { this.broadcastStringMessage(message + " (from: " + allUsers.get(session.getId()) + ")", null, allChatRooms.get(session.getId())); } } ChatBinaryEndpoint.java du projet ws-chatbinarywebsocket Invoquée pour l’envoi de l’image Invoquée pour l’envoi du texte -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Recevoir des messages 42 Si le message à recevoir est trop important, possibilité de le recevoir par paquet (taille du buffer) Partielle : segmentation du message Pour la réception « Partielle » un paramètre booléen dans la méthode annotée permet de connaître l’état de réception Complète : réception complète du message L’attribut maxMessageSize permet de fixer la taille du buffer (byte) Par défaut maxMessageSize = -1 et peut recevoir un fichier de la taille supporté par la JVM Si maxMessageSize != -1 le message ne peut dépasser la taille sous peine de fermeture de session avec CloseReason.TOO_BIG -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Recevoir des messages 43 Exemple : segmentation d’un message de taille importante @ServerEndpoint(value = "/hellopartial") public class HelloworldPartialEndpoint { ... @OnMessage public void onTextMessage(String message, Session session, boolean part) throws IOException { System.out.println("Text length " + message.length()); if (part) { System.out.println("Whole message received"); } else { System.out.println("Partial message received"); } session.getBasicRemote().sendText(System.currentTimeMillis() + ":" + message.length()); } } HelloworldPartialEndpoint.java du projet ws-helloworldpartialwebsocket Tant que l’envoi du message n’est pas terminé Une fois l’envoi du message terminé Génère un message de taille 1 Mo -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Recevoir des messages 44 Exemple : segmentation d’un message de taille importante @ServerEndpoint(value = "/hellomessagemax") public class HelloworldMessageMaxEndpoint { ... @OnMessage(maxMessageSize = 500) public void onTextMessage(String message, Session session) throws IOException { ... } @OnClose public void onClose(Session session, CloseReason reason) { System.out.println(reason.getReasonPhrase()); } @OnError public void onError(Session session, Throwable exception) { System.out.println(exception.getMessage()); } } HelloworldMessageMaxEndpoint.java du projet ws-helloworldpartialwebsocket Déclare la limite (en byte) du message La connexion est fermée car la taille du message est trop importante Traitement de l’erreur Génère un message de taille 1 Mo -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Envoyer des messages 45 Pour envoyer des messages depuis le WebSocket serveur Retour de la méthode annotée @OnMessage (à 1 seule connexion) Utilisation de l’objet Session (à 1..n connexions) Via objet Session, l’envoi peut se faire de façon synchrone ou asynchrone Dans les deux cas, possibilité de retourner des messages textuels ou binaires Exemples de méthodes Synchrone (#Session.getBasicRemote()) void sendText(String) void sendBinary(ByteBuffer buf) Asynchrone (#Session.getAsyncRemote()) void sendBinary(ByteBuffer, SendHandler) Future<Void> sendText(String) -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Envoyer des messages 46 @ServerEndpoint(value = "/hellosinglereturn") public class HelloworldSingleReturnEndpoint { ... @OnMessage public String onMessage(String message, Session session) { return System.currentTimeMillis() + ":" + message; } } HelloworldSingleReturnEndpoint.java du projet ws-helloworldwebsocket Exemple : deux façons d’envoyer des messages @ServerEndpoint(value = "/hellosinglesession") public class HelloworldSingleSessionEndpoint { ... @OnMessage public void onMessage(String message, Session session) { session.getBasicRemote().sendText(System.currentTimeMillis() + ":" + message); } } HelloworldSingleSessionEndpoint.java du projet ws-helloworldwebsocket Le résultat est identique -
Streaming et Messages - Mise en œuvre - M. Baron
- Page 47 Envoyer des messages Exemple : envoyer des messages à plusieurs connexions @ServerEndpoint(value = "/chat/{chatroom}/{username}") public class ChatBinaryEndpoint { @OnMessage public void onMessage(String message, Session session) { this.broadcastStringMessage(message + " (from: " + allUsers.get(session.getId()) + ")", null, allChatRooms.get(session.getId())); } private void broadcastStringMessage(String message, Session exclude, String currentChatRoom) { allSessions.forEach((username, session) -> { try { if (!(exclude != null && session.getId().equals(exclude.getId()))) { if (allChatRooms.get(session.getId()).equals(currentChatRoom)) { session.getBasicRemote().sendObject(message); } } } catch (IOException | EncodeException e) { e.printStackTrace(); } }); } } ChatBinaryEndpoint.java du projet ws-chatbinarywebsocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Plan du cours : WebSocket avec Java (Tyrus) 48 Généralités WebSocket avec Java Premier WebSocket avec Java et Tyrus Développement de WebSocket (vue serveur) Cycle de vie Envoyer et recevoir des messages Personnaliser les messages Développement de WebSocket (vue client) Déploiement -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Personnaliser les messages 49 Pour l’instant les messages reçus et envoyés étaient de type String et ByteBuffer Possibilité de définir des types personnalisés afin de structurer les messages Exemple pour le « chat » de discussion => ChatMessage (Message textuel, Date d’envoi et Navigateur utilisé) Le format de représentation (XML, JSON…) est à la charge du développeur L’API WebSocket fournit la possibilité de définir Decoder : décode le message vers un type personnalisé Encoder : encode le message depuis type personnalisé Pour préciser le Decoder et Encoder utilisation des attributs decoders et encoders de l’annotation @ServerEndpoint -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Personnaliser les messages : Decoder 50 Decoder une interface générique permettant de coder le passage d’un message vers un type personnalisé Comment implémenter sa classe Decoder ? Préciser le type générique (ChatMessage) Préciser le format du message sur lequel s’applique ce décodage (textuel ou binaire) Implémenter deux méthodes T decode(String ou ByteBuffer) : décrit la transformation de String ou ByteBuffer vers le type générique boolean willDecode(String ou ByteBuffer) : autorise ou pas la transformation -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Personnaliser les messages : Decoder 51 Exemple : décoder un message String vers ChatMessage public class ChatMessageDecoder implements Decoder.Text<ChatMessage> { public void init(EndpointConfig config) {} public void destroy() {} public ChatMessage decode(String s) throws DecodeException { ObjectMapper mapper = new ObjectMapper(); try { ChatMessage readValue = mapper.readValue(s, ChatMessage.class); if (readValue.getCreated() == null) { readValue.setCreated(new Date()); } return readValue; } catch (IOException e) { e.printStackTrace(); return null; } } public boolean willDecode(String s) { return (s != null); } } Type générique = ChatMessage Format de message = textuel Utilisation de l’API Jackson pour désérialiser JSON => ChatMessage Si le message n’est pas null alors le décodage est possible ChatMessageDecoder.java du projet ws-chatjsonwebsocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Personnaliser les messages : Decoder 52 Exemple (suite) : décoder un message String vers … public class ChatMessage { private String content; private Date created; private String browser; public ChatMessage() { } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Date getCreated() { return created; } public void setCreated(Date created) { this.created = created; } public String getBrowser() { return browser; } public void setBrowser(String browser) { this.browser = browser; } } ChatMessage.java du projet ws-chatjsonwebsocket Simple classe de type POJO -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Personnaliser les messages : Encoder 53 Encoder une interface générique permettant d’encoder un message depuis un type personnalisé Comment implémenter sa classe Encoder ? Préciser le type générique (ChatMessage) Préciser le format du message sur lequel s’applique cet encodage (textuel ou binaire) Implémenter une méthode String ou ByteBuffer encode (T) : décrit la transformation d’un type générique vers String ou ByteBuffer -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Personnaliser les messages : Encoder 54 Exemple : encoder un message String depuis ChatMessage public class ChatMessageEncoder implements Encoder.Text<ChatMessage> { public void init(EndpointConfig config) { } public void destroy() { } public String encode(ChatMessage object) throws EncodeException { ObjectMapper mapper = new ObjectMapper(); try { return mapper.writeValueAsString(object); } catch (JsonProcessingException e) { e.printStackTrace(); return "ERROR"; } } } Type générique = ChatMessage Format de message = textuel ChatMessageEncoder.java du projet ws-chatjsonwebsocket Utilisation de l’API Jackson pour sérialiser ChatMessage => JSON -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Personnaliser les messages 55 Exemple : déclarer les Encoder et Decoder @ServerEndpoint( value = "/chat/{chatroom}/{username}", decoders = ChatMessageDecoder.class, encoders = ChatMessageEncoder.class) public class ChatJSONEndpoint { @OnMessage public void onMessage(Session session, ChatMessage message) { this.broadcastObjectMessage(message, allUsers.get(session.getId()), null, allChatRooms.get(session.getId())); } private void broadcastObjectMessage(ChatMessage message, String user, Session exclude, String currentChatRoom) { allSessions.forEach((username, session) -> { try { if (!(exclude != null && session.getId().equals(exclude.getId()))) { if (allChatRooms.get(session.getId()).equals(currentChatRoom)) { session.getBasicRemote().sendObject(message); } } } catch (IOException | EncodeException e) { e.printStackTrace(); } }); } } ChatJSONEndpoint.java du projet ws-chatjsonwebsocket Déclaration des objets Decoder et Encoder L’utilisation du type personnalisé permet de choisir quelle méthode annotée @OnMessage à invoquer Le message est envoyé comme un objet ChatMessage. C’est au Encoder de faire le travail. -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Plan du cours : WebSocket avec Java (Tyrus) 56 Généralités WebSocket avec Java Premier WebSocket avec Java et Tyrus Développement de WebSocket (vue serveur) Cycle de vie Envoyer et recevoir des messages Personnaliser les messages Développement de WebSocket (vue client) Déploiement -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement Client 57 Le développement de la partie WebSocket cliente est très similaire à la partie WebSocket serveur Différences Annotation @ClientEndpoint au lieu de @ServerEndpoint On ne renseigne pas d’URI (attribut value) Un WebSocket client n’est connecté qu’à un seul WebSocket serveur Possibilité d’utiliser des classes annotées ou l’API Endpoint Pour préciser l’adresse du WebSocket serveur utilisation d’un objet ClientManager Dans la suite, nous donnerons des exemples pour Client Java via l’annotation et l’API (Endpoint) Client JavaScript -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement Client 58 Dépendance pour l’accès à l’API cliente de Tyrus <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-client</artifactId> <version>${tyrus.version}</version> </dependency> Dépendance pour l’accès à l’implémentation cliente Grizzly Il existe d’autres implémentations : InMemory, JDK-Client et Servlet <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-container-grizzly-client</artifactId> <version>${tyrus.version}</version> </dependency> -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement Client 59 Exemple : WebSocket client via @ClientEndpoint @ClientEndpoint public class ChatClientEndpoint { private CountDownLatch countDownLatch; public ChatClientEndpoint(CountDownLatch pCountDownLatch) { this.countDownLatch = pCountDownLatch; } @OnOpen public void onOpen(Session session) throws IOException { } @OnMessage public void onMessage(String message) { System.out.println("Received message: " + message); countDownLatch.countDown(); } } public class ChatClientWebSocketLauncher { private static CountDownLatch messageLatch = new CountDownLatch(1); public static void main(String[] args) { try { ClientManager client = ClientManager.createClient(); Session currentSession = client.connectToServer(new ChatClientEndpoint(messageLatch), new URI("ws://localhost:8026/chatwebsocket/chat/java/mickaelbaron")); currentSession.getBasicRemote().sendText("My First Message"); messageLatch.await(2, TimeUnit.SECONDS); } catch (Exception e) { e.printStackTrace(); } } } ChatClientEndpoint.java du projet ws-chatclientwebsocket ChatClientWebSocketLauncher.java du projet ws-chatclientwebsocket Connexion au WebSocket serveur -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement Client 60 Exemple : WebSocket client via l’API Endpoint public class ChatClientWebSocketUsingAPILauncher { private static CountDownLatch messageLatch; public static void main(String[] args) { try { messageLatch = new CountDownLatch(1); final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build(); ClientManager client = ClientManager.createClient(); Session currentSession = client.connectToServer(new Endpoint() { @Override public void onOpen(Session session, EndpointConfig config) { session.addMessageHandler(new MessageHandler.Whole<String>() { @Override public void onMessage(String message) { System.out.println("Received message: " + message); messageLatch.countDown(); } }); } }, cec, new URI("ws://localhost:8026/chatwebsocket/chat/java/mickaelbaron")); currentSession.getBasicRemote().sendText("My First Message"); messageLatch.await(100, TimeUnit.SECONDS); } catch (Exception e) { e.printStackTrace(); } } } ChatClientWebSocketUsingAPILauncher.java du projet ws-chatclientwebsocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement Client 61 Exemple : WebSocket client via l’API Endpoint avec OpenJFX public class ChatJavaFXClientWebSocketApplication extends Application { private void connect(String wsUri) { final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build(); ClientManager client = ClientManager.createClient(); try { currentSession = client.connectToServer(new Endpoint() { @Override public void onOpen(Session session, EndpointConfig config) { writeMessage("Connect to WSEndpoint."); session.addMessageHandler(new MessageHandler.Whole<String>() { @Override public void onMessage(String message) { writeMessage(message); } }); } }, cec, new URI(wsUri)); } catch (DeploymentException | IOException | URISyntaxException e) { e.printStackTrace(); } } } $ java -cp 'target/dependency/*:target/classes' --module-path target/dependency --add-modules=javafx.fxml,,javafx.controls fr.mickaelbaron.chatjavafxclientwebsocket.ChatJavaFXClientWebSocketApplication ChatJavaFXClientWebSocketApplication.java du projet ws-chatjavafxclientwebsocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement Client 62 Exemple : WebSocket client avec JavaScript var ws; function connect() { var host = document.location.host; var pathname = document.location.pathname; ws = new WebSocket(document.getElementById("wsURI").value); ws.onopen = function (evt) { console.log(evt); writeMessage("Connect to WSEndpoint."); }; ws.onmessage = function (evt) { console.log(evt); writeMessage(evt.data); }; ws.onerror = function (evt) { console.log(evt); }; ws.onclose = function (evt) { writeMessage("Disconnect from WSEndpoint."); } } function disConnect() { ws.close(); } function send() { ws.send(document.getElementById("wsMessage").value); } websocket.js du projet ws-chatwebsocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Plan du cours : WebSocket avec Java (Tyrus) 63 Généralités WebSocket avec Java Premier WebSocket avec Java et Tyrus Développement de WebSocket (vue serveur) Cycle de vie Envoyer et recevoir des messages Personnaliser les messages Développement de WebSocket (vue client) Déploiement -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Déploiement pour le WebSocket serveur 64 Deux formes de déploiement pour exécuter un WebSocket Déploiement sur un serveur d’application Java Nécessite l’installation d’un serveur compatible Java EE ou autres (Jetty 9+, Tomcat, WildFly, Glassfish…) Déploiement comme une application Java classique Populaire depuis l’arrivée des microservices Serveur d’application est intégré (embedded) -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Déploiement : serveur d’application Java 65 Les serveurs d’application implémentant Jakarta WebSocket compatibles Java EE (Glassfish, Wildfly…) les autres (Jetty, Tomcat…) Configuration Pas besoin de paramétrer le fichier web.xml Quelles classes sont scannées pour identifier les WebSockets ? celles qui implémentent ServerApplicationConfig les classes qui sont annotées par @ServerEndpoint les classes qui héritent de Endpoint Dépendances nécessaires ? <dependency> <groupId>jakarta.websocket</groupId> <artifactId>jakarta.websocket-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Déploiement : serveur d’application Java 66 Exemple : déployer un WebSocket (packagé war) dans une instance Tomcat via Docker $ mvn clean package # Compile et build le projet ws-chatwarwebsocket => Fichier chatwarwebsocket.war disponible dans le répertoire target/ $ docker pull tomcat:9-jre11-slim # Télécharge la version 9 de Tomcat avec une JRE 11 => Image Docker disponible $ docker run --rm --name chatwarwebsocket-tomcat -v $(pwd)/target/chatwarwebsocket.war:/usr/ local/tomcat/webapps/chatwarwebsocket.war -it -p 8080:8080 tomcat:9-jre11-slim => WebSocket disponible à l’adresse ws://localhost:8080/chatwarwebsocket/chat Projet ws-chatwarwebsocket -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Déploiement : application Java classique 67 Un WebSocket peut être déployé comme une application Java (JAR) sans avoir à fournir une application web (WAR) Usages Pour les tests fonctionnels, fournir des bouchons de WebSocket Déployer son application comme un microservice (voir cours) Dépendances Maven Dépendance pour l’accès à l’API serveur de Tyrus Dépendance pour l’accès à l’implémentation serveur Grizzly <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-server</artifactId> <version>${tyrus.version}</version> </dependency> <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-container-grizzly-server</artifactId> <version>${tyrus.version}</version> </dependency> -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Déploiement : application Java classique 68 Exemple : déployer un WebSocket serveur dans une application Java classique avec Grizzly public class ChatWebSocketLauncher { public static void main(String[] args) { final Map<String, Object> serverProperties = new HashMap<>(); serverProperties.put(Server.STATIC_CONTENT_ROOT, "./static"); Server server = new Server("localhost", 8028, "/chatjsonwebsocket", serverProperties, ChatJSONEndpoint.class); try { server.start(); System.out.println("Tyrus app started available at ws://localhost:8028/chatjsonwebsocket" + "\nHit enter to stop it..."); System.in.read(); } catch (Exception e) { e.printStackTrace(); } finally { server.stop(); } } } ChatWebSocketLauncher.java du projet ws-chatjsonwebsocket Possibilité de déployer des ressources statiques (html, JS…) -
SOA – Streaming et Messages Mickaël BARON – Mai 2020
(Rev. Novembre 2025) mailto:[email protected] ou mailto:[email protected] mickael-baron.fr mickaelbaron mickael-baron.fr mickaelbaron JAX-RS et Jersey (SSE) -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités SSE avec Java Premier SSE avec Java et JAX-RS Développement de SSE Diffusion unique (simple) Diffusion multiple (broadcast) Client Déploiement Plan du cours : Server Sent Events avec Java (JAX-RS) 70 -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Server-Sent Events technologie uni-directionnelle pour l’envoi de données serveur vers des clients Pour développer un serveur SSE utilisation de l’API JAX-RS Tout ce que nous avons vu avec JAX-RS reste valide https://mickael-baron.fr/soa/developper-serviceweb-rest-jaxrs Il existe deux implémentations Avant < JAX-RS 2.1 : spécifique à l’implémentation Jersey Depuis => JAX-RS 2.1 : décrit dans la spécification JAX-RS et supportée dans les implémentations (notamment Jersey) Généralités SSE avec Java 71 -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités SSE avec Java : bibliothèques alternatives 72 jEaSSE : implémentation pour Servlet 3 et Vert.x Site projet : github.com/mariomac/jeasse Akka : bibliothèque pour Java et Scala Site projet : akka.io Spring WebFlux : implémentation fournie par Spring Site projet : spring.io -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités SSE avec Java : dépendance Maven 73 Pour prendre en compte l’implémentation SSE supportée par JAX-RS avec Jersey Rappel : pour ajouter le support JSON lors de l’envoi d’un événement SSE <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-sse</artifactId> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> </dependency> -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités SSE : événement SSE (rappel) 74 Un événement SSE est un bloc de texte composé de event : le type d’événement (ex : add, remove…) data : le contenu du message envoyé par le serveur (ex : « Hello ») id : un identifiant (utile en cas de reprise déconnexion pour savoir quel était le dernier événement envoyé) retry : temps en millisecondes pour une reconnexion du client vers le serveur (en cas de panne) ‘:’ : commentaire Chaque événement SSE est séparé par un double \n\n : This is a new HelloWorld message and continue the communication. event: add-message id: 123 retry: 1000 data: HelloWorld : This is a new HelloWorld message and continue the communication. event: add-message id: 124 ... -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Premier SSE avec Java et JAX-RS 75 Exemple : envoyer un événement et mise en attente @Path("sse") public class HelloworldSseResource { @GET @Produces(MediaType.SERVER_SENT_EVENTS) public void getHelloWorldWithSimpleSSE( @Context SseEventSink eventSink, @Context Sse sse) { OutboundSseEvent event = sse.newEventBuilder() .name("add-message") .data("HelloWorld") .comment("This is a new HelloWorld message.") .id("123") .reconnectDelay(1000) .build(); eventSink.send(event); } } HelloWorldSseResource.java du projet sse-helloworld Doit produire un message de type text/event-stream Construction d’un événement de type SseEvent Transmission d’un seul événement au client -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Premier SSE avec Java et JAX-RS 76 Exemple (suite) : envoyer un événement et mise en attente $ curl -H "Accept:text/event-stream" http://localhost:9992/api/sse : This is a new HelloWorld message and continue the communication. event: add-message id: 123 retry: 1000 data: HelloWorld Connexion au serveur via cURL comme un simple appel de service web REST Réception de l’événement SSE Mise en attente de réception de nouveaux événements SSE ou de la fermeture de la connexion Délais avant que le client se reconnecte si une connexion se termine avec le serveur -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités SSE avec Java Premier SSE avec Java et JAX-RS Développement de SSE Diffusion unique (simple) Diffusion multiple (broadcast) Client Déploiement Plan du cours : Server Sent Events avec Java (JAX-RS) 77 -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement SSE avec JAX-RS : déclarer ? 78 Une ressource SSE est une méthode annotée avec JAX-RS Doit produire le type MIME : text/event-stream Doit injecter un objet SseEventSink pour communiquer avec le client Peut injecter un objet Sse utilisé pour créer des événements Si possible doit être annotée @GET (pas obligé) @Path("sse") public class HelloworldSseEndpoint { @GET @Produces(MediaType.SERVER_SENT_EVENTS) public void getHelloWorldWithSimpleSSE( @Context SseEventSink eventSink, @Context Sse sse) { ... // Voir transparent suivant } } 1 2 3 4 1 2 3 4 -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement SSE avec JAX-RS : implémenter ? 79 La construction d’un événement SSE via l’utilisation de Sse Pour la transmission de l’événement au client deux modes Diffusion à un seul client Diffusion à un ensemble de client (Broadcast) @GET @Produces(MediaType.SERVER_SENT_EVENTS) public void getHelloWorldWithSimpleSSE( @Context SseEventSink eventSink, @Context Sse sse) { OutboundSseEvent event = sse.newEventBuilder() .name("add-message") .data("HelloWorld") .comment("This is a new HelloWorld message.") .id("123") .reconnectDelay(1000) .build(); ... // Voir transparent suivant } => event (type d’évenemet) => data => commentaire => identifiant => retry (délai pour reconnexion) Sse utilisée pour créer des événements -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement SSE avec JAX-RS : diffusion unique 80 Exemple : envoyer plusieurs événements à un client @Path("withstreaming") @GET @Produces(MediaType.SERVER_SENT_EVENTS) public void getHelloWorldAndTimeWithSimpleSSE(@Context SseEventSink eventSink, @Context Sse sse) { new Thread(() -> { for (int i = 0; i < 10000; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } OutboundSseEvent event = sse.newEventBuilder() .name("add-message") .id(LocalTime.now().toString()) .data("HelloWorld" + LocalTime.now()) .comment("This is a new HelloWorld message published each 1 second.") .build(); if (!eventSink.isClosed()) { eventSink.send(event); } else { return; } } eventSink.close(); }).start(); } Transmission d’un événement à un seul client 10000 événements seront construits HelloWorldSseResource.java du projet sse-helloworld Dés que tous les événements sont transmis fermeture de la connexion -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement SSE avec JAX-RS : diffusion unique 81 Exemple (suite) : envoyer plusieurs événements à un client $ curl -H "Accept:text/event-stream" http://localhost:9992/api/sse/withstreaming : This is a new HelloWorld message published each 1 second. event: add-message id: 0 data: HelloWorld12:35:10.595740 : This is a new HelloWorld message published each 1 second. event: add-message id: 1 data: HelloWorld12:35:11.610276 : This is a new HelloWorld message published each 1 second. event: add-message id: 2 data: HelloWorld12:35:12.614277 ... : This is a new HelloWorld message published each 1 second. event: add-message id: 10000 -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement SSE avec JAX-RS : diffusion multiple 82 L’API SSE avec JAX-RS permet aussi d’envoyer un même événement à plusieurs clients (on parle de broadcast) Plusieurs choses à réaliser pour faire du broadcast Initialiser le broadcaster via une instance SseBroadcaster Inscrire un client (SseEventSink) au broadcaster Soumettre un événement au broadcaster qui transmettra à tous les clients inscrits 1 2 3 -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement SSE avec JAX-RS : diffusion multiple 83 Exemple : envoyer un événement à plusieurs clients @Singleton @Path("sse-broadcast") public class HelloWorldSseBroadcastResource { private Sse sse; private SseBroadcaster broadcaster; public HelloWorldSseBroadcastResource(@Context final Sse sse) { this.sse = sse; this.broadcaster = sse.newBroadcaster(); } @GET @Produces(MediaType.SERVER_SENT_EVENTS) public void subscribeToBroadcast(@Context SseEventSink eventSink) { this.broadcaster.register(eventSink); } @POST public String broadcastMessage(String message) { OutboundSseEvent event = sse.newEventBuilder().name("add-message").data(message) .comment("This is a new message.").id(LocalTime.now().toString()).build(); broadcaster.broadcast(event); return "Message '" + message + "' has been broadcast."; } } Initialisation du broadcaster dés la première connexion Inscription d’un client au broadcaster Transmission d’un événement au broadcaster HelloWorldSseBroadcastResource.java du projet sse-helloworld 1 2 3 -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement SSE avec JAX-RS : diffusion multiple 84 Exemple (suite) : envoyer un événement à plusieurs clients $ curl --request POST --data 'Mon événement' http://localhost:9992/api/sse-broadcast Message 'Mon événement' has been broadcast. $ curl -H "Accept:text/event-stream" http://localhost:9992/api/sse-broadcast : This is a new message. event: add-message id: 15:03:24.448687 data: Mon événement Trois clients en attente d’événements Appel au service web (requête POST) pour transmettre un message et créer un événement -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement SSE avec JAX-RS : type personnalisé 85 Par défaut le type MIME utilisé pour la valeur de la donnée d’un événement est text/plain Possibilité d’utiliser des types personnalisés en utilisant des formats reconnus application/json Cette information de format doit être transmise lors de la création de l’événement et pas via l’annotation @Produces Ne pas oublier d’ajouter la dépendance vers une bibliothèque qui gère le binding vers le format considéré <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> </dependency> -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Développement SSE avec JAX-RS : type personnalisé 86 Exemple : envoyer un événement avec du contenu JSON à plusieurs clients @Singleton @Path("sse-broadcast-json") public class HelloWorldSseBroadcastWithJSONResource { ... @POST public String broadcastMessage(String message) { Message newMessage = new Message(); newMessage.setContent(message); newMessage.setTime(LocalTime.now().toString()); OutboundSseEvent event = sse.newEventBuilder() .name("add-message") .mediaType(MediaType.APPLICATION_JSON_TYPE) .data(Message.class, newMessage) .comment("This is a new message.") .id(LocalTime.now().toString()) .build(); broadcaster.broadcast(event); return "Message '" + message + "' has been broadcast."; } } HelloWorldSseBroadcastWithJSONResource.java du projet sse-helloworld La donnée est transmise au client au format JSON -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités SSE avec Java Premier SSE avec Java et JAX-RS Développement de SSE Diffusion unique (simple) Diffusion multiple (broadcast) Client Déploiement Plan du cours : Server Sent Events avec Java (JAX-RS) 87 -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Client SSE 88 JAX-RS fournit également une API spécifique pour consommer un événement SSE envoyé par un serveur Elle se base sur l’API JAX-RS pour l’initialisation de la ressource Création d’un client (déjà vu) Création d’un chemin racine (déjà vu) Création d’un objet SseEventSource pour la gestion du message reçu, des erreurs et de la déconnexion Ouverture de la connexion Client client = ClientBuilder.newClient(); WebTarget webTarget = client.target("http://127.0.0.1:9992/sse-broadcast"); SseEventSource eventSource = SseEventSource.target(target).build(); eventSource.register(onEvent, onError, onComplete); eventSource.open(); -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Client SSE 89 Exemple : consommer un événement SSE public class HelloWorldSseClient { private static final String url = "http://localhost:9992/api"; public static void main(String... args) throws Exception { Client client = ClientBuilder.newClient(); WebTarget target = client.target(url).path(args.length == 1 ? args[0] : "sse"); try (SseEventSource eventSource = SseEventSource.target(target).build()) { eventSource.register(onEvent, onError, onComplete); eventSource.open(); // Consuming events for one hour Thread.sleep(60 * 60 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } client.close(); } private static Consumer<InboundSseEvent> onEvent = (inboundSseEvent) -> { String data = inboundSseEvent.readData();System.out.println(data); }; private static Consumer<Throwable> onError = (throwable) -> { throwable.printStackTrace(); }; private static Runnable onComplete = () -> { System.out.println("Done!"); }; } HelloWorldSseClient.java du projet sse-helloworld-client Définition de trois fonctions pour traiter la gestion des messages reçus, erreurs et fermeture -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Client SSE 90 Exemple : consommer un événement SSE avec JavaScript var ws; function connect() { source = new EventSource(document.getElementById("sseURI").value); source.onopen = function(event) { console.log(event); }; source.addEventListener('add-message', function(e) { append(e.data); }, false); source.onerror = function(event) { console.log(event); }; } function disConnect() { source.close(); } see.js du projet sse-helloworld Doit correspondre à la valeur donnée à event ou name -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Généralités SSE avec Java Premier SSE avec Java et JAX-RS Développement de SSE Diffusion unique (simple) Diffusion multiple (broadcast) Client Déploiement Plan du cours : Server Sent Events avec Java (JAX-RS) 91 -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Déploiement SSE : serveur d’application Java 92 Exemple : déployer un service web (packagé war) dans une instance Tomcat via Docker # Projet sse-helloworld-war $ mvn clean package # Compile et build le projet sse-helloworld-war => Fichier helloworldsse.war disponible dans le répertoire target/ $ docker pull tomcat:jre11-openjdk-slim # Télécharge la version 10 de Tomcat avec une JRE 11 => Image Docker disponible $ docker run --rm --name helloworldsse-tomcat -v $(pwd)/target/helloworldsse.war:/usr/local/tomcat/webapps /helloworldsse.war -it -p 8080:8080 tomcat:jre11-openjdk-slim => SSE disponible à l’adresse http://localhost:8080/helloworldsse/api/sse $ curl --request POST --data 'Mon événement' http://localhost:8080/helloworldsse/api/sse Message 'Mon événement' has been broadcast. Appel au service web (requête POST) pour transmettre un message et créer un événement -
Streaming et Messages - Mise en œuvre - M. Baron
- Page Déploiement SSE : application Java classique 93 Exemple : utiliser JAX-RS avec le support SSE avec Grizzly public class HelloWorldServerSetEventsLauncher { public static final URI BASE_URI = getBaseURI(); private static URI getBaseURI() { return UriBuilder.fromUri("http://localhost/api").port(9992).build(); } public static void main(String[] args) throws ... { ResourceConfig resourceConfig = new ResourceConfig(); resourceConfig.registerClasses(HelloWorldSseResource.class, HelloWorldSseBroadcastResource.class, HelloWorldSseBroadcastWithJSONResource.class); StaticHttpHandler staticHttpHandler = new StaticHttpHandler("./static"); HttpServer server = GrizzlyHttpServerFactory .createHttpServer(BASE_URI, resourceConfig); server.start(); System.out.println(String.format("Jersey app started available at %s” + "\nHit enter to stop it...", BASE_URI)); System.in.read(); server.shutdownNow(); } } HelloWorldServerSetEventsLauncher.java du projet sse-helloworld Permet de déployer aussi les ressources statiques (HTML et JS) -
SOA – Streaming et Messages Mickaël BARON – Mai 2020
(Rev. Novembre 2025) mailto:[email protected] ou mailto:[email protected] mickael-baron.fr mickaelbaron mickael-baron.fr mickaelbaron io.grpc (gRPC) -
Streaming et Messages - Mise en œuvre - M. Baron
- Page gRPC 95 -
SOA – Streaming et Messages Mickaël BARON – Mai 2020
(Rev. Novembre 2025) mailto:[email protected] ou mailto:[email protected] mickael-baron.fr mickaelbaron mickael-baron.fr mickaelbaron WebRTC-Java (WebRTC) -
Streaming et Messages - Mise en œuvre - M. Baron
- Page WebRTC 97 -
SOA – Streaming et Messages Mickaël BARON – Mai 2020
(Rev. Novembre 2025) mailto:[email protected] ou mailto:[email protected] mickael-baron.fr mickaelbaron mickael-baron.fr mickaelbaron RabbitMQ (AMQP) -
Streaming et Messages - Mise en œuvre - M. Baron
- Page RabbitMQ 99