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
.
Let's look at the annotations in detail; here is the table of attributes for the annotation
@javax.websocket.ClientEndpoint
.
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?
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.
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.
3.147.71.94