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. Comme 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 examinerons 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:

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

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

Nous allons créer une application de type chat très simple. 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 fichier pom.xml :

 javax.websocket javax.websocket-api 1.1 

La dernière version peut être trouvée ici.

Afin de convertir les objets Java 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érentiel Maven Central.

3.1. Configuration du point final

Il existe deux façons de configurer les points de terminaison: basée sur des annotations et basée sur des extensions. Vous pouvez étendre la classe javax.websocket.Endpoint ou utiliser des annotations dédiées au niveau de la méthode. Comme le modèle d'annotation conduit à un code plus propre par rapport au modèle programmatique, l'annotation est devenue le choix conventionnel de codage. Dans ce cas, les événements de cycle de vie du point de terminaison WebSocket sont gérés par les annotations suivantes:

  • @ServerEndpoint: s'il est décoré avec @ServerEndpoint, le conteneur assure la disponibilité de la classe en tant que serveur WebSocket écoutant un espace URI spécifique
  • @ClientEndpoint : Une classe décorée avec cette annotation est traitée comme un client WebSocket
  • @OnOpen : une méthode Java avec @OnOpen est appelée par le conteneur lorsqu'une nouvelle connexion WebSocket est initiée
  • @OnMessage : une méthode Java, annotée avec @OnMessage, reçoit les informations du conteneur WebSocket lorsqu'un message est envoyé au point de terminaison
  • @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 appelée par le conteneur lorsque la connexion WebSocket se ferme

3.2. Ecriture du point de terminaison du serveur

Nous déclarons un point de terminaison de serveur WebSocket de classe Java en l'annotant avec @ServerEndpoint . Nous spécifions également l'URI où le point de terminaison est déployé. L'URI est défini par rapport à 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 du point de terminaison du serveur pour notre application de type chat. Comme vous pouvez le voir, nous avons 4 annotations mappées à leurs méthodes respectives. Ci-dessous, vous pouvez voir la mise en œuvre de ces 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éthode de diffusion .

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 points de terminaison.

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 on-wire - texte et binaire. L'API prend en charge ces deux formats, ajoute des fonctionnalités pour travailler avec des objets Java et des messages de vérification de l'état (ping-pong) comme défini dans la spécification:

  • Texte : toutes les données textuelles ( java.lang.String , primitives ou leurs classes wrapper équivalentes)
  • Binaire : données binaires (par exemple audio, image, etc.) représentées par un java.nio.ByteBuffer ou un byte [] (tableau d'octets)
  • Objets Java : 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 protocole WebSocket
  • Ping-Pong : un javax.websocket.PongMessage est un accusé de réception envoyé par un pair WebSocket en réponse à une demande de vérification de l'état (ping)

Pour notre application, nous utiliserons des objets Java. 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 adaptée à la transmission sous forme de message tel que JSON, XML ou représentation binaire. Les encodeurs peuvent être utilisés en implémentant les interfaces Encoder.Text ou Encoder.Binary .

Dans le code ci-dessous, nous définissons la classe Message à encoder et dans la méthode encoder, 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 encodeur et est utilisé pour transformer les données en un objet Java. Les décodeurs peuvent être implémentés à l'aide des interfaces Decoder.Text ou Decoder.Binary .

Comme nous l'avons vu avec l'encodeur, la méthode decode 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ée Message:

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

Mettons tout ensemble en ajoutant les classes créées pour encoder et décoder les données au niveau de 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 point de terminaison, ils seront 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 elle peut nous aider à créer des applications telles que ce chat en temps réel.

Nous avons vu les deux modèles de programmation pour créer un point de terminaison: les annotations et le programmatique. Nous avons défini un point de terminaison en utilisant le modèle d'annotation de notre application ainsi que les méthodes du cycle de vie.

Aussi, afin de pouvoir communiquer entre le serveur et le client, nous avons vu que nous avons besoin d'encodeurs et de décodeurs pour convertir les objets Java en JSON et vice versa.

L'API JSR 356 est très simple et le modèle de programmation basé sur les annotations 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: // localhost: 8080 / java-websocket /. Vous pouvez trouver le lien vers le référentiel ici.