Vertx + Pebble + Bootstrap = <3

Update


In between, there has been an official implementation of Pebble for Vertx. You should use this dependency in favor of my implementation.


It’s been a long time since I wrote something in here. That is mainly because I moved to a project where no Java was involved and I had to code PHP for quite some time. While I did this, I came closer to web development and the bootstrap-library from twitter. For a new project I decided to switch to Java again, because we had realtime requirements and – to be honest – because I wanted to code in Java again. A lot has happened in between though: Java 8 came out and the whole micro-services train started to move into direction from theoretical discussions to rock-solid technologies which are used in production. One of which is vertx which was released in the third version.

The Goal

In this blogpost I want to show you how you can write a simple chat application using the technologies mentioned above:

  • Vertx: „A polyglot tool-kit to build reactive applications on the JVM”
  • Bootstrap: „The most popular HTML, CSS, and JS framework for developing responsive, mobile first projects on the web“
  • Pebble: „A lightweight but rock solid Java templating engine”

In chat applications there is the requirement that messages have to be pushed from the server to the client. This makes a classic web server stack obsolete since the request-response pattern does not apply. I decided to use WebSockets which are built on top of the well-known HTTP protocol and can be easily implemented on client-side using Javascript.

The WebSocket Server

While I’ve been working on the PHP project, I realized that I don’t have to care about writing web servers. You just write your code and let application servers like apache or nginx do the rest. But we are back in the Java-World. And in the Java-World you have to take care of that. This post is not a deep-dive into every aspect of vertx and I will just explain the parts that are necessary for this post to be understood. If you’re hooked, I recommend you reading their docs. Please note that you don’t have to copy the code snippets one by one, because I’ve setup a github repo for this purpose.


@Override
public void start() throws Exception {
super.start();
router.route("/ws/websocket/*").handler(request -> {
ServerWebSocket webSocket = request.request().upgrade();
logger.debug("New connection {}", webSocket.binaryHandlerID());
webSocket.handler(buffer -> {
logger.debug("Received: {} {} ", webSocket.binaryHandlerID(), new String(buffer.getBytes()));
JsonObject json = new JsonObject(new String(buffer.getBytes()));
json.put("senderId", webSocket.binaryHandlerID());
vertx.eventBus().publish("chat.broadcast", json);
});
MessageConsumer<JsonObject> consumer = vertx.eventBus().<JsonObject>consumer("chat.broadcast", messageHandler->{
JsonObject body = messageHandler.body();
String senderId = body.getString("senderId");
if(!senderId.equals(webSocket.binaryHandlerID())){
body.remove("senderId");
webSocket.writeBinaryMessage(Buffer.buffer(body.encode()));
}
});
webSocket.exceptionHandler(e -> logger.error(e.getMessage(), e));
webSocket.closeHandler(c -> {
logger.debug("Websocket {} closed.", webSocket.binaryHandlerID());
consumer.unregister();
});
});
vertx.createHttpServer().requestHandler(router::accept).listen(8080);
}

Line 4: First of all, we need a Router. It is used to route the request coming from the vertx-Httpserver. We define a route and handler which is responsible for handling that route so whenever someone is calling „/ws/websocket/„ the handler will be called. The asterisk in this case means that the request could also be „/ws/websocket/asdf“ and would still be handled by that handler.

Line 5: This is how to upgrade a regular request to a WebSockets in vertx (for the sake of completeness: there are also other ways of handling WebSockets in vertx, but I liked this the most).

Line 7: Here we added a handler that is called whenever a message is received on the WebSocket. We expect messages to be binary encoded JSON. For every message we add a senderId-field with a unique identifier for each client to the message and send it to the event bus. The event bus is used to the send messages between several vertx instances – either locally or even clusterwide. But it also works only for one vertx instances like in our case. Every message needs to have an address which is in our case „chat.broadcast“. One thing worth mentioning: there is also a send-method on the eventbus which implies the call of ONE handler, whereas publish will signal all registered handlers.

Line 13: This is how the client will receive messages. We register a consumer on the eventbus that is notified whenever a message is published. We evaluate the notification and check for the senderId-field. If we are not the sender, the message is send to our client.

Line 21: If something unexpected happens, we want to log it.

Line 22: When the client is closed, we have to cleanup everything.

Line 27: Ramp up the HTTP server with the defined route.

That is all logic needed for handling the client-server communication. 

The web server

For convenience reasons, I decided to add the client into the same application. This basically means that we need to have a web server that delivers the HTML-document which has a java-script-client embedded. 


@Override
public void start() throws Exception {
super.start();
router.route("/resources/*").handler(StaticHandler.create("webroot/resources"));
router.route().handler(FaviconHandler.create("webroot/favicon.ico"));
PebbleTemplateEngine templateEngine = new PebbleTemplateEngine();
TemplateHandler templateHandler = TemplateHandler.create(templateEngine, "webroot/templates","text/html");
router.route("/templates/*").handler(templateHandler);
router.route().handler(h->h.reroute("/templates/chat_client.html"));
vertx.createHttpServer().requestHandler(router::accept).listen(8080);
}

Line 4: We’re working again with a router here. To deliver static content (JavaScript-Libraries, CSS-Content and anything else ’static’) we need a StaticHandler which is part of the Vertx library. We need to define a base-path in which our files are stored. In our case it is “webroot/resources“ which I’ve created under „src/main/resources/„. (The Router is the same that we used for the WebSocket-server. This is important, because if we would create a Router in each Verticle some requests wouldn’t be served. This is because the HttpServer would call the Routers in a round-robin-fashion and therefore not all routes we defined would be known.)

Line 5: Our app will have a fancy favicon, this is how you deliver it.

Line 6: In this example we’re using a templating engine to render our HTML pages. Vertx comes with an out-of-the-box-support of four template engines but I wanted to give pebble a try. The main reason for using Pebble is that I like the templating syntax and it gives a nice performance (https://github.com/mbosecke/template-benchmark) too. Therefore I created my own PebbleTemplate-Engine and added it to the TemplateHandler. The templates are located under „webroot/templates” in this example. 

Line 9: Route every request that has not matched by any other route to our chat_client.html.

Line 10: Ramping up the HTTP server. But wait! Didn’t we use the same port for the WebSocket-server? Yes we did, but vertx is smart enough to combine the routes and handle everything accordingly.

Bring it


public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
Router router = Router.router(vertx);
vertx.deployVerticle(new WebsocketVerticle(router),h ->{ if(h.succeeded()) logger.info("Websocket ready.");});
vertx.deployVerticle(new WebserverVerticle(router),h ->{ if(h.succeeded()) logger.info("Webserver ready.");});
}

view raw

Main.java

hosted with ❤ by GitHub

We create the server using the good old main-method and instantiate a Vertx-instance as shown. Vertx has the concept of Verticles that provide„a simple, scalable, actor-like deployment and concurrency model“. One benefit is that you can write code as if it was single threaded, because code is always called from the same eventloop. The whole example would totally have worked without Verticles, but creating a Router and everything directly in the main-method. Using Verticles however encourages you to separate your concerns and can be used as an entry-point for developing microservices. Another thing worth mentioning is, that Verticles are deployed asynchronously and therefore the Main-Thread will not block until they are deployed.

The Template

The client is written in JavaScript and embedded into an HTML-document so let’s first look at the document-layout. 


<!DOCTYPE html>
<html lang="en">
{% include './header.html'%}
<body>
{% include './navbar.html'%}
<div class="container-fluid">
<div class="row">
<div class="col-md-offset-1 col-md-10 col-md-offset-1">
<div class="starter-template" id="content">
{% block content %}
{# This section is to be overriden by child templates #}
{% endblock content %}
</div>
</div>
</div>
</div>
{% include './footer.html'%}
<div id="footer">
{% block footer %}
{# This section is to be overridden by child templates #}
{% endblock footer %}
</div>
</body>
</html>

view raw

base.html

hosted with ❤ by GitHub

This is the skeleton from which every page will inherit and is basically the start-template provided by bootstrap, however divided into several parts. The include-keyword includes the content of the given source into this document like you would expect (one has to prepend a dot followed by a slash to the source-name in order to make the loading work (at least on my mac)). The HTML itself is pretty basic but I’ll explain the used element classes that come with bootstrap:

  • container-fluid describes a container spanning over the whole document
  • rows are used to define a horizontal group of columns
  • col-md-offset-1 describes that we’ll have 1 column padding on each side without content. The col-md-10 describes a column that has a size of 10/12 of the width of the enclosing row-div since rows are divided in 12 md-columns. If we would add two divs with each having the class col-md-5, we would end up having two columns in one row (plus the two offset-columns). Because bootstrap is mobile first, this is responsive and automatically scales well on all kinds of devices. We could also define the classes not using col-md-x but col-xs-x or col-s-x to have different layouts for devices with extra-small (phones) or small (tablets) devices. For further reading I recommend the bootstrap-docs (http://getbootstrap.com/css/#grid).
  • the last div has the starter-template class which basically moves the content below the navigation header.

Now we are coming to another interesting part of the template engine. We define a block called ‚content‘ which can be overridden by any page that is extending this document. (In our example we only have one site – the chat application – which makes the usage of templates a bit obsolete, but that is another story ;)). We could also add some default values but in this case it is not necessary. The key takeaway from this is that you can write all the boilerplate code in one file and when writing a new pages you just have to add the parts that are new. 

The Client


{% extends './base.html' %}
{% block content %}
<h1>Chat Client</h1>
<p class="lead">
<div class="row">
<div class="col-md-2">
<label>Username</label>
<input id="username" type="text" class="form-control"/>
</div>
<div class="openOnConnect collapse">
<div class="col-md-2">
<label>Action</label>
<select id="action" class="form-control">
<option value="broadcast" selected>Broadcast</option>
</select>
</div>
<div class="col-md-8">
<label>Message</label>
<textarea id="messageBody" class="form-control" placeholder="some text"></textarea>
</div>
</div>
</div>
<div class="row">
<br/>
<div class="col-md-2">
<button class="form-control btn btn-default" value="Connect" onclick="connect()">Connect</button>
</div>
<div class="col-md-1 openOnConnect collapse">
<button class="btn btn-success" value="Send" onclick="send()">Send <span class="glyphicon glyphicon-cloud-upload"></span></button>
</div>
</div>
<div class="row col-md-12">
<h2>Output</h2>
<div id="output"></div>
</div>
{% endblock content%}
{% block footer %}
<script type="application/javascript">
var connection;
var output;
function connect(){
output = $('#output');
connection = new WebSocket('ws://localhost:8080/ws/websocket/');
connection.binaryType = 'arraybuffer';
connection.onopen = function () {
sendInternal($('#username').val(),'joined');
$(".openOnConnect").collapse('toggle');
};
// Log errors
connection.onerror = function (error) {
$(".openOnConnect").collapse('toggle');
};
// Log messages from the server
connection.onmessage = function (e) {
if(e.data != 'n'){
var dataBytes = new Uint8Array(e.data);
appendResponse(String.fromCharCode.apply(null, dataBytes));
}
};
connection.onclose = function(e){
appendToDocument("alert-danger","glyphicon-exclamation-sign","Remote closed connection.");
};
}
function send(){
sendInternal($("#username").val(),$("#messageBody").val());
}
function sendInternal(username,body){
var message = {};
message.username = username;
message.body = body;
appendAndSend(message);
}
function appendAndSend(message){
appendRequest(message);
connection.send(JSON.stringify(message));
}
function appendResponse(message){
var asJson = JSON.parse(message);
if(asJson.failure != null){
appendToDocument("alert-warning","glyphicon-cloud-download",message.body)
}else if(asJson.error != null){
appendToDocument("alert-danger","glyphicon-cloud-download",message.body)
}else{
appendToDocument("alert-success","glyphicon-cloud-download",asJson.username+': '+asJson.body)
}
}
function appendRequest(message){
appendToDocument("alert-info","glyphicon-cloud-upload",message.body)
}
function appendToDocument(divClass,prefix,message){
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
var html = '<div class="col-md-12 alert '+divClass+'"><span class="glyphicon '+prefix+'"></span> '+getTime()+'<br/>'+message+'</div>';
pre.innerHTML = html;
output.prepend(pre);
}
function getTime(){
var time = new Date();
return time.getHours()+':'+time.getMinutes()+':'+time.getSeconds()+'.'+time.getMilliseconds();
}
function disconnect(){
if(connection != null){
connection.onclose = function () {}; // disable onclose handler first
connection.close()
}
}
window.onbeforeunload = disconnect();
</script>
{% endblock footer %}

By using the extends keyword we tell Pebble to inherit from that document. We’re overwriting the content-block with straight-forward HTML. On the bottom of the page, the WebSocket-client logic is defined in Javascript. On Line 45 we’re opening a connection to localhost. Because the server expects and sends binary frames, we set the binaryType accordingly. The rest of the code is not very hard to understand and should be self-explainority. 

The Test

I hosted the complete project on GitHub. The project requires maven and Java 8 to be built properly. After you cloned the project, run Main.java in the IDE of your choice, open two tabs in your browser, point to localhost:8080, select a username and start chatting! As you can see, messages are instantly pushed to the other clients – the benefit of WebSockets. I left some space for modifications though: right now you can only broadcast messages. But what if you want to send a private message to just one recipient? Maybe you want to personalize your account by uploading avatars or securing them with a password on a separate page/template? If I find the time I will cover this in one of my next blog-posts, but until then: happy coding!