31 May 2012 Halo Vert.x – Building a Chatbot With Vert.x
A little while ago I did an internal talk at Shine describing the use cases and benefits of using asynchronous web technologies. As part of the talk I showcased a little demo I’d put together called Haloworld.
Haloworld is a simple web-based chat application with a chatbot called Hal (inspired by the infamous movie bot) who can answer various questions you throw at it.
At the time, I used Atmosphere on top of Jetty for the demo, in order to illustrate that these sort of applications are now easy to build on top of the JVM. While it was relatively painless, it still seemed quite heavyweight compared to using something like Socket.io or SockJS with Node.js.
Not long afterwards, I came across Vert.x, a new JVM based framework for developing highly scalable networked applications. Vert.x seemed a much better fit for Haloworld, so I decided to port the demo across to it. You can find the results on our Github repository along with details on how to get up and running with the demo.
Vert.x provides a platform for building simple but powerful networked applications that can be easily clustered to scale horizontally. It’s a polyglot platform which means that it supports multiple JVM based programming languages (currently Java, JavaScript, Ruby and Groovy with more to come).
One of the nicest things about Vert.x is its simple but powerful APIs for doing asynchronous network and file IO. In just a few lines of code you can create clients and servers for TCP, HTTP and SockJS. If you are used to developing with Node.js you will probably feel quite at home with Vert.x. Because the API and programming model are so straightforward there is a low overhead to getting something up and running.
For example, here’s the code that creates the SockJS server used to handle send and receive messages and feed results:
private void createSockJsServer() { // create HTTP server HttpServer server = vertx.createHttpServer(); JsonObject config = new JsonObject().putString("prefix", "/chat"); // setup a white list of addresses that browser clients can post to. JsonArray permitted = new JsonArray() .add(new JsonObject().putString("address", "chat.msg")) .add(new JsonObject().putString("address", "chat.manager.ping")) .add(new JsonObject().putString("address", "chat.manager.list")); // Create a SockJS server off the HTTP server and bridge the event bus to the client side vertx.createSockJSServer(server).bridge(config, permitted); server.listen(8081); }
You design your Vert.x application as a series of objects called “verticles”. A Vert.x application instance may have many verticle’s running on it and many verticles can share the same thread.
Vert.x comes with a simplified concurrency model. Each verticle is single threaded and is always guaranteed to run on the same thread each time. Each verticle also gets its own class loader. So not only are verticles isolated from each other, they also don’t need to worry about concurrent access to any state they hold.
For example, when you write code to update a verticle’s field, you don’t have to worry that another thread will come in and change that field underneath you, because your thread is the only one that is executing code in that verticle. This feature makes writing applications in a highly asynchronous environment a whole lot easier.
Vert.x has an event bus that verticles can use to communicate with each other. Vert.x instances simply register a handler on a particular address (i.e a topic) to receive messages sent to that address. Vert.x supports extending this event bus across multiple Vert.x instance and computers in a cluster, making it easier to extend your applications out horizontally for extra scalability and/or fault tolerance.
Vert.x provides a built-in SockJS server. If you’re not familiar with SockJS, it provides a client side library to emulate WebSockets over a number of legacy transports (long polling, HTTP streaming etc.), which makes writing web applications that need bidirectional communication between the client and the server a whole lot easier.
One of the coolest features that Vert.x provides on top of its SockJS support is the ability to bridge the Vert.x event bus across to the browser. It does this using a small JavaScript library that works together with SockJS. In other words, your browser side code can now send and receive messages to and from any other Vert.x enabled browser or server instance that is connected to the event bus.
For example here’s the code to connect to the event bus that we bridged across using the SockJS server in the previous code snippet:
function connectToServer() { if (!eb) { eb = new vertx.EventBus("http://localhost:8081/chat"); eb.onopen = function() { console.log("Connected to chat"); // subscribe to chat address so we can receive messages setupSubscriptions(); /* -- snip -- */ }; eb.onclose = function() { console.log("Disconnected from chat"); eb = null; }; } }
Here’s the code to send and receive messages on the event bus from the browser:
// Send a message to the event bus function send(address, message) { if (eb) { eb.send(address, message); } } function setupSubscriptions() { if (eb) { // register to receive any messages sent on the chat address eb.registerHandler(CHAT_ADDRESS, function(msg, replyTo) { handleMessage(msg) }); /* -- snip -- */ } }
Compare this to the very similar code on the server side (which would be almost identical if we were using JavaScript on the server):
// Register to receive event bus messages sent to the chat address vertx.eventBus().registerHandler(CHAT_ADDRESS, new Handler<Message<JsonObject>>() { public void handle(Message<JsonObject> event) { handleChatMessage(event); } } ); // Send a message on the event bus to the chat address JsonObject msg = new JsonObject().setString("messageText", "Hello World"); EventBus eventBus = vertx.eventBus(); eventBus.send(CHAT_ADDRESS, msg);
This makes writing distributed web applications a whole lot easier. Of course, there are security concerns to be wary of here, which is why Vert.x discards any messages put onto the event bus by a browser unless you have specifically whitelisted the address that it is sending them to.
All of the above features made converting Haloworld to run on Vert.x more of a pleasure than a challenge. The key change I made was to make good use of the event bus. Each part of the application is now a separate Vert.x module (verticle) that communicates with other modules over the event bus. The diagram below shows how this works conceptually.
I created an HTTP server for serving up the static content (using Vert.x’s built in web-server module) and a separate SockJS server on a different port for handling chat messages. Bridging the SockJS server to the event bus and setting aside a designated address (chat.msg) for chat messages, trivially provided support for multiuser chat.
The other major design change I made was to split out the chatbot HAL into its own verticle talking over the event bus. This seemed more natural than before where it was strongly tied to the chat manager.
I also added better support for communicating presence information (i.e. who is in the chat session). Originally the demo did not support presence notifications. By making HAL and the chat clients send periodic ping messages, the chat manager is able to detect when users enter and leave the chat. Vert.x’s single threaded concurrency model for verticles made this easy because I didn’t need to worry about synchronising on the members list when handling pings or checking for client timeouts.
All in all I had a lot of fun doing this conversion, and I’m keen to see what else is possible with Vert.x. I’d highly recommend you checking it out and would encourage you to give the Haloworld demo a go and dive down into the code to see how it all works.
Dave Z
Posted at 03:01h, 01 SeptemberGreat Post! I started playing around with vert.x this week, so your thoughts and code here are helpful.
Simon Collins
Posted at 09:43h, 24 SeptemberGlad it was helpful Dave. Let me know if you have any questions.
Harsha
Posted at 03:54h, 13 SeptemberHi Simon,
I did download your code for the chatbot application as this article seemed to be very interesting and I am a newbie to vert.x. At this current time, I am hitting a lot of issues with d3.v2.min.js file(tried all the latest version available in the d3js and github.com site; did not work), finally got rid of the file itself as per one suggestion on the net and there were no further complains, also the console command in haloworld.js was not being recognized so had to use window.console declaration to get it working without any javascript errors. Now, when I do login and enter my question I do not see any posting on the main page. Kindly suggest as to what could be going wrong and any tips that you may be aware to get it working for any potential issues.
Thanks
Harsha
Adam
Posted at 07:18h, 02 OctoberSimon ,
Github project is no longer available, do you have an updated link in place
Adam