JavaEnterprise JavaDeveloping WebSocket Client/Server Endpoints

Developing WebSocket Client/Server Endpoints

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Communication in a WebSocket-based client/server application begins with a handshake. The server publishes the WebSocket endpoint in the form of an URI that the client clings to for subsequent data transfer. It virtually establishes a communication freeway between the client and server ends. This agreement between both the parties is in sync as long as a connection remains unbroken. It’s a costly affair to remain in connection if the data transfer rate is infrequent. WebSocket protocol, however, reduced the exchange of a heavyweight HTTP header to a bare minimum. This definitely minimized throughput latency. But still, holding on to a connection in every situation is not a good idea. WebSocket is particularly suitable for those applications that require high, real-time throughput with low latency. The hacks such as Polling, Long Polling, and so forth, prior to this, are more an attempt to replicate the idea of real time communication, whereas WebSocket is the answer to the quest, at least for now. The article explores ways to implement client/server WebSocket endpoints with Java in EE7.

WebSocket APIs in the Java EE7 Framework

Java EE7 ships a very compact WebSocket API structure in two packages: javax.websocket.server and javax.websocket. The javax.websocket.server package consists of APIs for creating server endpoints and javax.websocket provides the necessary APIs for building client endpoints. From the perspective of WebSocket programming, ‘endpoint’ grossly means destinations where request response messages are processed. This is represented by a Java object that handles WebSocket conversations. There are two ways to create these endpoints:

  • Programmatic creation of endpoints
  • Annotation-based creation of endpoints

Annotation makes far cleaner code than the programmatic expression of creating endpoints. Because there are two choices to achieve the same end, annotation became the conventional and obvious choice of coding. WebSocket endpoint life cycle events are handled with the help of the following annotations:

  • @OnOpen: Decorates the function associated with handling a connection open event
  • @OnMessage: Decorates the function associated with handling an event on message received
  • @OnError: Decorates the function associated with handling a connection error event
  • @OnClose: Decorates the function associated with handling an event on connection close

Before going any further, let’s get a glimpse of a very simplified but fully functional WebSocket-based client-server application.

Annotation-based Server Endpoints Example

The application is very simple: The client sends a message; the server responds back. That’s it. Observe how the onMessage function gets hold of the session object to send the messages. A POJO is marked as a server with the annotation @ServerEndpoint.

package org.mano.websocket;

import java.io.IOException;
import java.util.Random;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/testserver")
public class TestWebsocketServer {
   private static int count=1;

   @OnMessage
   public void onMessage(Session session,
         String message) {
      if(message.trim().isEmpty())
         return;
      try {
         session.getBasicRemote().sendText
            ("Received message "+count+": " + message);
         session.getBasicRemote().sendText
            ("Lucky number! "+new Random().nextInt(99));
         session.getBasicRemote().sendText
            ("-----------------------------------");
      } catch (IOException ex) {

      }
      count++;
   }
}

Each endpoint class instance associates one connection only. As a result, it is often convenient to store client state information using session instance variables. For example, the get and put methods can be invoked to retrieve and store client information respectively in the following manner.

String oldMessage = (String) session.getUserProperties()
   .get("oldMessage");
session.getUserProperties().put("oldMessage", newMessage);

Here, messages are sending to a single client. A server, however, can send messages to multiple clients with a little modification of the preceding code.

@OnMessage
   public void onMessage(Session session, String message) {
      //...
      for (Session s : session.getOpenSessions()) {
         if (s.isOpen()){
         session.getBasicRemote().sendText("Lucky number!
            "+new Random().nextInt(99));                    }
      }

      //...
   }

The client part that initiates the interaction is written in simple HTML with JavaScript aid.

<!DOCTYPE html>
<html>
   <head>
      <title>TODO supply a title</title>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width,
         initial-scale=1.0">

      <script type="text/javascript">
         var uri = "ws://" + window.location.host +
            "/WebsocketTest/testserver";
         var websocket = null;
         var message = "";
         function openConnection() {
            websocket = new WebSocket(uri);
            websocket.onmessage = function (event) {
               var node = document.getElementById('fromServer');
               var newNode = document.createElement('h1');
               newNode.appendChild(document.
                  createTextNode(event.data));
               node.appendChild(newNode);
            };
         }

         function closeConnection() {
            websocket.close();
         }

         function sendMessage() {
            var msg = document.getElementById('messageText').value;
            websocket.send(msg);
         }
      </script>

   </head>
   <body onload="openConnection();" onunload="closeConnection();">
      <div>
         <p>Client Message: <input id="messageText" type="text"/>
            <input id="sendButton" type="button" value="Send"
               onclick="sendMessage();"/>
         </p>
         <div id="fromServer"></div>
      </div>
   </body>
</html>

Message Transfer

Wired messages to be transferred are composed of frames. At a low level, these (one/more) frames carry the payload of text messages encoded as UTF-8 or binary format. The data intended for protocol level signaling is carried by control frames. In general, the control frames are close, ping, and pong. (Pong refers to the response of ping frame.) Apart from containing control signal, ping and pong may also contain application data. The annotation decorator for message processing is @OnMessage.

Some variation of the onMessage function can be as follows.

For String,

@OnMessage
public void onMessage(String message) {...}

For a Java integer primitive

@OnMessage
public void onMessage(int ival) {...}

or,

@OnMessage
public void onMessage(String message,
   boolean end) {...}

In this case, the Boolean parameter determines with a true value that the last part of a large string is received and there is no more left. A false value simply indicates there are parts remaining, yet to be received. This is particularly useful in sending a huge chunk of message in n parts.

If we want to send a block stream of a binary message, the onMessage function can be as follows.

@OnMessage
public void onMessage(Reader reader) {...}

or,

@OnMessage
public void onMessage(InputStream stream) {...}

Annotation-based Client Endpoints Example

In some situations, we want to create a standalone WebSocket client that interacts with the server endpoint. A POJO can be decorated as a WebSocket client with the help of @ClientEndpoint. The client endpoint can interact with any server endpoint as long as it knows the WebSocket server URI. In the previous example, the client UI part was written in HTML+JavaScript. Here, a POJO is converted into a WebSocket client interacting with the same server (refer to the previous example), and the client UI part is written in Java Swing.

package org.mano.websocket;

import java.io.IOException;
import java.net.URI;
import javax.websocket.ClientEndpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;

@ClientEndpoint
public class TestWebsocketClient {
   private final String uri="ws://localhost:8080/
      WebsocketTest/testserver";
   private Session session;
   private ClientWindow clientWindow;

   public TestWebsocketClient(ClientWindow cw){
      clientWindow=cw;
      try{
         WebSocketContainer container=ContainerProvider.
            getWebSocketContainer();
         container.connectToServer(this, new URI(uri));

      }catch(Exception ex){

      }
   }

   @OnOpen
   public void onOpen(Session session){
      this.session=session;
   }

   @OnMessage
   public void onMessage(String message, Session session){
      clientWindow.writeServerMessage(message);
   }

   public void sendMessage(String message){
      try {
         session.getBasicRemote().sendText(message);
      } catch (IOException ex) {

      }
   }
}

The following class provides a Java Swing user interface for the client endpoint.

package org.mano.websocket;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class ClientWindow extends JFrame {

   private final JLabel messageLabel =
      new JLabel("Client Message");
   private final JTextField messageField =
      new JTextField(10);
   private final JButton sendButton =
      new JButton("Send");
   private final JTextArea serverMessageText =
      new JTextArea("");
   private final TestWebsocketClient client;

   public ClientWindow() {
      setSize(400, 400);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setVisible(true);
      getContentPane().setLayout(new BorderLayout(10, 10));

      JPanel p = new JPanel();
      p.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
      p.add(messageLabel);
      p.add(messageField);
      p.add(sendButton);

      add(p, BorderLayout.NORTH);
      JScrollPane scroll = new JScrollPane(
         JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
         JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
      scroll.setViewportView(serverMessageText);
      add(scroll, BorderLayout.CENTER);

      client = new TestWebsocketClient(this);

      sendButton.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
            client.sendMessage(messageField.getText());
         }
      });
   }

   public void writeServerMessage(String message) {
      serverMessageText.setText(serverMessageText.getText()
         + "n" + message);
   }

   public static void main(String[] args) {
      ClientWindow clientWindow = new ClientWindow();
   }
}

Encoder and Decoder

Message transmission in a WebSocket cannot always be expected to be of known types such as String, int, Reader, and so on. Requirements are varied, and so are the types. In view of the situation, Java WebSocket APIs have a provision for sending and receiving a custom Java type’s data. This is possible with the help of the Encoder and Decoder interface of the javax.websocket package. An Encoder basically takes a Java object and produces a typical representation suitable for transmission as a WebSocket message such as JSON, XML, or binary representation. A Decoder is just the opposite, used to transform data back into a Java object.

Encoders can be used by implementing the Encoder.Text<T> or Encoder.Binary<T> interfaces. These interfaces specify the encode method supposed to be custom defined. For example,

public String encode(MyMessage msgObj)
      throws EncodeException {
   return msgObj.getJsonObject.toString();
}

In a similar manner, Decoders can be implemented using Decoder.Text<T> or Decoder.Binary<T> interfaces. These interfaces specify the decode method supposed to be custom defined. For example,

public String decode(String msg) throws DecodeException {
   MyMessage myMessage = new MyMessage(
   Json.createReader(new StringReader(string)).readObject());
   return myMessage;
}

Conclusion

The article provides a very high-level overview on how to implement client/server endpoints. There are complicated concepts involved behind the scenes and a variety of other features to manipulate message processing. One of the bad effects of simplification is that lot remains unsaid. But, it is better to start simple than to entangle with spaghetti construct at the outset. In the coming days, a lot of client/server communication shall be seen with WebSocket. In fact, WebSocket opened a horizon of ideas in the arena of real-time Web application. Java EE7 took no time to pick up the ideas and provide APIs for the development. Programmers have to pocket the concepts sooner than later; there is no choice.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories