Guide de l’API Java pour WebSocket

Guide de l'API Java pour WebSocket

1. Vue d'ensemble

WebSocket offre une alternative à la limitation de la communication efficace entre le serveur et le navigateur Web en fournissant des communications client / serveur bidirectionnelles, en duplex intégral et en temps réel. Le serveur peut envoyer des données au client à tout moment. Parce qu'il fonctionne sur TCP, il fournit également une communication de bas niveau à faible latence et réduit la surcharge de chaque message.

Dans cet article, nous allons examiner l'API Java pour WebSockets en créant une application de type chat.

2. JSR 356

JSR 356 ou l'API Java pour WebSocket, spécifie une API que les développeurs Java peuvent utiliser pour intégrer WebSockets avec leurs applications, à la fois du côté serveur et du côté client Java.

Cette API Java fournit des composants côté serveur et côté client:

  • Server: tout dans le packagejavax.websocket.server.

  • Client: le contenu du packagejavax.websocket, qui se compose d'API côté client et également de bibliothèques communes au serveur et au client.

3. Créer une conversation à l'aide de WebSockets

Nous allons construire une application très simple de type chat. Tout utilisateur pourra ouvrir le chat depuis n'importe quel navigateur, taper son nom, se connecter au chat et commencer à communiquer avec toutes les personnes connectées au chat.

Nous allons commencer par ajouter la dernière dépendance au fichierpom.xml:


    javax.websocket
    javax.websocket-api
    1.1

La dernière version peut être trouvéehere.

Afin de convertir JavaObjects en leurs représentations JSON et vice versa, nous utiliserons Gson:


    com.google.code.gson
    gson
    2.8.0

La dernière version est disponible dans le référentielMaven Central.

3.1. Configuration du point de terminaison

Il existe deux façons de configurer les points de terminaison: basés surannotation- et basés sur l'extension. Vous pouvez étendre la classejavax.websocket.Endpoint ou utiliser des annotations dédiées au niveau de la méthode. Étant donné que le modèle d'annotation conduit à un code plus propre par rapport au modèle de programmation, l'annotation est devenue le choix de codage classique. Dans ce cas, les événements de cycle de vie des noeuds finaux WebSocket sont gérés par les annotations suivantes:

  • @ServerEndpoint: S'il est décoré avec@ServerEndpoint,, le conteneur garantit la disponibilité de la classe en tant que serveurWebSocket écoutant un espace URI spécifique

  • @ClientEndpoint: une classe décorée avec cette annotation est traitée comme un clientWebSocket

  • @OnOpen: une méthode Java avec@OnOpen est appelée par le conteneur lorsqu'une nouvelle connexionWebSocket est initiée

  • @OnMessage: une méthode Java, annotée avec@OnMessage, reçoit les informations du conteneurWebSocket lorsqu'un message est envoyé au noeud final

  • @OnError: une méthode avec@OnError est invoquée en cas de problème de communication

  • @OnClose: Utilisé pour décorer une méthode Java qui est appelée par le conteneur lorsque la connexionWebSocket se ferme

3.2. Ecriture du point de terminaison du serveur

Nous déclarons un point de terminaison de serveur de classe JavaWebSocket en l'annotant avec@ServerEndpoint. Nous spécifions également l'URI sur lequel le noeud final est déployé. L'URI est défini relativement à la racine du conteneur du serveur et doit commencer par une barre oblique:

@ServerEndpoint(value = "/chat/{username}")
public class ChatEndpoint {

    @OnOpen
    public void onOpen(Session session) throws IOException {
        // Get session and WebSocket connection
    }

    @OnMessage
    public void onMessage(Session session, Message message) throws IOException {
        // Handle new messages
    }

    @OnClose
    public void onClose(Session session) throws IOException {
        // WebSocket connection closes
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        // Do error handling here
    }
}

Le code ci-dessus est le squelette d'extrémité de serveur pour notre application de type chat. Comme vous pouvez le constater, 4 annotations sont associées à leurs méthodes respectives. Ci-dessous, vous pouvez voir la mise en œuvre de telles méthodes:

@ServerEndpoint(value="/chat/{username}")
public class ChatEndpoint {

    private Session session;
    private static Set chatEndpoints
      = new CopyOnWriteArraySet<>();
    private static HashMap users = new HashMap<>();

    @OnOpen
    public void onOpen(
      Session session,
      @PathParam("username") String username) throws IOException {

        this.session = session;
        chatEndpoints.add(this);
        users.put(session.getId(), username);

        Message message = new Message();
        message.setFrom(username);
        message.setContent("Connected!");
        broadcast(message);
    }

    @OnMessage
    public void onMessage(Session session, Message message)
      throws IOException {

        message.setFrom(users.get(session.getId()));
        broadcast(message);
    }

    @OnClose
    public void onClose(Session session) throws IOException {

        chatEndpoints.remove(this);
        Message message = new Message();
        message.setFrom(users.get(session.getId()));
        message.setContent("Disconnected!");
        broadcast(message);
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        // Do error handling here
    }

    private static void broadcast(Message message)
      throws IOException, EncodeException {

        chatEndpoints.forEach(endpoint -> {
            synchronized (endpoint) {
                try {
                    endpoint.session.getBasicRemote().
                      sendObject(message);
                } catch (IOException | EncodeException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

Lorsqu'un nouvel utilisateur se connecte (@OnOpen) est immédiatement mappé à une structure de données d'utilisateurs actifs. Ensuite, un message est créé et envoyé à tous les points de terminaison à l'aide de la méthodebroadcast.

Cette méthode est également utilisée chaque fois qu'un nouveau message est envoyé (@OnMessage) par l'un des utilisateurs connectés - c'est l'objectif principal du chat.

Si à un moment donné une erreur se produit, la méthode avec l'annotation@OnError la gère. Vous pouvez utiliser cette méthode pour consigner les informations sur l'erreur et effacer les ordinateurs d'extrémité.

Enfin, lorsqu'un utilisateur n'est plus connecté au chat, la méthode@OnClose efface le point de terminaison et diffuse à tous les utilisateurs qu'un utilisateur a été déconnecté.

4. Types de messages

La spécification WebSocket prend en charge deux formats de données sur le réseau - texte et binaire. L'API prend en charge ces deux formats et ajoute des fonctionnalités permettant de travailler avec des objets Java et des messages de vérification de l'intégrité (ping-pong) tels que définis dans la spécification:

  • Text: toutes les données textuelles (java.lang.String, primitives ou leurs classes wrapper équivalentes)

  • Binary: données binaires (par ex. audio, image etc.) représenté par unjava.nio.ByteBuffer ou unbyte[] (tableau d'octets)

  • Java objects: L'API permet de travailler avec des représentations natives (objet Java) dans votre code et d'utiliser des transformateurs personnalisés (encodeurs / décodeurs) pour les convertir en formats on-wire compatibles (texte, binaire) autorisés par le WebSocket protocole

  • Ping-Pong: Unjavax.websocket.PongMessage est un accusé de réception envoyé par un homologue WebSocket en réponse à une demande de vérification de l'état (ping)

Pour notre application, nous utiliseronsJava Objects. Nous allons créer les classes pour encoder et décoder les messages.

4.1. Encodeur

Un encodeur prend un objet Java et produit une représentation typique pouvant être transmise sous forme de message, tel que JSON, XML ou binaire. Les encodeurs peuvent être utilisés en implémentant les interfacesEncoder.Text<T> ouEncoder.Binary<T>.

Dans le code ci-dessous, nous définissons la classeMessage à encoder et dans la méthodeencode nous utilisons Gson pour encoder l'objet Java en JSON:

public class Message {
    private String from;
    private String to;
    private String content;

    //standard constructors, getters, setters
}
public class MessageEncoder implements Encoder.Text {

    private static Gson gson = new Gson();

    @Override
    public String encode(Message message) throws EncodeException {
        return gson.toJson(message);
    }

    @Override
    public void init(EndpointConfig endpointConfig) {
        // Custom initialization logic
    }

    @Override
    public void destroy() {
        // Close resources
    }
}

4.2. Décodeur

Un décodeur est l'opposé d'un codeur et est utilisé pour transformer des données en un objet Java. Les décodeurs peuvent être implémentés à l'aide des interfacesDecoder.Text<T> ouDecoder.Binary<T>.

Comme nous l'avons vu avec l'encodeur, la méthodedecode est l'endroit où nous prenons le JSON récupéré dans le message envoyé au point de terminaison et utilisons Gson pour le transformer en une classe Java appeléeMessage:

public class MessageDecoder implements Decoder.Text {

    private static Gson gson = new Gson();

    @Override
    public Message decode(String s) throws DecodeException {
        return gson.fromJson(s, Message.class);
    }

    @Override
    public boolean willDecode(String s) {
        return (s != null);
    }

    @Override
    public void init(EndpointConfig endpointConfig) {
        // Custom initialization logic
    }

    @Override
    public void destroy() {
        // Close resources
    }
}

4.3. Définition de l'encodeur et du décodeur dans le point de terminaison du serveur

Assemblons tout en ajoutant les classes créées pour encoder et décoder les données à l'annotation de niveau classe@ServerEndpoint:

@ServerEndpoint(
  value="/chat/{username}",
  decoders = MessageDecoder.class,
  encoders = MessageEncoder.class )

Chaque fois que des messages sont envoyés au terminal, ils sont automatiquement convertis en objets JSON ou Java.

5. Conclusion

Dans cet article, nous avons examiné ce qu'est l'API Java pour WebSockets et comment cela peut nous aider à créer des applications telles que ce chat en temps réel.

Nous avons vu les deux modèles de programmation permettant de créer un noeud final: les annotations et les programmes. Nous avons défini un noeud final à l'aide du modèle d'annotation pour notre application, ainsi que des méthodes de cycle de vie.

De plus, afin de pouvoir communiquer entre le serveur et le client, nous avons constaté que nous avions besoin d'encodeurs et de décodeurs pour convertir les objets Java en JSON et inversement.

L'API JSR 356 est très simple et constitue un modèle de programmation basé sur des annotations qui facilite la création d'applications WebSocket.

Pour exécuter l'application que nous avons créée dans l'exemple, il suffit de déployer le fichier war sur un serveur Web et d'accéder à l'URL:http://localhost:8080/java-websocket/. Vous pouvez trouver le lien vers le référentielhere.