Some time a go, a colleague of mine (Joris) told me about vert.x. I liked the idea of having something like node.js on the java platform. So I was interested, but I did not spend time on it. Recently I attended the talk of Tim Fox at the Goto conference in Amsterdam. Again I liked the idea about vert.x. Therefore I decided to give it a go.

For the axon trader sample I am working on I want to have a screen of executed trades. I want the screen to be updated as soon as possible. I have some wild ideas of connecting the vert.x event bus to the axon event bus, but that is not the focus for this blog post. In this blog post I am going to step through the stages of creating a sample vert.x application that makes use of sockjs to visit a page where you can connect to the event system and get updates about the executed trades. The executed trades are stored in a MongoDB instance. I use the query tables from the axon-trader sample, but this is not a requirement for you to replay the sample.

I am also not going to explain what vert.x is, the website http://vertx.io/ does a good job already. Expect a lot of code samples and of course the source is available in github. Check the references at the end.

If you are still interested, the click the read more link and start reading. Feedback and improvements are appreciated.

Create a web app

We start of creating a very basic web application. No static files, no ajax, no web2.0. Just a request and a response. The following code block shows you how to create a web server serving three urls: /trades, /orders/buy and /orders/sell. I also wanted to create a request nog found page, but this was not working in my current code base using groovy.

def server = vertx.createHttpServer()
def routeMatcher = new RouteMatcher()
routeMatcher.get("/trades") {req ->
    req.response.end "You requested trades"
}
routeMatcher.get("/orders/:type") {req ->
    def type = req.params["type"]
    if (type == "sell")
        req.response.end "You requested buy orders"
    else if (type == "buy")
        req.response.end "You requested sell orders"
    else
        req.response.end "You requested orders of unknown type"
}
server.requestHandler(routeMatcher.asClosure()).listen(8080, "localhost")

As you can see we need to create the http server and a Routematcher. Then we specify the routes to match. The trades is very easy and the orders are a little bit more complicated using a query parameter, but you should be able to understand this as well. The final registers the handler for the event of receiving a request and initializes the listener using the server name and the port.

Next step is obtaining data from mongoDB using the provided mongodb BusMod.

Attach the web app to mongodb

In the nature of vert.x and using events, we use the event bus to send a message to mongo and receive the reply in a callback. But before we can send a message, we need to initialize the mongo verticle. The next code block shows setting up the mongodb vertical using the defaults for address,host and port. The address is interesting, this is the address in the event bus that the mongodb persistence verticle listens for incoming requests. This default is vertx.mongopersistor. As you can see we use the mongo database for the axon trader. See references for more information about this sample.

def mongoConfig = ["db_name":"axontrader"]
container.with {
    deployVerticle('mongo-persistor', mongoConfig)
}

Before we can send messages to the event bus, we need to have a reference to the event bus. This is easy to obtain.

def eventBus = vertx.eventBus

Now we can send messages. I already mentioned the address to send the messages to. When sending a message to obtain data you need to provide the collection to read the data from and a matcher to filter the items that you need. I had some problems finding the right format for the query (see the problems section). You also want to provide a callback that receives the data that was found. The next code block shows how we return data back to the client that was found in mongo.

routeMatcher.get("/trades") {req ->
    def query = ["action":"find","collection":"tradeExecutedEntry","matcher":[:]]
    eventBus.send("vertx.mongopersistor",query) {message ->
        req.response.end "The following trades were found: ${message.body}"
    }
}

Have a look at the query where we specify the action, the collection and the matcher. There are other options, check the manual for them. The result is a message. The message contains the found data in the body. Also notice the used address for sending the message to using the event bus.

Since we want a human readable website, we cannot show a string containing json. Therefore we also want to have a better looking front end.

Create a client that shows the data

Creating a nice website involves providing access to some static files. Think about style sheets, javascripts and images. Of course vert.x has support for this. Vert.x comes with a verticle for serving static content. This is the WebServer. A nice functionality of this module is that you can use a bridge. This way the clients can send requests to the event bus immediately. A nice functionality, but I want to show another mechanism. I want to combine static resources with dynamic resources. This is not possible with the WebServer. There is no integration between the WebServer and the RouteMatcher. Therefore I have written a few functions. We start we the functions that handle the request for static resources. All resources are located in the static folder. The following two functions return the index.html and content requested starting with /static. These are actual files on the file system and also treated that way using the sendFile function.

routeMatcher.get("/") { req ->
    req.response.sendFile("traderclient/static/index.html")
}
routeMatcher.getWithRegEx("^\\/static\\/.*") { req ->
    req.response.sendFile("traderclient/" + req.path.substring(1))
}

Notice the way we use getWithRegEx to map all static content paths to the requested files that start with /static/.

The next step is more interesting. How to we render a webpage with the obtained executed trades. The next code block shows the new function.

routeMatcher.get("/trades") {req ->
    def query = ["action":"find","collection":"tradeExecutedEntry","matcher":[:]]
    eventBus.send("vertx.mongopersistor",query) {message ->
        def buffer = new Buffer();
        buffer.appendBytes(new File('traderclient/header.tpl').readBytes())

        buffer.appendString("<table class='table table-striped'><thead><tr><th>Company</th><th>amount</th><th>prize</th></tr></thead><tbody>")
        def results = message.body.results
        results.each { item ->
            buffer.appendString("<tr><td>${item.companyName}</td><td>${item.tradeCount}</td><td>${item.tradePrice}</td></tr>")
        }
        buffer.appendString("</tbody></table>")

        buffer.appendBytes(new File('traderclient/footer.tpl').readBytes())
        req.response.end buffer
    }
}

First we create the buffer object and store the header.tpl file in it followed by the table with the items and the footer.tpl. As you can see this is a lot of text for creating a nice looking page.

A big disadvantage is that the server now generates the html, the browser is a lot better in doing this. Therefore I want to take the next step and use the WebSocket technology to render pages.

Use WebSockets to send data to the browser

Since WebSockets are not yet supported by all browsers, vert.x has support for the socks library. The framework facilitates creating a socks server that is connected to the HttpServer. We start we a clean groovy script. The following code block shows the initialization of the HttpServer together with a RouteMatcher that takes care of the static content.

def server = vertx.createHttpServer()
def routeMatcher = new RouteMatcher()

routeMatcher.get("/") { req ->
    req.response.sendFile("traderclient/static/socketDemo.html")
}

routeMatcher.getWithRegEx("^\\/static\\/.*") { req ->
    req.response.sendFile("traderclient/" + req.path.substring(1))
}

server.requestHandler(routeMatcher.asClosure())

This code has been discussed before. The next part is initializing the Sockjs server. This can be done very easily.

vertx.createSockJSServer(server).bridge(prefix: '/eventbus', [[:]])
server.listen(9090)

The next part on the server is to periodically check the mongo datastore for trades and send these trades to registered listeners.

EventBus eb = vertx.eventBus
vertx.setPeriodic(1000l) {
    def query = ["action": "find", "collection": "tradeExecutedEntry", "matcher": [:]]

    eb.send("vertx.mongopersistor", query) {message ->
        eb.send("updates.trades", message.body)
    }
}

As you can see, vert.x comes with a function to periodically do something. With these lines we call the mongo persisters via the event bus. The callback calls the even but again and send the data to the address updates.trades. This address is important for the client that needs to register a subscriber.

Now we can move on to the client. I am not going to show the complete page. Refer to the sources later on. The next code block shows the javascript code required to open the connection using sockjs and to subscribe to an address. The subscriber also defines the callback to use when a message is received.

    var eb = null;

    function subscribe() {
        if (eb) {
            eb.registerHandler("updates.trades", function (msg, replyTo) {
                var results = msg.results;
                $('#tradesBody').text('');
                for (var i = 0; i < results.length; i++) {
                    var item = results[i];
                    $('#tradesBody').append("<tr><td>" + item.companyName + "</td><td>" + item.tradeCount + "</td><td>" + item.tradePrice + "</td></tr>");
                }
                $('#lastUpdate').text(" " + new Date());
            });
        }
    }

    function closeConn() {
        if (eb) {
            eb.close();
        }
    }

    function openConn() {
        if (!eb) {
            eb = new vertx.EventBus("http://localhost:9090/eventbus");

            eb.onopen = function () {
                $("#connectionStatus").text("Connected");
                subscribe();
            };

            eb.onclose = function () {
                $("#connectionStatus").text("Not connected");
                eb = null;
            };
        }
    }

    $(document).ready(function () {
        $("#closeButton").click(function () {
            closeConn();
        });

        $("#connectButton").click(function () {
            openConn();
        });
    });

The openConn function creates a connection to the event bus at a certain url. We use jQuery to change the status to connected and Not connected. We also call the subscribe function to subscribe to the trades.updates address. The subscribe function registers a handler for receiving a message on the mentioned address. The callback receives the results. Within the callback we loop over the results and using jQuery we replace the current contents of the table body.

Now we have a webpage that is being updated by the server when it is connected. The timer can be used to see things are changing. I use the mongodb collections from the Axon-Trader sample. In the nearby future I want to create an application with vert.x that responds to events coming from the axon event bus. But more on this in a future blog post.

Screen Shot 2012 06 01 at 14 16 54

Problems during development

Caused by: groovy.lang.MissingMethodException: No signature of method: org.vertx.java.core.http.RouteMatcher.noMatch() is applicable for argument types: (App$_run_closure3) values: [App$_run_closure3@77587f0]
Possible solutions: noMatch(org.vertx.java.core.Handler), patch(java.lang.String, org.vertx.java.core.Handler), each(groovy.lang.Closure)

Seems to be a problem that is already resolved in the master, but not in the current release. This error is found when trying to use the noMatch method of the RouteMatcher.

mei 31, 2012 10:00:48 AM org.vertx.java.core.logging.impl.JULLogDelegate error
SEVERE: matcher must be specified
[message:matcher must be specified, status:error]

The problem was an error in the query. You need to provide a matcher, but how to do it? The following line show the query with at the end the matcher that you must configure, even if you want to match everything.

def query = ["action":"find","collection":"tradeExecutedEntry","matcher":[:]]

Concluding

I really like what vert.x is doing. With very little code you can create a very responsive application. I really like the WebSocket and sockjs integration. The amount of modules are limited, but more will come in the nearby future. Check the references for more information about vert.x

References

First steps with vert.x: creating a WebSocket sample
Tagged on: