Skip to main content

Web Sockets and HTML5 in Glassfish

Posted by spericas on October 6, 2010 at 7:54 AM PDT

It's been several months since my last blog! I have recently re-joined the Glassfish team at Oracle and I'm currently looking at Web tier technologies like Web sockets and HTML5. In this blog, I'd like to show you a simple Web application in which you can control an HTML5 video object remotely. The basic idea is to intercept video events like 'play', 'pause' and 'seeked' and remote them using Web Sockets to control another player. Although there may be some use cases for an application like this (such as coaching), the real objective of this exercise is to show the low latency of the Web sockets implementation in Glassfish. So let's get started!

Here is a screenshot of the application:

A Web socket is nothing more than a socket connection that can be established between a (modern) Web browser and a Web container. It provides a low-latency, bi-directional communication "parallel" to the HTTP channel. It is Ajax/Comet on steroids. No hacks, no jumping through loops, nothing. It has the potential of redefining what we understand as a Web application lifecycle given it's low latency and always connected characteristics (but more about this on a future blog).

Our Web application comprises of a single page and some JavaScript code. We use JSF 2.0 facelets to define the main page:

 <html ...>
   <h:head>...</h:head>
   <h:body>
      <p>Let's share this video:</p>
      <p><h5:video height="360" controls="true" src="PBIRbest.m4v"/></p>
      <h:outputScript library="js" name="json2.js" target="head"/>
      <h:outputScript library="js" name="app.js" target="head"/>
   </h:body>
</html>

 The tag h5:video is defined by the following custom JSF component:

<html ...>
  <cc:interface>
    <cc:attribute name="src" required="true"/>
    <cc:attribute name="controls" required="false"/>
    <cc:attribute name="width" required="false"/>
    <cc:attribute name="height" required="false"/>
  </cc:interface>
  <cc:implementation>
   <video src="#{cc.attrs.src}" controls="#{cc.attrs.controls}"
      width="#{cc.attrs.width}" height="#{cc.attrs.height}"></video>
  </cc:implementation>
</html>

 Defining a new h5:video tag (instead of using HTML5's video tag directly) is not a requirement for the application, but it does provide a clear separation and makes the example more readable (a separate component can be used to insert additional JavaScript code, if needed). Note the inclusion of the JavaScript libraries: json2.js and app.js. The former provides us with JSON.stringify() and JSON.parse(); the latter is where our client-side code resides. Let's have a look at that.

 Let us define a JavaScript function that returns a network object (i.e., a closure). This network object has two methods: initialize() and send(). The former opens a Web socket and registers listeners for incoming messages; the latter is for sending outgoing messages.

var network = function (websocket) {
    return {
        initialize: function() {
            var url = 'ws://' + document.location.host + document.location.pathname + 'videosharing';
            websocket = new WebSocket(url);
            websocket.name = APP.id;
            websocket.onopen = function() { };
            websocket.onmessage = function (evt) {
                var command = JSON.parse(evt.data);
                if (command.type == "pause") {
                    APP.pauseVideo();
                } else if (command.type == "play") {
                    APP.playVideo();
                } else if (command.type == "seeked") {
                    APP.seekVideo(command.currentTime);
                } else {
                    alert("Unknown command " + command);
                }
            };
            websocket.onclose = function() { };
        },
        send: function(shape) {
            websocket.send(shape);
        }
    }
};

 First, a Web socket is created from a URL constructed using the main page's host, pathname and "/videosharing". Notice the use of the "ws://" protocol. Next, listeners for onopen, onmessage and onclose events are registered. The onmessage listener parses an incoming command, determines its type and calls the appropriate method in out application object APP. This is how UI events are remoted. The send() method is used to remote events that are local to this window instance. 

The network's initialize() method is called by APP.initialize(). This method also registers listeners for all local video events for remoting purposes as shown next.

    initialize: function () {
        APP.network.initialize();

        var video = APP.getVideo();
        video.addEventListener('play',
            function (event) {
                var command = { type: "play" };
                APP.network.send(JSON.stringify(command));
            },
            false);
        video.addEventListener('pause',
            function (event) {
                var command = { type: "pause" };
                APP.network.send(JSON.stringify(command));
            },
            false);
        video.addEventListener('seeked',
            function (event) {
                var command = { type: "seeked",
                                currentTime: APP.getVideo().currentTime };
                APP.network.send(JSON.stringify(command));
            },
            false);
    }

Note how commands are constructed, stringified and remoted using to the network object. All commands have a type field; the 'seeked' command also includes a currentTime field in order to seek remote video objects to the correct location. The rest of the client code is uninteresting so lets look at the server side pieces.

 The server side API provided in Glassifish 3.1 for Web sockets is straightforward. First, we extend the class WebSocketApplication and override methods createSocket() and onMessage() as shown next.

public class VideoSharingApplication extends WebSocketApplication {

    @Override
    public WebSocket createSocket(NetworkHandler handler,
            WebSocketListener... listeners) throws IOException {
        return new VideoSharingWebSocket(handler, listeners);
    }

    @Override
    public void onMessage(WebSocket socket, DataFrame frame) throws IOException {
        final String data = frame.getTextPayload();
        for (WebSocket webSocket : getWebSockets()) {
            try {
                if (socket != webSocket) {
                    webSocket.send(data);
                }
            } catch (IOException e) {
                e.printStackTrace();
                webSocket.close();
            }
        }
    }
}

The onMessage() method handles socket frames by extracting the data in the frame and broadcasting it to all other Web socket clients. The createSocket() method simply creates an instance of our app's Web socket which, in this application, it is very simple.

public class VideoSharingWebSocket extends BaseServerWebSocket {
    public VideoSharingWebSocket(NetworkHandler handler,
            WebSocketListener... listeners) {
        super(handler, listeners);
    }
}

The last piece in this puzzle is the registration of our application in the WebSocketEngine. This can be accomplished using the init() method in a servlet as shown below.

public class WebSocketsServlet extends HttpServlet {

    private final VideoSharingApplication app = new VideoSharingApplication();

    @Override
    public void init(ServletConfig config) throws ServletException {
        WebSocketEngine.getEngine().register(
            config.getServletContext().getContextPath() + "/videosharing", app);
    }
}

Of note, this server-side API is still evolving and is subject to change and improvements in upcoming versions of Glassfish. A source bundle for this sample is attached and includes a NetBeans 6.9.1 project file. You must use Glassfish Server Open Source Edition 3.1 and a browser that supports Web sockets (as well as the codec for the video that you use!). In my project, I include an MPEG-4 video and Safari 5.X on a Mac. 

 

AttachmentSize
screenshot.gif84.96 KB
VideoSharing.zip549.56 KB
Related Topics >>

Comments

Hey folks, did anyone recently get it to work? I used ...

Hey folks,

did anyone recently get it to work?

I used NetBean 7.0.1 with Glassfish 3.1 and grizzly-websockets-1.9.20-SNAPSHOT.

Due to some api changes i had to fix some build errors. The application runs now, but the sync between browser windows is not working at all.

Does anybody have any help? Can someone upload a working example including the libraries?

Greetings,

Wombatus

Tried compiling your sample

Tried compiling your sample in both NetBeans 6.9.1 + GlassFish 3.0.1 and NetBeans 7.0 M2 + GlassFish 3.1 b24 without success. Reference to grizzly-websockets-1.9.20-SNAPSHOT.jar causing problem. Downloaded this jar from grizzly.dev.java.net, copied it to the GlassFish modules folder and then, in the NetBeans IDE, added it to the GlassFish library. No help. I understand that Grizzly is now integrated into GlassFish but, when I examine the Grizzly jars, I don't see com.sun.grizzly.websockets.[class]. Also, how can I Start the GlassFish Server (in the NB IDE's Services pane) with the command string given in the first comment above? Please give step-by-step instructions for NB newbie. Thanks.

Hi, I'm was also having

Hi,
I'm was also having reference problems with grizzly-websockets-1.9.20-SNAPSHOT.jar. I downloaded the jar from here, clicked "Resolve reference problem" and located the file. The reference problem was gone. BUT:
The API seems different. I get this:
Compiling 3 source files to /home/fratelli/Projects/VideoSharing/build/web/WEB-INF/classes
/home/fratelli/Projects/VideoSharing/src/java/com/oracle/websockets/VideoSharingApplication.java:17: com.oracle.websockets.VideoSharingApplication is not abstract and does not override abstract method isApplicationRequest(com.sun.grizzly.tcp.Request) in com.sun.grizzly.websockets.WebSocketApplication
public class VideoSharingApplication extends WebSocketApplication {
/home/fratelli/Projects/VideoSharing/src/java/com/oracle/websockets/VideoSharingApplication.java:19: method does not override or implement a method from a supertype
@Override
/home/fratelli/Projects/VideoSharing/src/java/com/oracle/websockets/VideoSharingWebSocket.java:17: cannot find symbol
symbol : constructor BaseServerWebSocket(com.sun.grizzly.websockets.NetworkHandler,com.sun.grizzly.websockets.WebSocketListener[])
location: class com.sun.grizzly.websockets.BaseServerWebSocket
super(handler, listeners);
Note: /home/fratelli/Projects/VideoSharing/src/java/com/oracle/websockets/WebSocketsServlet.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
3 errors
/home/fratelli/Projects/VideoSharing/nbproject/build-impl.xml:538: The following error occurred while executing this line:
/home/fratelli/Projects/VideoSharing/nbproject/build-impl.xml:251: Compile failed; see the compiler error output for details.

Does anybody know what the problem is?
Regards

 Were you able to compile the

 Were you able to compile the project after adding the Grizzly jar? If not, what error did you get. For the asadmin command, just run that from the command line after installing Glassfish (or creating a new domain).

Enable WebSockets for GlassFish

Make sure you enable websockets for GlassFish if you try this at home :
asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.websockets-support-enabled=true

Enable WebSockets for GlassFish

 Right! Keep forgetting about this. Hopefully we can make this the default some day.

Is there anything else I have

Is there anything else I have to do?
I deployed your app in a 3.1-b23 version of GlassFish and the only code that seems to run is the init() for the servlet.
I did make a change in the project to make it compile: I pointed the missing reference to grizzly-websockets in the project to the one that comes with GF.
And I'm using the 8.0.552.0 dev version of Google Chrome (which works without problem with lots of WebSocket demos if tried on the web).
Any ideas what I'm doing wrong?

edit: forget it, it just started working after hittinf Save on this messgae *sigh*
Sorry about the noise.

<p>I have used the stub code provided here, to try and get ...

I have used the stub code provided here, to try and get it working on my environment. (GlassFish Server Open Source Edition 3.0.1 (build 22), jdk 1.6 and Netbeans 6.9.1)
As per the instructions provided through earlier threads I have enabled websockets on server side. And also have imported the following jars (grizzly-websockets-1.9.20.jar, grizzly-utils.jar and glassfish server runtime classpath) to build my application. The build works fine. However when I deploy to the server I get the below exception.

java.lang.RuntimeException: java.lang.UnsupportedOperationException: Not supported yet.
at com.sun.grizzly.arp.AsyncProcessorTask.doTask(AsyncProcessorTask.java:131)
at com.sun.grizzly.http.TaskBase.run(TaskBase.java:193)
at com.sun.grizzly.http.TaskBase.execute(TaskBase.java:175)
at com.sun.grizzly.arp.DefaultAsyncHandler.handle(DefaultAsyncHandler.java:145)
at com.sun.grizzly.arp.AsyncProtocolFilter.execute(AsyncProtocolFilter.java:204)
at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.UnsupportedOperationException: Not supported yet.
at com.oracle.websockets.VideoSharingApplication.isApplicationRequest(VideoSharingApplication.java:43)
at com.sun.grizzly.websockets.WebSocketApplication.upgrade(WebSocketApplication.java:93)
at com.sun.grizzly.websockets.WebSocketEngine.getApplication(WebSocketEngine.java:113)
at com.sun.grizzly.websockets.WebSocketEngine.upgrade(WebSocketEngine.java:133)
at com.sun.grizzly.websockets.WebSocketAsyncFilter.doFilter(WebSocketAsyncFilter.java:52)
at com.sun.grizzly.arp.DefaultAsyncExecutor.invokeFilters(DefaultAsyncExecutor.java:171)
at com.sun.grizzly.arp.DefaultAsyncExecutor.interrupt(DefaultAsyncExecutor.java:143)
at com.sun.grizzly.arp.AsyncProcessorTask.doTask(AsyncProcessorTask.java:94)
... 14 more

does Glassfish server support websockets currently? thank you for your response.

Note that you must use GF 3.1, as explained in the blog ...

Note that you must use GF 3.1, as explained in the blog entry. The Grizzly internally API used here may have changed before GF 3.1 went final. Compilation errors should be pretty easy to fix, though. Post them here if you need help.

<p>[quote=spericas]Note that you must use GF 3.1, as ...

spericas wrote:
Note that you must use GF 3.1, as explained in the blog entry. The Grizzly internally API used here may have changed before GF 3.1 went final. Compilation errors should be pretty easy to fix, though. Post them here if you need help.

I am still getting the same exception.
asadmin> set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.websockets-support-enabled=trueconfigs.config.server-config.network-config.protocols.protocol.http-listener-1.http.websockets-support-enabled=true
Command set executed successfully.
asadmin> version
Version = Oracle GlassFish Server 3.1 (build 43)
After this I tried deploying this application on the server. However, I am still getting the same stack trace. Can you please tell me what I am doing wrong. I downloaded this server instance. cds.sun.com/is-bin/INTERSHOP.enfinity/WFS/CDS-CDS_Developer-Site/en_US/-/USD/VerifyItem-Start/ogs-3.1.zip
I have been able to compile the application without any issue after adding the right jars. But I am still getting this server side exception. Is there something else that I need to be doing?
[#|2011-04-19T01:07:11.807-0700|INFO|oracle-glassfish3.1|com.sun.grizzly.config.
GrizzlyServiceListener|_ThreadID=25;_ThreadName=http-thread-pool-8080(1);|Processor exception
java.lang.RuntimeException: java.lang.UnsupportedOperationException: Not supported yet.
at com.sun.grizzly.arp.AsyncProcessorTask.doTask(AsyncProcessorTask.java:131)
at com.sun.grizzly.http.TaskBase.run(TaskBase.java:193)
at com.sun.grizzly.http.TaskBase.execute(TaskBase.java:175)
at com.sun.grizzly.arp.DefaultAsyncHandler.handle(DefaultAsyncHandler.java:145)
at com.sun.grizzly.arp.AsyncProtocolFilter.execute(AsyncProtocolFilter.java:204)
at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextask.java:54)
at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.UnsupportedOperationException: Not supported yet.
at com.oracle.websockets.VideoSharingApplication.isApplicationRequest(VideoSharingApplication.java:43)
at com.sun.grizzly.websockets.WebSocketApplication.upgrade(WebSocketApplication.java:93)
at com.sun.grizzly.websockets.WebSocketEngine.getApplication(WebSocketEngine.java:113)
at com.sun.grizzly.websockets.WebSocketEngine.upgrade(WebSocketEngine.java:133)
at com.sun.grizzly.websockets.WebSocketAsyncFilter.doFilter(WebSocketAsyncFilter.java:52)
at com.sun.grizzly.arp.DefaultAsyncExecutor.invokeFilters(DefaultAsyncExecutor.java:171)
at com.sun.grizzly.arp.DefaultAsyncExecutor.interrupt(DefaultAsyncExecutor.java:143)
at com.sun.grizzly.arp.AsyncProcessorTask.doTask(AsyncProcessorTask.java

<p>I uploaded a new version of the source bundle <a ...

I uploaded a new version of the source bundle at this location. This new version uses Maven to avoid problems with dependencies and also implements all the methods required by the latest changes in Grizzly. I have tested it using GF 3.1. Good luck.

&nbsp;Can you upload it again? Link is not working!!!!!!

Can you upload it again? Link is not working!!!!!!

<p>&quot;The requested page could not be found.&quot; If you ...

"The requested page could not be found." If you could package up your project once again that would be great!

<p>I got this error:&nbsp;Grizzly Framework ...

I got this error: Grizzly Framework 1.9.35

com.sun.grizzly.websockets.HandshakeException: Null keys are not allowed.

at com.sun.grizzly.websockets.SecKey.<init>(SecKey.java:79)

at com.sun.grizzly.websockets.ServerHandShake.<init>(ServerHandShake.java:68)

at com.sun.grizzly.websockets.ServerNetworkHandler.handshake(ServerNetworkHandler.java:95)

at com.sun.grizzly.websockets.WebSocketEngine.upgrade(WebSocketEngine.java:136)

at com.sun.grizzly.websockets.WebSocketAsyncFilter.doFilter(WebSocketAsyncFilter.java:52)

at com.sun.grizzly.arp.DefaultAsyncExecutor.invokeFilters(DefaultAsyncExecutor.java:171)

at com.sun.grizzly.arp.DefaultAsyncExecutor.interrupt(DefaultAsyncExecutor.java:143)

at com.sun.grizzly.arp.AsyncProcessorTask.doTask(AsyncProcessorTask.java:94)

at com.sun.grizzly.http.TaskBase.run(TaskBase.java:193)

at com.sun.grizzly.http.TaskBase.execute(TaskBase.java:175)

at com.sun.grizzly.arp.DefaultAsyncHandler.handle(DefaultAsyncHandler.java:145)

at com.sun.grizzly.arp.AsyncProtocolFilter.execute(AsyncProtocolFilter.java:204)

at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)

at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)

at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)

at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)

at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)

at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)

at com.sun.grizzly.ContextTask.run(ContextTask.java:71)

at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)

at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)

I use NetBeans IDE 7.0.1 RC1 (Build 201106222100) and glassfish-3.1.1-b08.

In this glassfish version, there is grizzly 1.9.35.

<p>&nbsp;aa</p>

aa