$30 off During Our Annual Pro Sale. View Details »

Streaming HTTP : mise en oeuvre avec le langage Java

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
Tweet

More Decks by Mickael BARON

Other Decks in Programming

Transcript

  1. SOA – Streaming et Messages
    Mickaël BARON – Mai 2020 (Rev. Novembre 2023)
    mailto:[email protected] ou mailto:[email protected]
    mickael-baron.fr
    mickaelbaron
    Mise en œuvre

    View Slide

  2. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 - Outils

    View Slide

  3. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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, 2 000 forums et jusqu'à 5 000 messages par jour
    Streaming et Messages - Outils
    mickael-baron.fr
    mickaelbaron

    View Slide

  4. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    4
    Plan du cours
    † Technologies Push avec le langage Java
    † WebSocket (Tyrus)
    † SSE (JAX-RS)
    † gRPC (io.grpc)
    † Technologie basée sur l’envoi de messages
    † RabbitMQ (AMQP)
    Streaming et Messages - Outils

    View Slide

  5. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 - Outils

    View Slide

  6. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 - Outils

    View Slide

  7. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Version Java supportée par ce support de cours
    7
    q Pas de gestion des modules dans les exemples (utilisation du classpath)
    Streaming et Messages - Outils

    View Slide

  8. SOA – Streaming et Messages
    Mickaël BARON – Mai 2020 (Rev. Novembre 2023)
    mailto:[email protected] ou mailto:[email protected]
    mickael-baron.fr
    mickaelbaron
    WebSocket avec Java (Tyrus)

    View Slide

  9. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Plan du cours : WebSocket avec Java (Tyrus)
    9
    WebSocket
    † 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

    View Slide

  10. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 …
    WebSocket

    View Slide

  11. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Généralités WebSocket avec Java : la spécification
    11
    WebSocket
    † 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

    View Slide

  12. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Généralités WebSocket avec Java : l’implémentation
    12
    WebSocket
    † 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

    View Slide

  13. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Généralités WebSocket avec Java : l’implémentation
    13
    WebSocket
    † 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

    View Slide

  14. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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

    jakarta.websocket
    jakarta.websocket-api
    2.0
    provided

    Nécessaire pour
    l’accès à l’API
    (pour la
    compilation)

    org.glassfish.tyrus
    tyrus-server
    ${tyrus.version}

    WebSocket

    org.glassfish.tyrus
    tyrus-client
    ${tyrus.version}

    Différentes
    implémentations
    existes (voir
    section Cliente et
    Déploiement)

    View Slide

  15. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  16. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Plan du cours : WebSocket avec Java (Tyrus)
    16
    WebSocket
    † 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

    View Slide

  17. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  18. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  19. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  20. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  21. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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&param2=value2 WebSocket
    @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

    View Slide

  22. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket
    ws://localhost:8025/chatwebsocket/chat/java/mbaron

    View Slide

  23. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Plan du cours : WebSocket avec Java (Tyrus)
    23
    WebSocket
    † 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

    View Slide

  24. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  25. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  26. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  27. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 allSessions = new HashMap<>();
    // Key = session id
    private static Map allUsers = new HashMap<>();
    // Key = session id
    private static Map 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é
    WebSocket

    View Slide

  28. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  29. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 WebSocket

    View Slide

  30. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 allUsers = new HashMap<>();
    // Key = session id
    private static Map 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é
    WebSocket

    View Slide

  31. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  32. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 allUsers = new HashMap<>();
    // Key = session id
    private static Map 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
    32
    Tous les autres connexions du même salon
    recevront un message indiquant
    qu’un nouvel utilisateur s’est déconnecté
    WebSocket

    View Slide

  33. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  34. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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() {
    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
    WebSocket

    View Slide

  35. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  36. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 getPathParameters() : paramètres de chemin
    † String getQueryString() : les paramètres de requêtes
    † Map> 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
    WebSocket

    View Slide

  37. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  38. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Plan du cours : WebSocket avec Java (Tyrus)
    38
    WebSocket
    † 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

    View Slide

  39. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  40. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Recevoir des messages
    40
    WebSocket
    † 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)

    View Slide

  41. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Recevoir des messages
    41
    † Exemple : envoyer une image pendant un 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
    WebSocket

    View Slide

  42. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  43. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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é
    WebSocket
    Génère un message
    de taille 1 Mo

    View Slide

  44. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket
    Génère un message
    de taille 1 Mo

    View Slide

  45. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 sendText(String)
    WebSocket

    View Slide

  46. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Envoyer des messages
    46
    WebSocket
    @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

    View Slide

  47. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  48. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Plan du cours : WebSocket avec Java (Tyrus)
    48
    WebSocket
    † 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

    View Slide

  49. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  50. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  51. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Personnaliser les messages : Decoder
    51
    WebSocket
    † Exemple : décoder un message String vers ChatMessage
    public class ChatMessageDecoder implements Decoder.Text {
    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

    View Slide

  52. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  53. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  54. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Personnaliser les messages : Encoder
    54
    † Exemple : encoder un message String depuis ChatMessage
    public class ChatMessageEncoder implements Encoder.Text {
    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
    WebSocket

    View Slide

  55. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket
    Le message est envoyé comme un
    objet ChatMessage. C’est au Encoder
    de faire le travail.

    View Slide

  56. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Plan du cours : WebSocket avec Java (Tyrus)
    56
    WebSocket
    † 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

    View Slide

  57. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Développement Client
    57
    WebSocket
    † 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

    View Slide

  58. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Développement Client
    58
    † Dépendance pour l’accès à l’API cliente de Tyrus

    org.glassfish.tyrus
    tyrus-client
    ${tyrus.version}

    † Dépendance pour l’accès à l’implémentation cliente Grizzly
    † Il existe d’autres implémentations : InMemory, JDK-Client et
    Servlet

    org.glassfish.tyrus
    tyrus-container-grizzly-client
    ${tyrus.version}

    WebSocket

    View Slide

  59. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Développement Client
    59
    WebSocket
    † 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

    View Slide

  60. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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() {
    @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 WebSocket

    View Slide

  61. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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() {
    @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
    WebSocket

    View Slide

  62. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    WebSocket

    View Slide

  63. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Plan du cours : WebSocket avec Java (Tyrus)
    63
    WebSocket
    † 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

    View Slide

  64. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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)
    WebSocket

    View Slide

  65. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 ?
    WebSocket

    jakarta.websocket
    jakarta.websocket-api
    2.0
    provided

    View Slide

  66. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Déploiement : serveur d’application Java
    66
    WebSocket
    † 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

    View Slide

  67. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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

    org.glassfish.tyrus
    tyrus-server
    ${tyrus.version}


    org.glassfish.tyrus
    tyrus-container-grizzly-server
    ${tyrus.version}

    WebSocket

    View Slide

  68. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 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();
    }
    }
    }
    WebSocket
    ChatWebSocketLauncher.java du projet
    ws-chatjsonwebsocket
    Possibilité de déployer des
    ressources statiques (html,
    JS…)

    View Slide

  69. SOA – Streaming et Messages
    Mickaël BARON – Mai 2020 (Rev. Novembre 2023)
    mailto:[email protected] ou mailto:[email protected]
    mickael-baron.fr
    mickaelbaron
    JAX-RS et Jersey (SSE)

    View Slide

  70. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    † 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
    SSE

    View Slide

  71. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    † 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
    SSE

    View Slide

  72. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Généralités SSE avec Java : bibliothèques alternatives
    72
    SSE
    † 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

    View Slide

  73. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Généralités SSE avec Java : dépendance Maven
    73
    SSE
    † 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

    org.glassfish.jersey.media
    jersey-media-sse


    org.glassfish.jersey.media
    jersey-media-json-jackson

    View Slide

  74. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    ...
    SSE

    View Slide

  75. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Premier SSE avec Java et JAX-RS
    75
    SSE
    † 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

    View Slide

  76. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    SSE
    Délais avant que le client se
    reconnecte si une connexion
    se termine avec le serveur

    View Slide

  77. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    † 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
    SSE

    View Slide

  78. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Développement SSE avec JAX-RS : déclarer ?
    78
    SSE
    † 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

    View Slide

  79. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Développement SSE avec JAX-RS : implémenter ?
    79
    SSE
    † 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

    View Slide

  80. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Développement SSE avec JAX-RS : diffusion unique
    80
    SSE
    † 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

    View Slide

  81. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Développement SSE avec JAX-RS : diffusion unique
    81
    SSE
    † 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

    View Slide

  82. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Développement SSE avec JAX-RS : diffusion multiple
    82
    SSE
    † 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

    View Slide

  83. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    SSE

    View Slide

  84. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    SSE
    Appel au service web
    (requête POST) pour
    transmettre un message
    et créer un événement

    View Slide

  85. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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é
    SSE

    org.glassfish.jersey.media
    jersey-media-json-jackson

    View Slide

  86. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    SSE

    View Slide

  87. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    † 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
    SSE

    View Slide

  88. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Client SSE
    88
    SSE
    † 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();

    View Slide

  89. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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 onEvent = (inboundSseEvent) -> {
    String data = inboundSseEvent.readData();System.out.println(data);
    };
    private static Consumer 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
    SSE

    View Slide

  90. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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();
    }
    SSE
    see.js du projet
    sse-helloworld
    Doit correspondre à la
    valeur donnée à event
    ou name

    View Slide

  91. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    † 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
    SSE

    View Slide

  92. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    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
    SSE

    View Slide

  93. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    Déploiement SSE : application Java classique
    93
    SSE
    † 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)

    View Slide

  94. SOA – Streaming et Messages
    Mickaël BARON – Mai 2020 (Rev. Novembre 2023)
    mailto:[email protected] ou mailto:[email protected]
    mickael-baron.fr
    mickaelbaron
    io.grpc (gRPC)

    View Slide

  95. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    gRPC
    95
    gRPC

    View Slide

  96. SOA – Streaming et Messages
    Mickaël BARON – Mai 2020 (Rev. Novembre 2023)
    mailto:[email protected] ou mailto:[email protected]
    mickael-baron.fr
    mickaelbaron
    RabbitMQ (AMQP)

    View Slide

  97. - M. Baron - Page
    mickael-baron.fr mickaelbaron
    RabbitMQ
    97
    RabbitMQ

    View Slide