X

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

Guest Author


This blog has published a few blogs on using JSR 356 Reference
Implementation (Tyrus) as its integrated in href="http://download.java.net/glassfish/4.0/promoted">GlassFish 4
promoted builds.
  • href="https://blogs.oracle.com/arungupta/entry/websocket_applications_using_java_jsr">TOTD
    #183: Getting Started with WebSocket in GlassFish
  • href="https://blogs.oracle.com/arungupta/entry/logging_websocket_frames_using_chrome">TOTD
    #184: Logging WebSocket Frames using Chrome Developer
    Tools, Net-internals and Wireshark
  • href="https://blogs.oracle.com/arungupta/entry/processing_text_and_binary_payload">TOTD
    #185: Processing Text and Binary (Blob, ArrayBuffer,
    ArrayBufferView) Payload in WebSocket
  • href="https://blogs.oracle.com/arungupta/entry/custom_text_and_binary_payloads">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 BuildSource Code
66here
74here
76here
78here
84here

The code in the application is explained below.



The web page (index.jsp) has a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html">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 href="https://www.khronos.org/registry/typedarray/specs/1.0/#5">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 href="https://blogs.oracle.com/arungupta/entry/tic_tac_toe_using_rails">a
few years ago. Do somebody want to rewrite the game using
WebSocket APIs ? :-)

Many thanks to Jitu
and Akshay for href="http://stackoverflow.com/questions/13390454/receive-blob-in-websocket-and-render-as-image-in-canvas">helping
through the WebSocket internals!


Here are some references for you:
  • JSR 356:
    Java API for WebSocket - href="http://websocket-spec.java.net/">Specification (href="http://jcp.org/aboutJava/communityprocess/edr/jsr356/index.html">Early
    Draft) and Implementation
    (already integrated in href="http://download.java.net/glassfish/4.0/promoted">GlassFish
    4
    promoted builds)

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

Join the discussion

Comments ( 25 )
  • vbarzana Friday, November 30, 2012

    Nice work Mr. Gupta, I really appreciate it, is simple and amazing.

    I am a jWebSocket developer, we have a big structure but with not much documentation, some long ago I made an engine for jWebSocket to wrap grizzly websockets interface that GlassFish uses, maybe you can be interested on it. It allows us keep all the approach from jWebSocket inside GlassFish, probably you would also like to take a look it is already included in the jWebSocket packet, with just changing the configuration which engine to start you get it running, http://jwebsocket.org. We have a lot of functionalities already implemented such as listeners, plugins, very easy to use in server and client side, allowing you to focus only in your application without taking into account many other functionalities already implemented in the framework.

    Best regards,

    Victor Antonio


  • Jeff Saturday, December 1, 2012

    Awesome work, Arun!


  • guest Sunday, December 2, 2012

    Hi Arun,

    thanks for the excellent blog! Very comprehensive and easy to follow! Just as your YouTube video recording for the same example at

    http://www.youtube.com/watch?v=uoMKlSNUPsk

    Thanks and best regards,

    Peter


  • Melvin Vivas Monday, December 3, 2012

    Great stuff! Will it only work in Glassfish?


  • Arun Gupta Monday, December 3, 2012

    @Victor, please share your feedback with the JSR 356 EG at http://java.net/projects/websocket-spec/lists/users/archive

    This sample should work on any JSR 356 compliant implementation, for now this is only implemented in GlassFish 4. But any Java EE 7 compliant application server will support it.


  • christi parks Monday, December 3, 2012

    Hello, sir i would like to ask that what is the scope of java training, what all topics should be covered and it is kinda bothering me … and has anyone studies from this course http://www.wiziq.com/course/1779-core-and-advance-java-concepts of core and advance java online ?? or tell me any other guidance...

    would really appreciate help… and Also i would like to thank for all the information you are providing on java concepts.


  • guest Tuesday, December 11, 2012

    Hello all, this example does not run in glassfish 4.0-b66


  • Arun Gupta Tuesday, December 11, 2012

    APIs have changed from 64 -> 66. I'll upload an updated version later today.


  • Arun Gupta Tuesday, December 11, 2012

    New sample code download is now available and is tested on GlassFish build 66.


  • Jeff Williams Tuesday, January 8, 2013

    Arun, I posted to GitHub a simple tool that I built to do regression tests against a WebSocket server in development. It's an HTML5/JS single page app that makes it nice/easy to do replay tests.

    http://jw-gadgetworks.github.com/websocket-tester/


  • Arun Gupta Wednesday, January 9, 2013

    Thanks a lot Jeff for sharing the link!

    Looking forward to your sample being ported to GlassFish 4/Tyrus. Happy to help in any manner.


  • christi parks Wednesday, January 23, 2013

    Are there some advantages of one language C and Java over the other? Which one is optimal? Which one is more "future proof"? Would it be optimal to know both? If so, which order? I am a little confused on the subject. Just got across this course http://www.wiziq.com/course/1779-core-and-advance-java-concepts, will it be helpful also. A little enlightenment could help. Thanks


  • guest Tuesday, January 29, 2013

    Hi Arun,

    I observed that the problems I am facing in my program are there in the whiteboard app given in your blog too. (At least when I try to run locally .

    Problem is the java code throws exception when you refresh one of the browser white board page. And after that the program does not work.

    The reason is that the session object is null on close. http://java.net/jira/browse/TYRUS-73

    I have raised a couple of JIRA bugs.

    To avoid this situation I have put the code "peer.getRemote().sendObject(figure);" in try block so that if it fails , it can gracefully remove that peer instead of ending up in exception.

    Also the isOpen API does not work ( http://java.net/jira/browse/TYRUS-76 ) for me so that I could check if the socket is still open before trying to send any message.


  • Neil Sunday, February 3, 2013

    Hey , Has the specs changed again ? The whiteboard seems to be not running in build 73

    Error:

    [13:13:36.097] Firefox can't establish a connection to the server at ws://localhost:8080/whiteboard/websocket. @ http://localhost:8080/whiteboard/websocket.js:42

    --

    [13:13:44.190] sending text: {"shape":"square","color":"#FF0000","coords":{"x":106,"y":79.41667175292969}}


  • Arun Gupta Monday, February 4, 2013

    Neil,

    I'll try 73 later today and update the sample code if required.

    Arun


  • Arun Gupta Monday, February 4, 2013

    Neil,

    I do not see b73 http://download.java.net/glassfish/4.0/promoted. Where did you download it from ?

    Arun


  • Johnny Bekkestad Thursday, February 7, 2013

    Hi Arun, i also have issues with b73. It is not able to connect.

    What i saw is changes in the EndpointFactory and i am not able to create the DummyEndpointFactory


  • Arun Gupta Thursday, February 7, 2013

    73 is not promoted yet. Can we please stick with 72 only ?

    http://dlc.sun.com.edgesuite.net/glassfish/4.0/promoted/glassfish-4.0-b72.zip

    I'll update the sample when 73 is available :-)


  • Arun Gupta Tuesday, February 12, 2013

    THe sample is now working with GlassFish b74 and the source code is available at:

    https://blogs.oracle.com/arungupta/resource/totd189-whiteboard-glassfish74.zip


  • guest Saturday, February 16, 2013

    GF4 b74 occasionally drops websocket return messages under high load. Is this a known issue?

    To reproduce, write a simple echo server like TOTD #183 and send it a bunch of 1000 string messages each 10bk of size. Some of the messages won't return to the browser (though all of them reach the server).


  • Arun Gupta Monday, February 18, 2013

    Just checked with the engineering team and this seems to be a known issue. Filed http://java.net/jira/browse/TYRUS-98 for tracking.


  • Neil Wednesday, February 20, 2013

    I tried b76 today and used the code Posted by Arun Gupta on February 11, 2013 at 05:17 PM PST # , facing the same problem

    [12:41:22.599] Firefox can't establish a connection to the server at ws://localhost:8080/whiteboard/websocket. @ http://localhost:8080/whiteboard/websocket.js:42

    Somehow it is not able to reach the endpoint. JSP renders just fine. The libraries also got downloaded via POM when I rebuild.


  • Arun Gupta Thursday, February 21, 2013

    The sample is now updated to work with GlassFish b76 and the source code is available at:

    https://blogs.oracle.com/arungupta/resource/totd189-whiteboard-glassfish76.zip


  • Arun Gupta Thursday, February 21, 2013

    Neil,

    Does this work with b74 and only causing issue with b76 ?

    Arun


  • guest Wednesday, May 8, 2013

    Hi Arun,

    Could you advise how I can get the sample project to work.

    I get the error 'unexpected response code 404'.

    The error is displayed in the chrome javascript console

    These are the environment details

    Source code

    ~~~~~~~~~~~

    totd189-whiteboard-glassfish78

    Glassfish

    ~~~~~~~~~

    glassfish-4.0-b78

    Netbeans

    ~~~~~~~~

    Product Version: NetBeans IDE Dev (Build 201305072300)

    Updates: Updates available

    Java SDK

    ~~~~~~~~

    Java: 1.7.0_21; Java HotSpot(TM) 64-Bit Server VM 23.21-b01

    Runtime: Java(TM) SE Runtime Environment 1.7.0_21-b11

    Linux

    ~~~~~

    Debian Squeeze 6.0.6

    System: Linux version 3.2.0-0.bpo.2-amd64 running on amd64; UTF-8; en_US (nb)


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha