
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.
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!