Una guida all'API Java per WebSocket

1. Panoramica

WebSocket fornisce un'alternativa alla limitazione della comunicazione efficiente tra il server e il browser web fornendo comunicazioni client / server bidirezionali, full-duplex e in tempo reale. Il server può inviare dati al client in qualsiasi momento. Poiché funziona su TCP, fornisce anche una comunicazione di basso livello a bassa latenza e riduce il sovraccarico di ogni messaggio .

In questo articolo daremo un'occhiata all'API Java per WebSocket creando un'applicazione simile a una chat.

2. JSR 356

JSR 356 o l'API Java per WebSocket, specifica un'API che gli sviluppatori Java possono utilizzare per integrare WebSocket con le loro applicazioni, sia sul lato server che sul lato client Java.

Questa API Java fornisce componenti lato server e client:

  • Server : tutto nel pacchetto javax.websocket.server .
  • Client : il contenuto del pacchetto javax.websocket , che consiste in API lato client e anche librerie comuni sia per il server che per il client.

3. Creazione di una chat utilizzando WebSocket

Creeremo un'applicazione molto semplice simile a una chat. Qualsiasi utente potrà aprire la chat da qualsiasi browser, digitare il proprio nome, accedere alla chat e iniziare a comunicare con tutti coloro che sono connessi alla chat.

Inizieremo aggiungendo l'ultima dipendenza al file pom.xml :

 javax.websocket javax.websocket-api 1.1 

L'ultima versione può essere trovata qui.

Per convertire gli oggetti Java nelle loro rappresentazioni JSON e viceversa, utilizzeremo Gson:

 com.google.code.gson gson 2.8.0 

L'ultima versione è disponibile nel repository Maven Central.

3.1. Configurazione endpoint

Esistono due modi per configurare gli endpoint: basati su annotazioni e basati su estensione. È possibile estendere la classe javax.websocket.Endpoint o utilizzare annotazioni a livello di metodo dedicate. Poiché il modello di annotazione porta a un codice più pulito rispetto al modello programmatico, l'annotazione è diventata la scelta convenzionale di codifica. In questo caso, gli eventi del ciclo di vita dell'endpoint WebSocket vengono gestiti dalle seguenti annotazioni:

  • @ServerEndpoint: se decorato con @ServerEndpoint, il contenitore garantisce la disponibilità della classe come server WebSocket in ascolto di uno spazio URI specifico
  • @ClientEndpoint : una classe decorata con questa annotazione viene considerata come un client WebSocket
  • @OnOpen : un metodo Java con @OnOpen viene richiamato dal contenitore quando viene avviata una nuova connessione WebSocket
  • @OnMessage : un metodo Java, annotato con @OnMessage, riceve le informazioni dal contenitore WebSocket quando un messaggio viene inviato all'endpoint
  • @OnError : viene richiamato un metodo con @OnError quando si verifica un problema con la comunicazione
  • @OnClose : utilizzato per decorare un metodo Java che viene chiamato dal contenitore quando la connessione WebSocket si chiude

3.2. Scrittura dell'endpoint del server

Dichiariamo un endpoint del server WebSocket di classe Java annotandolo con @ServerEndpoint . Specifichiamo anche l'URI in cui viene distribuito l'endpoint. L'URI è definito relativamente alla radice del contenitore del server e deve iniziare con una barra:

@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 } }

Il codice sopra è lo scheletro dell'endpoint del server per la nostra applicazione simile a una chat. Come puoi vedere, abbiamo 4 annotazioni mappate ai rispettivi metodi. Di seguito puoi vedere l'implementazione di tali metodi:

@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(); } } }); } }

Quando un nuovo utente accede ( @OnOpen ) viene immediatamente mappato a una struttura dati di utenti attivi. Quindi, viene creato un messaggio e inviato a tutti gli endpoint utilizzando il metodo di trasmissione .

Questo metodo viene utilizzato anche ogni volta che viene inviato un nuovo messaggio ( @OnMessage ) da uno qualsiasi degli utenti collegati: questo è lo scopo principale della chat.

Se a un certo punto si verifica un errore, il metodo con l'annotazione @OnError lo gestisce. È possibile utilizzare questo metodo per registrare le informazioni sull'errore e cancellare gli endpoint.

Infine, quando un utente non è più connesso alla chat, il metodo @OnClose cancella l'endpoint e trasmette a tutti gli utenti che un utente è stato disconnesso.

4. Tipi di messaggio

La specifica WebSocket supporta due formati di dati in linea: testo e binario. L'API supporta entrambi questi formati, aggiunge funzionalità per lavorare con oggetti Java e messaggi di controllo dello stato (ping-pong) come definito nella specifica:

  • Testo : qualsiasi dato testuale ( java.lang.String , primitive o le loro classi wrapper equivalenti)
  • Binario : dati binari (es. Audio, immagine ecc.) Rappresentati da java.nio.ByteBuffer o byte [] (array di byte)
  • Oggetti Java : l'API consente di lavorare con rappresentazioni native (oggetti Java) nel codice e utilizzare trasformatori personalizzati (codificatori / decodificatori) per convertirli in formati on-wire compatibili (testo, binari) consentiti dal protocollo WebSocket
  • Ping-Pong : un javax.websocket.PongMessage è un riconoscimento inviato da un peer WebSocket in risposta a una richiesta di controllo dello stato (ping)

Per la nostra applicazione utilizzeremo Java Objects. Creeremo le classi per la codifica e la decodifica dei messaggi.

4.1. Codificatore

Un codificatore prende un oggetto Java e produce una tipica rappresentazione adatta per la trasmissione come un messaggio come JSON, XML o rappresentazione binaria. Gli encoder possono essere utilizzati implementando le interfacce Encoder.Text o Encoder.Binary .

Nel codice seguente definiamo la classe Message da codificare e nel metodo encode utilizziamo Gson per codificare l'oggetto Java in 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. Decoder

Un decodificatore è l'opposto di un codificatore e viene utilizzato per trasformare i dati in un oggetto Java. I decoder possono essere implementati utilizzando le interfacce Decoder.Text o Decoder.Binary .

Come abbiamo visto con il codificatore, il metodo di decodifica è dove prendiamo il JSON recuperato nel messaggio inviato all'endpoint e usiamo Gson per trasformarlo in una classe Java chiamata 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. Impostazione del codificatore e del decodificatore nell'endpoint del server

Mettiamo insieme tutto aggiungendo le classi create per la codifica e la decodifica dei dati all'annotazione a livello di classe @ServerEndpoint :

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

Ogni volta che i messaggi vengono inviati all'endpoint, verranno automaticamente convertiti in oggetti JSON o Java.

5. conclusione

In questo articolo, abbiamo esaminato cos'è l'API Java per WebSocket e come può aiutarci a creare applicazioni come questa chat in tempo reale.

Abbiamo visto i due modelli di programmazione per la creazione di un endpoint: annotazioni e programmatico. Abbiamo definito un endpoint utilizzando il modello di annotazione per la nostra applicazione insieme ai metodi del ciclo di vita.

Inoltre, per poter comunicare avanti e indietro tra il server e il client, abbiamo visto che abbiamo bisogno di codificatori e decodificatori per convertire oggetti Java in JSON e viceversa.

L'API JSR 356 è molto semplice e il modello di programmazione basato su annotazioni che semplifica la creazione di applicazioni WebSocket.

Per eseguire l'applicazione che abbiamo creato nell'esempio, tutto ciò che dobbiamo fare è distribuire il file war in un server web e andare all'URL: // localhost: 8080 / java-websocket /. Puoi trovare il link al repository qui.