Liferay, WebSockets, and Node: Good Times!

Lately I've been tinkering a lot with lightweight, asynchronous, event-driven apps on Liferay using a variety of established techniques and frameworks. It's a nice way to build apps! After working it for a while, I wanted to share what I've learned, so put together a talk and was fortunate to be selected to attend and speak at this year's KCDC, and presented my learnings and a demo via a presentation on Event-driven Programming with Java and HTML5 WebSockets.
 
You can find the slides on SlideShare, and the demo code on GitHub.
 
At the end of the presentation I showed a demo, and I wanted to go into a bit more detail here. It is essentially a WebSockets demo using a Google Heatmap to visualize the location of bloggers who are blogging on a Liferay site. I used Tomcat 8.x for the JSR-356 WebSockets implementation, partly because I am comfortable it, but also because it has a bug that I wanted to use to demonstrate a point I made during the talk :) I'm pretty sure GlassFish doesn't suffer from this!
 

Social Driver

To simulate lots of people doing activity on Liferay (and therefore generating activities for which I can listen), I re-used my Social Driver, resurrected from 7Cogs. This code spawns theads that programmatically create users and create blog entries, wiki pages, forum posts, and do other activities like vote and comment on content. It does this in separate threads, which simulates lots of people doing things on your Liferay site.
 
I have covered the basics of the SocialDriver in my series of "7Cogs is Dead! Long Live 7Cogs!" posts ( here, and here), which I hope to finish off in the next couple of weeks. 
 

Demo Part 1

In the first part of the demo, I have a Google Heatmap which visualizes the location of the fake users, based on their registered address (which is also faked). When content (blogs, wikis, forums) is created, a SocialActivity is generated. My hook listens for these events, and sends them to my WebSocket endpoint, which ships them to my client webpage with the Google map.
It all works great when you have a single thread generating events.  The map builds nicely, and all is well. However, a few seconds after you start up more threads, things get weird, and WebSocket messages emitted from the server get jumbled and mixed together, causing the browser to immediately fail the websocket connection, and the client comes to a grinding halt (Liferay happily continues to create activities, though).
 

Demo Part 2

In part 2 we used Wireshark to inspect the network traffic, in an attempt to debug the problem.  Looking at the network traffic reveals that in the end, the last few WebSockets frames are mixed up / jumbled up, causing the browser to misinterpret the bytes, and fail quickly (which is nice!).
 
The problem appears to be that the code in Tomcat for sending messages down the pipe isn't multithread-safe.  Two or more threads can get into the same area of code, and send content at the same time, and this is exactly what happened here: my blog thread and my wiki thread tried to send messages to the client at the same time, and one's message was mixed in with the other, causing the browser to issue a cryptic Could not decode a text frame as UTF-8.  Looking at the offending packet in Wireshark:
You can see the complete message of {"address":"Sudan"} but then some more bits that is the beginning of the next message, which the browser tries to interpret as text, and fails (it's actually the beginning of the next websocket frame).
 
Synchronizing the code that calls into Tomcat does the trick (e.g. via synchronized ), but a) I shouldn't have to do that because it's part of the spec  (and I think this is a bug in Tomcat) , and b) Tomcat might not be the best place to scale out, especially because it's hosting Liferay already.   Node.js to the rescue!
 

Demo Part 3

Here, we let Node handle the websockets broadcasts to the clients, while Tomcat and Liferay handle the portal itself.
The code in my tiny node server (which requires websockets.js, via npm install websockets) does the trick.  It listens for messages over HTML (this could have, and probably should have been done with redis pub/sub but I was out of time), and then broadcasts them to all clients listening on the websocket. In this demo there's only one client, 
 
With node in place, and click the switch on the portlet, to switch over to it and then happily start up many threads and watch our heatmap build nicely:
 

Lessons learned

  • Coding event-based, asynchronous web program is fun and exciting! There are many frameworks and technologies to make it easy, both on client and server, and if I can do it, anyone can do it :)
  • It's really easy to integrate awesome apps with Liferay, due to its Java heritage, rich APIs and lightweight JSON or RESTful web services.
  • Java EE features (like JSR-356, aka WebSockets) and other upcoming technologies in Java EE 7 will lower the Java EE learning curve even further.
Enjoy!
Blogues
Hi James! really very interesting sample - only now return back to it (because need also to do some experiments with Liferay and websockets). Few questions:
1. Liferay has part of API designed for about same - called poller.Do you know - are there any plans to implement Poller on top of Websocket API?
2. Is Liferay (6.1.x) working with tomcat 8? I've got strange errors during starting liferay 6.1.2 (about spring listeners initialization) - just wan to clarify which version or LR and Tomcat you used for this demonstration? Did you do any special tricks to run Liferay on Tomcat 8?
3. Do you know - is bug you met (thread-safe websocket) in tomcat 8 registered in their bug-tracking? Is it fixed in current version (tomcat 8-RC1)?
Alexey -

1: Not sure, will ask!

2: There were a few (very minor) changes I had to make to make it work emoticon The version I used was compiled from revision 1479152 of Tomcat's trunk. Liferay was 6.1 CE GA2, with a few modifications because of changes in Tomcat 8's JavaEE implementation. I basically tried it, it failed (with a clear error message), so i went into Liferay source code and changed what needed to be changed. I had to change some spots where Liferay would call servletContext.getRealPath(StringPool.BLANK); to be servletContext.getRealPath("/"), I guess something changed in the Java EE spec or Tomcat changed something.

3: Nope, I believe it was fixed by a developer adding a bunch of 'synchronized' keywords to various methods before I had a chance to file any bug, e.g. see http://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
Hi James,
Your article is very inspiring. I gone through liferay poller recently and found that its not very efficient. So I did a hacky thing and developed a hook to replace liferay poller with nodejs/sockjs ;)
https://github.com/saggiyogesh/liferay-node-poller
Liferay is very cool..