Thursday Apr 04, 2013

WebSocket Client and Server Endpoint: Annotated and Programmatic (TOTD #212)


JSR 356 defines Java API for WebSocket 1.0. It defines a standard-based programming model for creating WebSocket client and server endpoint. Both kind of endpoints can be created programmatically or using annotations. This Tip Of The Day (TOTD) provide short snippets of how to write a WebSocket client and server endpoint programmatically or using annotations.

The complete source code in this sample can be downloaded from here.

Lets start with annotation-based server endpoint.

@ServerEndpoint("/websocket")
public class MyEndpoint {
   
  @OnMessage
  public String echoText(String name) {
    return name;
  }
}

@ServerEndpoint marks the POJO as a WebSocket server endpoint. URI of the deployed endpoint is as value attribute of the annotation.  echoText method is invoked whenever a message with text payload is received by this endpoint. Payload of the message is mapped to the parameter name. A synchronous response is returned to the client using the return value.

Programmatic server endpoint can be defined as:

public class MyEndpoint extends Endpoint {

  @Override
  public void onOpen(final Session session, EndpointConfig ec) {
    session.addMessageHandler(new MessageHandler.Whole<String>() {

      @Override
      public void onMessage(String text) {
        try {
          session.getBasicRemote().sendText(text);
        } catch (IOException ex) {
          Logger.getLogger(MyEndpoint.class.getName()).log(Level.SEVERE, null, ex);
        }
      }
  });
}

A programmatic server endpoint is defined by extending Endpoint abstract class. onOpen method is overridden to be notified of when a new conversation has started. Session captures the other end of the conversation. EndpointConfig identifies the configuration object used to configure this endpoint. Multiple MessageHandlers are registered to handle text, binary, and pong messages. The first parameter of onOpen captures the other end of the conversation. A synchronous response to the client is sent by calling getBasicRemote().sendText(...) method.

Programmatic server endpoint needs to be configured using ServerApplicationConfig.

public class MyApplicationConfig implements ServerApplicationConfig {

  @Override
  public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> set) {
    return new HashSet<ServerEndpointConfig>() {
      {
        add(ServerEndpointConfig.Builder
            .create(MyEndpoint.class, "/websocket")
            .build());
      }
    };
  }

  @Override
  public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> set) {
    return Collections.emptySet();
  }
}

WebSocket runtime scans the WAR file with all implementations of ServerApplicationConfig and registers the endpoint returned from getEndpointConfigs and getAnnotatedEndpointClasses. The URI of the server endpoint is published using ServerEndpointConfig.Builder.

Now lets take a look at annotated client endpoint.

@ClientEndpoint
public class MyClient {
  @OnOpen
  public void onOpen(Session session) {
    try {
      String name = "Duke";
      System.out.println("Sending message to endpoint: " + name);
      session.getBasicRemote().sendText(name);
    } catch (IOException ex) {
      Logger.getLogger(MyClient.class.getName()).log(Level.SEVERE, null, ex);
    }
  }
}

@ClientEndpoint marks the POJO as a WebSocket client endpoint. onOpen method is invoked whenever a new WebSocket connection is opened and is identified by @OnOpen annotation. Session captures the other end of the conversation. A synchronous message is sent to the server using session.getBasicRemote.sendText() method.

This client can connect to the endpoint as:

WebSocketContainer container = ContainerProvider.getWebSocketContainer();
String uri = "ws://localhost:8080" + request.getContextPath() + "/websocket";
container.connectToServer(MyClient.class, URI.create(uri));

And finally programmatic client endpoint.

public class MyClient extends Endpoint {
  @Override
  public void onOpen(final Session session, EndpointConfig ec) {
    session.addMessageHandler(new MessageHandler.Whole<String>() {

      @Override
      public void onMessage(String text) {
        System.out.println("Received response in client from endpoint: " + text);
      }
  });
  try {
    String name = "Duke";
      System.out.println("Sending message from client -> endpoint: " + name);
      session.getBasicRemote().sendText(name);
    } catch (IOException ex) {
      Logger.getLogger(MyClient.class.getName()).log(Level.SEVERE, null, ex);
    }
  }
}


The first parameter of onOpen captures the other end of the conversation.  EndpointConfig identifies the configuration object used to configure this endpoint. Multiple MessageHandlers are registered to handle text, binary, and pong messages. onMessage method is called whenever a message is received from the endpoint. A synchronous request to the server is sent by calling getBasicRemote().sendText(...) method.

This client can connect to the endpoint as:

WebSocketContainer container = ContainerProvider.getWebSocketContainer();
String uri = "ws://localhost:8080" + request.getContextPath() + "/websocket";
container.connectToServer(MyClient.class,
                null,
                URI.create(uri));

Now go download your GlassFish b82, samples source code, and run them.


Thursday Feb 28, 2013

Updating to latest WebSocket API - Starting with GlassFish b78 (TOTD #209)


Java API for WebSocket (JSR 356) has recently gone through a major refactoring. The changes are integrated in Tyrus b12 and now available in GlassFish b78. This Tip Of The Day (TOTD) shows the change log for WebSocket endpoint in Collaborative Whiteboard Sample (explained in TOTD #189) between GlassFish b76 and b78.

First of all, everything stays in javax.websocket.* and javax.websocket.server.* package.

There are name changes in annotations, APIs, class renames, and some behavior change as well. Pavel's blog provides a comprehensive list of changes and the annotation changes are listed here as well:

Name before b78
Name after b78
@WebSocketEndpoint
@ServerEndpoint
@WebSocketClient
@ClientEndpoint
@WebSocketMessage
@OnMessage
@WebSocketPathParam @PathParam
@WebSocketOpen
@OnOpen
@WebSocketClose
@OnClose
@WebSocketError
@OnError


Here is the change log for updating Whiteboard.java:

--- <html>Whiteboard.java (<b>849f919</b>)</html>
+++ <html><b>Current File</b></html>
@@ -45,50 +45,48 @@
import java.util.HashSet;
import java.util.Set;
import javax.websocket.EncodeException;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
import javax.websocket.Session;
-import javax.websocket.WebSocketClose;
-import javax.websocket.WebSocketMessage;
-import javax.websocket.WebSocketOpen;
-import javax.websocket.server.WebSocketEndpoint;
+import javax.websocket.server.ServerEndpoint;

/**
* @author Arun Gupta
*/
-@WebSocketEndpoint(value = "/websocket",
+@ServerEndpoint(value = "/websocket",
encoders = {FigureEncoder.class},
decoders = {FigureDecoder.class})
public class Whiteboard {

private static Set<Session> peers = Collections.synchronizedSet(new HashSet<Session>());

- @WebSocketOpen
+ @OnOpen
public void onOpen(Session peer) {
peers.add(peer);
}

- @WebSocketClose
+ @OnClose
public void onClose(Session peer) {
peers.remove(peer);
}

- @WebSocketMessage
+ @OnMessage
public void boradcastFigure(Figure figure, Session session) throws IOException, EncodeException {
System.out.println("boradcastFigure: " + figure);
for (Session peer : peers) {
if (!peer.equals(session)) {
- peer.getRemote().sendObject(figure);
+ peer.getBasicRemote().sendObject(figure);
}
}
}

- @WebSocketMessage
+ @OnMessage
public void broadcastSnapshot(ByteBuffer data, Session session) throws IOException {
System.out.println("broadcastBinary: " + data);
for (Session peer : peers) {
if (!peer.equals(session)) {
- peer.getRemote().sendBytes(data);
+ peer.getBasicRemote().sendBinary(data);
}
}
}

The updated source code for the sample is available here and runs on GlassFish b78.

Hopefully this gives you a good idea on how to migrate your WebSocket applications from the older to the newer API. The changes are still evolving and the specification (PDF) and javadocs (ZIP) are the definitive source for the latest information. The complete set of latest spec downloads are available here.

As always, send the spec feedback to users@websocket-spec.java.net and implementation feedback to users@tyrus.java.net.

Tuesday Feb 26, 2013

WebSocket Java Client API in GlassFish 4 (TOTD #208)


The WebSocket API by W3C defines an API that enables Web pages to use the WebSocket protocol for two-way communication with a remote host. Java API for WebSocket (JSR 356) has added an ability to invoke a WebSocket endpoint from a Java application. This Tip Of The Day (TOTD) will explain how you can use that API for invoking a WebSocket endpoint.

JSR 356 introduces a class-level annotation @WebSocketClient that converts a POJO into a WebSocket client. The usual lifecycle annotations such as @WebSocketOpen, @WebSocketClose, @WebSocketError, and @WebSocketMessage can be specified on methods.

@WebSocketClient
public class MyClientEndpoint {
@WebSocketOpen
public void onOpen(Session session) {
System.out.println("Connected to endpoint: " + session.getRemote());
try {
String name = "Duke";
System.out.println("Sending message to endpoint: " + name);
session.getRemote().sendString(name);
} catch (IOException ex) {
Logger.getLogger(MyClientEndpoint.class.getName()).log(Level.SEVERE, null, ex);
}
}

@WebSocketMessage
public void processMessage(String message) {
System.out.println("Received message in client: " + message);
Client.messageLatch.countDown();
}
}
This code sends a message to the connected WebSocket endpoint from the onOpen method and prints the message when it comes back. This endpoint is bootstrapped using a separate class:

public class Client {

final static CountDownLatch messageLatch = new CountDownLatch(1);

public static void main(String[] args) {
try {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
String uri = "ws://echo.websocket.org:80/";
System.out.println("Connecting to " + uri);
container.connectToServer(MyClientEndpoint.class, URI.create(uri));
messageLatch.await(100, TimeUnit.SECONDS);
} catch (DeploymentException | InterruptedException ex) {
Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Note that a CountDownLatch is used to ensure that there is enough time for the client to connect to the endpoint. The latch counts down in MyClientEndpoint.processMessage once the echo message is received from the endpoint.

The source code can be downloaded here, connects to a remote WebSocket endpoint, and displays the output on the console.

The Maven dependency for now are specified as:
<dependencies>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-container-grizzly</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

Hopefully these could be specified as javax dependencies only!

The latest specification draft and javadocs can be downloaded here. Share your feedback at users@websocket-spec.java.net (for the specification) or users@tyrus.java.net (for the implenentation).

Monday Feb 25, 2013

Chat Sever using WebSocket in GlassFish 4 (TOTD #207)


A canonical example of using WebSocket is a chat server. This Tip Of The Day (TOTD) will share the code for building exactly that!

The complete code in this TOTD is available here.

Here is the code for the Chat Server built as a WebSocket endpoint:

@WebSocketEndpoint("/websocket")
public class ChatEndpoint {
private static final Set<Session> peers = Collections.synchronizedSet(new HashSet<Session>());

@WebSocketOpen
public void onOpen(Session peer) {
peers.add(peer);
}

@WebSocketClose
public void onClose(Session peer) {
peers.remove(peer);
}

@WebSocketMessage
public void message(String message, Session client) throws IOException, EncodeException {
for (Session peer : peers) {
peer.getRemote().sendObject(message);
}
}
}

In this code:
  • Lifecycle callbacks manage the coordinates of the connecting clients
  • message method receives all the messages sent by the client and then transmits to all the connected clients.
Simple, isn't it ?

You can use a trivial JavaScript to exchange messages between two browsers. A chat session between two Chrome and Firefox is shown below:



Download the source code and run it in GlassFish 4 b77.

Wednesday Feb 13, 2013

@WebSocketClient in GlassFish 4 (TOTD #204)


Tyrus is the Reference Implementation for Java API for WebSocket (JSR 356) and is already integrated in GlassFish 4. The implementation is rapidly evolving and and adding support for different features already defined in the latest version of the specification.

The latest integration in GlassFish added support for creating Java-based WebSocket clients.

Any POJO can be made a WebSocket client by adding @WebSocketClient. A inbound message is received in a method annotated with @WebSocketMessage. Any method can be designated as lifecycle method by specifying @WebSocketOpen, @WebSocketClose, @WebSocketError. Here is a simple sample:

@WebSocketClient
public class MyClient {
@WebSocketOpen
public void onOpen(Session session) {
System.out.println("Connected to endpoint: " + session.getRemote());
try {
session.getRemote().sendString("Duke");
} catch (IOException ex) {
Logger.getLogger(MyClient.class.getName()).log(Level.SEVERE, null, ex);
}
}

@WebSocketMessage
public void processMessage(String message, Session session) {
System.out.println(getClass().getName() + ": " + message);
}
}

In this code, a message is sent to the connected WebSocket endpoint whenever the connection is initiated. An inbound message (coming from the server) is processed using processMessage method.

The endpoint code is pretty trivial:

@WebSocketEndpoint(value="/websocket", configuration=DefaultServerConfiguration.class)
public class MyEndpoint {

@WebSocketMessage
public String sayHello(String name) {
System.out.println(getClass().getName() + ": " + name);
return "Hello " + name;
}

}
The endpoint has a single method that receives the payload, appends a string and returns back as the response. Even though server is returning the response right away, the client views this as two separate messages, one outbound and the other one inbound. So any message-exchange-pattern such as request/response need to be specifically built using the application code. The configuration attribute, in this case, is optional and will be fixed in a subsequent release of Tyrus.

The client can bootstrap using Java SE or Java EE components as:

WebSocketContainer container = ContainerProvider.getWebSocketContainer();
String uri = "ws://localhost:8080" + request.getContextPath() + "/websocket";
container.connectToServer(MyClient.class, URI.create(uri));
The complete source code used in this Tip Of The Day (TOTD) is available here. It will run on the latest checked out version of GlassFish workspace, not a promoted build yet (bleeding edge eh ;-)

Please provide feedback on users@tyrus.java.net.

How are you going to use Java-based WebSocket client in your applications ?

Here are other Java EE 7 entries published on this blog so far:


Wednesday Dec 12, 2012

WebSocket Samples in GlassFish 4 build 66 - javax.websocket.* package: TOTD #190


This blog has published a few blogs on using JSR 356 Reference Implementation (Tyrus) integrated in GlassFish 4 promoted builds.
  • TOTD #183: Getting Started with WebSocket in GlassFish
  • TOTD #184: Logging WebSocket Frames using Chrome Developer Tools, Net-internals and Wireshark
  • TOTD #185: Processing Text and Binary (Blob, ArrayBuffer, ArrayBufferView) Payload in WebSocket
  • TOTD #186: Custom Text and Binary Payloads using WebSocket
  • TOTD #189: Collaborative Whiteboard using WebSocket in GlassFish 4
The earlier blogs created a WebSocket endpoint as:
import javax.net.websocket.annotations.WebSocketEndpoint;

@WebSocketEndpoint("websocket")
public class MyEndpoint {
. . .
Based upon the discussion in JSR 356 EG, the package names have changed to javax.websocket.*. So the updated endpoint definition will look like:
import javax.websocket.WebSocketEndpoint;

@WebSocketEndpoint("websocket")
public class MyEndpoint {
. . .
The POM dependency is:
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.0-b09</version>
</dependency>

And if you are using GlassFish 4 build 66, then you also need to provide a dummy EndpointFactory implementation as:
import javax.websocket.WebSocketEndpoint;

@WebSocketEndpoint(value="websocket", factory=MyEndpoint.DummyEndpointFactory.class)
public class MyEndpoint {
 . . .   
class DummyEndpointFactory implements EndpointFactory {
    @Override
public Object createEndpoint() { return null; }
  }
}
This is only interim and will be cleaned up in subsequent builds. But I've seen couple of complaints about this already and so this deserves a short blog.

Have you been tracking the latest Java EE 7 implementations in GlassFish 4 promoted builds ?

Thursday Nov 29, 2012

Collaborative Whiteboard using WebSocket in GlassFish 4 - Text/JSON and Binary/ArrayBuffer Data Transfer (TOTD #189)


This blog has published a few blogs on using JSR 356 Reference Implementation (Tyrus) as its integrated in GlassFish 4 promoted builds.
  • TOTD #183: Getting Started with WebSocket in GlassFish
  • TOTD #184: Logging WebSocket Frames using Chrome Developer Tools, Net-internals and Wireshark
  • TOTD #185: Processing Text and Binary (Blob, ArrayBuffer, ArrayBufferView) Payload in WebSocket
  • TOTD #186: Custom Text and Binary Payloads using WebSocket
One of the typical usecase for WebSocket is online collaborative games. This Tip Of The Day (TOTD) explains a sample that can be used to build such games easily.

The application is a collaborative whiteboard where different shapes can be drawn in multiple colors. The shapes drawn on one browser are automatically drawn on all other peer browsers that are connected to the same endpoint. The shape, color, and coordinates of the image are transfered using a JSON structure. A browser may opt-out of sharing the figures. Alternatively any browser can send a snapshot of their existing whiteboard to all other browsers. Take a look at this video to understand how the application work and the underlying code.



The Java API for WebSocket has evolved since this sample was first created. Here is the source code corresponding to different builds of GlassFish:
GlassFish4 4 Build Source Code
66 here
74 here
76 here
78 here
84 here
The code in the application is explained below.

The web page (index.jsp) has a HTML5 Canvas as shown:
<canvas id="myCanvas" width="150" height="150" style="border:1px solid #000000;"></canvas>
And some radio buttons to choose the color and shape. By default, the shape, color, and coordinates of any figure drawn on the canvas are put in a JSON structure and sent as a message to the WebSocket endpoint. The JSON structure looks like:
{
"shape": "square",
"color": "#FF0000",
"coords": {
"x": 31.59999942779541,
"y": 49.91999053955078
}
}
The endpoint definition looks like:
@WebSocketEndpoint(value = "websocket",
encoders = {FigureDecoderEncoder.class},
decoders = {FigureDecoderEncoder.class})
public class Whiteboard {

As you can see, the endpoint has decoder and encoder registered that decodes JSON to a Figure (a POJO class) and vice versa respectively. The decode method looks like:
public Figure decode(String string) throws DecodeException {
try {
JSONObject jsonObject = new JSONObject(string);
return new Figure(jsonObject);
} catch (JSONException ex) {
throw new DecodeException("Error parsing JSON", ex.getMessage(), ex.fillInStackTrace());
}
}
And the encode method looks like:
public String encode(Figure figure) throws EncodeException {
return figure.getJson().toString();
}
FigureDecoderEncoder implements both decoder and encoder functionality but thats purely for convenience. But the recommended design pattern is to keep them in separate classes. In certain cases, you may even need only one of them.

On the client-side, the Canvas is initialized as:
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
canvas.addEventListener("click", defineImage, false);

The defineImage method constructs the JSON structure as shown above and sends it to the endpoint using websocket.send().

An instant snapshot of the canvas is sent using binary transfer with WebSocket. The WebSocket is initialized as:
var wsUri = "ws://localhost:8080/whiteboard/websocket";
var websocket = new WebSocket(wsUri);
websocket.binaryType = "arraybuffer";

The important part is to set the binaryType property of WebSocket to arraybuffer. This ensures that any binary transfers using WebSocket are done using ArrayBuffer as the default type seem to be blob. The actual binary data transfer is done using the following:

var image = context.getImageData(0, 0, canvas.width, canvas.height);
var buffer = new ArrayBuffer(image.data.length);
var bytes = new Uint8Array(buffer);
for (var i=0; i<bytes.length; i++) {
bytes[i] = image.data[i];
}
websocket.send(bytes);

This comprehensive sample shows the following features of JSR 356 API:
  • Annotation-driven endpoints
  • Send/receive text and binary payload in WebSocket
  • Encoders/decoders for custom text payload

In addition, it also shows how images can be captured and drawn using HTML5 Canvas in a JSP.

How could this be turned in to an online game ? Imagine drawing a Tic-tac-toe board on the canvas with two players playing and others watching. Then you can build access rights and controls within the application itself. Instead of sending a snapshot of the canvas on demand, a new peer joining the game could be automatically transferred the current state as well. Do you want to build this game ?

I built a similar game a few years ago. Do somebody want to rewrite the game using WebSocket APIs ? :-)

Many thanks to Jitu and Akshay for helping through the WebSocket internals!

Here are some references for you:
Subsequent blogs will discuss the following topics (not necessary in that order) ...
  • Error handling
  • Interface-driven WebSocket endpoint
  • Java client API
  • Client and Server configuration
  • Security
  • Subprotocols
  • Extensions
  • Other topics from the API

Thursday Nov 15, 2012

Custom Text and Binary Payloads using WebSocket (TOTD #186)


TOTD #185 explained how to process text and binary payloads in a WebSocket endpoint. In summary, a text payload may be received as

public void receiveTextMessage(String message) {
    . . .
}

And binary payload may be received as:

public void recieveBinaryMessage(ByteBuffer message) {
    . . .
}

As you realize, both of these methods receive the text and binary data in raw format. However you may like to receive and send the data using a POJO. This marshaling and unmarshaling can be done in the method implementation but JSR 356 API provides a cleaner way. For encoding and decoding text payload into POJO, Decoder.Text (for inbound payload) and Encoder.Text (for outbound payload) interfaces need to be implemented.

A sample implementation below shows how text payload consisting of JSON structures can be encoded and decoded.
public class MyMessage implements Decoder.Text<MyMessage>, Encoder.Text<MyMessage> {
    private JsonObject jsonObject;

    @Override
    public MyMessage decode(String string) throws DecodeException {
        this.jsonObject = new JsonReader(new StringReader(string)).readObject();      
        return this;
    }

    @Override
    public boolean willDecode(String string) {
        return true;
    }

    @Override
    public String encode(MyMessage myMessage) throws EncodeException {
        return myMessage.jsonObject.toString();
    }

public JsonObject getObject() { return jsonObject; }
}
In this implementation, the decode method decodes incoming text payload to MyMessage, the encode method encodes MyMessage for the outgoing text payload, and the willDecode method returns true or false if the message can be decoded.

The encoder and decoder implementation classes need to be specified in the WebSocket endpoint as:
@WebSocketEndpoint(value="/endpoint",
encoders={MyMessage.class},
decoders={MyMessage.class}) public class MyEndpoint { public MyMessage receiveMessage(MyMessage message) { . . . } }
Notice the updated method signature where the application is working with MyMessage instead of the raw string.

Note that the encoder and decoder implementations just illustrate the point and provide no validation or exception handling. Similarly Encooder.Binary and Decoder.Binary interfaces need to be implemented for encoding and decoding binary payload.

Here are some references for you:
Subsequent blogs will discuss the following topics (not necessary in that order) ...
  • Error handling
  • Interface-driven WebSocket endpoint
  • Java client API
  • Client and Server configuration
  • Security
  • Subprotocols
  • Extensions
  • Other topics from the API

Sunday Nov 11, 2012

Processing Text and Binary (Blob, ArrayBuffer, ArrayBufferView) Payload in WebSocket - (TOTD #185)


The WebSocket API defines different send(xxx) methods that can be used to send text and binary data. This Tip Of The Day (TOTD) will show how to send and receive text and binary data using WebSocket.

TOTD #183 explains how to get started with a WebSocket endpoint using GlassFish 4. A simple endpoint from that blog looks like:
@WebSocketEndpoint("/endpoint")
public class MyEndpoint {
public void receiveTextMessage(String message) {
. . .
}
}
A message with the first parameter of the type String is invoked when a text payload is received. The payload of the incoming WebSocket frame is mapped to this first parameter.

An optional second parameter, Session, can be specified to map to the "other end" of this conversation. For example:
public void receiveTextMessage(String message, Session session) {
    . . .
}
The return type is void and that means no response is returned to the client that invoked this endpoint. A response may be returned to the client in two different ways. First, set the return type to the expected type, such as:
public String receiveTextMessage(String message) {
String response = . . .
. . .
return response; }
In this case a text payload is returned back to the invoking endpoint.

The second way to send a response back is to use the mapped session to send response using one of the sendXXX methods in Session, when and if needed.
public void receiveTextMessage(String message, Session session) {
    . . .
    RemoteEndpoint remote = session.getRemote();
    remote.sendString(...);
    . . .
    remote.sendString(...);
   . . .
   remote.sendString(...);
}
This shows how duplex and asynchronous communication between the two endpoints can be achieved. This can be used to define different message exchange patterns between the client and server.

The WebSocket client can send the message as:
websocket.send(myTextField.value);
where myTextField is a text field in the web page.

Binary payload in the incoming WebSocket frame can be received if ByteBuffer is used as the first parameter of the method signature. The endpoint method signature in that case would look like:
public void receiveBinaryMessage(ByteBuffer message) {
    . . .
}
From the client side, the binary data can be sent using Blob, ArrayBuffer, and ArrayBufferView. Blob is a just raw data and the actual interpretation is left to the application. ArrayBuffer and ArrayBufferView are defined in the TypedArray specification and are designed to send binary data using WebSocket. In short, ArrayBuffer is a fixed-length binary buffer with no format and no mechanism for accessing its contents. These buffers are manipulated using one of the views defined by one of the subclasses of ArrayBufferView listed below:
  • Int8Array (signed 8-bit integer or char)
  • Uint8Array (unsigned 8-bit integer or unsigned char)
  • Int16Array (signed 16-bit integer or short)
  • Uint16Array (unsigned 16-bit integer or unsigned short)
  • Int32Array (signed 32-bit integer or int)
  • Uint32Array (unsigned 16-bit integer or unsigned int)
  • Float32Array (signed 32-bit float or float)
  • Float64Array (signed 64-bit float or double)
WebSocket can send binary data using ArrayBuffer with a view defined by a subclass of ArrayBufferView or a subclass of ArrayBufferView itself.

The WebSocket client can send the message using Blob as:

blob = new Blob([myField2.value]);
websocket.send(blob);

where myField2 is a text field in the web page.

The WebSocket client can send the message using ArrayBuffer as:

var buffer = new ArrayBuffer(10);
var bytes = new Uint8Array(buffer);
for (var i=0; i<bytes.length; i++) {
bytes[i] = i;
}
websocket.send(buffer);
A concrete implementation of receiving the binary message may look like:

@WebSocketMessage
public void echoBinary(ByteBuffer data, Session session) throws IOException {
    System.out.println("echoBinary: " + data);
    for (byte b : data.array()) {
        System.out.print(b);
    }
    session.getRemote().sendBytes(data);
}

This method is just printing the binary data for verification but you may actually be storing it in a database or converting to an image or something more meaningful.

Be aware of TYRUS-51 if you are trying to send binary data from server to client using method return type.

Here are some references for you:
Subsequent blogs will discuss the following topics (not necessary in that order) ...
  • Error handling
  • Custom payloads using encoder/decoder
  • Interface-driven WebSocket endpoint
  • Java client API
  • Client and Server configuration
  • Security
  • Subprotocols
  • Extensions
  • Other topics from the API

Wednesday Nov 07, 2012

Logging WebSocket Frames using Chrome Developer Tools, Net-internals and Wireshark (TOTD #184)


TOTD #183 explained how to build a WebSocket-driven application using GlassFish 4. This Tip Of The Day (TOTD) will explain how do view/debug on-the-wire messages, or frames as they are called in WebSocket parlance, over this upgraded connection. This blog will use the application built in TOTD #183.

First of all, make sure you are using a browser that supports WebSocket. If you recall from TOTD #183 then WebSocket is combination of Protocol and JavaScript API. A browser supporting WebSocket, or not, means they understand your web pages with the WebSocket JavaScript. caniuse.com/websockets provide a current status of WebSocket support in different browsers. Most of the major browsers such as Chrome, Firefox, Safari already support WebSocket for the past few versions. As of this writing, IE still does not support WebSocket however its planned for a future release.

Viewing WebSocket farmes require special settings because all the communication happens over an upgraded HTTP connection over a single TCP connection. If you are building your application using Java, then there are two common ways to debug WebSocket messages today. Other language libraries provide different mechanisms to log the messages.

Lets get started!

Chrome Developer Tools provide information about the initial handshake only. This can be viewed in the Network tab and selecting the endpoint hosting the WebSocket endpoint.



You can also click on "WebSockets" on the bottom-right to show only the WebSocket endpoints.

Click on "Frames" in the right panel to view the actual frames being exchanged between the client and server.



The frames are not refreshed when new messages are sent or received. You need to refresh the panel by clicking on the endpoint again.

To see more detailed information about the WebSocket frames, you need to type "chrome://net-internals" in a new tab. Click on "Sockets" in the left navigation bar and then on "View live sockets" to see the page.



Select the box with the address to your WebSocket endpoint and see some basic information about connection and bytes exchanged between the client and the endpoint.



Clicking on the blue text "source dependency ..." shows more details about the handshake.




If you are interested in viewing the exact payload of WebSocket messages then you need a network sniffer. These tools are used to snoop network traffic and provide a lot more details about the raw messages exchanged over the network. However because they provide lot more information so they need to be configured in order to view the relevant information.

Wireshark (nee Ethereal) is a pretty standard tool for sniffing network traffic and will be used here. For this blog purpose, we'll assume that the WebSocket endpoint is hosted on the local machine. These tools do allow to sniff traffic across the network though. Wireshark is quite a comprehensive tool and we'll capture traffic on the loopback address.

Start wireshark, select "loopback" and click on "Start".



By default, all traffic information on the loopback address is displayed. That includes tons of TCP protocol messages, applications running on your local machines (like GlassFish or Dropbox on mine), and many others. Specify "http" as the filter in the top-left. Invoke the application built in TOTD #183 and click on "Say Hello" button once. The output in wireshark looks like



Here is a description of the messages exchanged:
  • Message #4: Initial HTTP request of the JSP page
  • Message #6: Response returning the JSP page
  • Message #16: HTTP Upgrade request



  • Message #18: Upgrade request accepted

  • Message #20: Request favicon
  • Message #22: Responding with favicon not found
  • Message #24: Browser making a WebSocket request to the endpoint


  • Message #26: WebSocket endpoint responding back

You can also use Fiddler to debug your WebSocket messages.

How are you viewing your WebSocket messages ?

Here are some references for you:
Subsequent blogs will discuss the following topics (not necessary in that order) ...
  • Binary data as payload
  • Custom payloads using encoder/decoder
  • Error handling
  • Interface-driven WebSocket endpoint
  • Java client API
  • Client and Server configuration
  • Security
  • Subprotocols
  • Extensions
  • Other topics from the API
About

profile image
Arun Gupta is a technology enthusiast, a passionate runner, author, and a community guy who works for Oracle Corp.


Java EE 7 Samples

Stay Connected

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today