Client-side Java WebSockets

The Java WebSocket API also allows developers to write client endpoints. They can be easily defined with annotation and of course with more difficulty by programming against the configuration API.

The annotation @javax.websock.ClientEndpoint denotes a client specific WebSocket endpoint.

Here is the code for a sample client WebSocket:

package je7hb.websocket.basic;
import javax.websocket.ClientEndpoint;
import javax.websocket.OnMessage;
import javax.websocket.Session;

@ClientEndpoint
public class ClientEchoEndpoint {

  @OnMessage
  public void messageReceived( Session session, String text ) {
    System.out.printf("Message server text: %s
", text);
  }
}

The client ClientEchoEndpoint is defined as an annotated WebSocket endpoint. The implementation will register this class as a WebSocket event handler and when messages are sent to this connection the implementation will invoke the life-cycle event handler messageReceived(). The method messageReceived() is annotated with @OnMessage.

@ClientEndpoint

Let's look at the annotations in detail; here is the table of attributes for the annotation @javax.websocket.ClientEndpoint.

Attribute

Type

Description

Default Value

subProtocols

String []

Defines an ordered array of WebSocket protocols that this client supports.

None

decoders

Decoder[]

Specifies an ordered array of encoder classes this client will use.

None

decoders

Encoder[]

Specifies an ordered array of encoder classes that the client will use.

None

configurator

Class<? Extends ClientEndPoint-Configurator>

Defines a custom configurator that the developer would like to use to configure a logical client endpoint instance.

ClientEndpoint-Configurator.class

Annotated client example

A WebSocket client is useful for publish and subscribe clients, where the client makes a connection to the server and then changes its mode of operation to that of mostly reading data from the server endpoint. Real-time data can be information about products, inventory updates, any row change in a database, or any data that requires notification.

We will use a streaming service that wraps a single price as an example. We will create an annotated @ClientEndpoint.

@ClientEndpoint
public class ClientPriceReaderEndpoint {

  @OnOpen
  public void openRemoteConnection( Session session) {
    System.out.printf(
     "%s.openRemoteConnection( session = [%s], ",
      getClass().getSimpleName(), session);
  }

  @OnMessage
  public void messageReceived( Session session, String text ) {
    System.out.printf(">>>> RECEIVED text : %s
", text);
  }

  @OnClose
  public void closeRemote(CloseReason reason, Session session) {
    System.out.printf(
      "%s.closeRemote() session = [%s], reason=%s",
      getClass().getSimpleName(), session, reason);
  }  

}

We annotate this type ClientPriceReaderEndpoint as @ClientEndPoint, which means it is able to receive messages. This endpoint has similar life-cycle methods @OnMessage, for a message that is received and @OnOpen for when a remote connection is established. With @OnClose annotated method, in particular, note that you can find out why the connection was closed with the CloseReason parameter.

You may wonder what is the difference between a @ClientEndPoint and a@ServerEndPoint, especially since we have been told that HTML5 WebSocket is the peer-to-peer connections? A client-side connection is not made available to the URI space of the web server. In other words, this is a Java implementation design feature in that client-side WebSockets do not have URIs.

A client needs a WebSocket server endpoint to connect to, so let's examine a server-side one that continuously updates a single price:

package je7hb.websocket.basic;
import javax.enterprise.concurrent.*;
import javax.enterprise.context.ApplicationScoped;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
/* ... */

@ApplicationScoped
@ServerEndpoint("/streamingPrice")
public class StreamingPriceWebSocketServer {
  @Resource(name = "concurrent/ScheduledTasksExecutor")
  ManagedScheduledExecutorService executorService;
private Object lock = new Object();
  private BigDecimal price = new BigDecimal("1000.0");
  private BigDecimal unitPrice = new BigDecimal("0.01");

  @OnOpen
  public void openRemoteConnection( final Session session) {
    executorService.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        try {
          session.getBasicRemote()
            .sendText("PRICE = " + price);
          synchronized (lock) {
            if (Math.random() < 0.5) {
              price = price.subtract(unitPrice);
            } else {
              price = price.add(unitPrice);
            }
          }
        } catch (IOException e) {
          e.printStackTrace(System.err);
        }
      }
    }, 500, 500, MILLISECONDS);
  }
}

This type StreamingPriceWebSocketServer is a server-side endpoint, because it is annotated with @ServerEndpoint and it occupies the URI space of /streamingPrice after the web context path. For demonstration purposes only, we cheat here by taking advantage of the @OnOpen lifecycle to create a scheduled managed task. You can read about Managed Executors in Appendix D, Java EE 7 Assorted Topics. Essentially, the managed task, the anonymous inner class Runnable is executed every 500 milliseconds with an initial delay of 500 milliseconds.

The managed task has a reference to the incoming Session instance. It retrieves a BasicRemote in order to send update text messages about the price. We use a random generator to move the price up or down by a unit, which happens inside a synchronization block. The managed task sends the latest price update to the peer remote WebSocket. The task ends afterwards and the ManagedScheduledExecutorService will invoke a new task at the next period.

Advanced readers will know that there are a couple of issues with this demonstration code. What happens to the concurrent managed task when the peer WebSocket closes or the network is broken? How does this implementation scale with multiple peers?

Tip

Whenever you acquire a resource, ensure that you release it. Whenever you spawn a managed task, also code the means to halt it gracefully. Failure to enforce this advice could be ruinous in the long-term running of your application.

In the book's source code, there is an embedded runner that demonstrates Java client-side WebSocket. A standalone application needs to tell the WebSocket framework that a client endpoint has a connection to a remote server. Here is how this is done:

WebSocketContainer container = 
  ContainerProvider.getWebSocketContainer();
container.connectToServer(ClientPriceReaderEndpoint.class,
new URI("ws://localhost:8080/mywebapp/streamingPrice"));

We retrieve a javax.websocket.WebSocketContainer from the ContainerProvider. The container is the application view of the Java WebSocket implementation. This service provider interface has a key method connectToServer, which registers an Endpoint with the container. The client endpoint is associated with the remote URI.

The book's source code also deploys an example JSP page. Point your browser to http://localhost:8080/mywebapp/index.jsp after launching the embedded runner.

Remote endpoints

Any Java WebSocket can send a message to the peer by taking a Session instance and retrieving a RemoteEndpoint reference.

RemoteEndpoint.Basic remote = session.getBasicRemote();
remote.sendText("This is it!");

This code is valid for client and server-side web sockets. The RemoteEndpoint.Basic is a representation of the peer. The RemoteEndpoint.Async is the interface for asynchronous communications. The developer cannot only send text messages, but they can also write binary messages and for the more advanced Ping and Pong messages.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.71.94