Vert.x is a modern, lightweight framework to build high performance applications running on the Java Virtual Machine. The framework is polyglot so that you’re able to write your application in Java, Groovy, Ruby, Python or even JavaScript.
In addition it offers a nice component system, an actor-like concurrency model a distributed event bus and an elegant API to create scalable applications in no time.
In the following tutorial we’re going to build a websocket chat by creating a HTTP server and the websocket server using Vert.x, Java and Maven.
The Chat Application
We want to build a chat application where a user is able to enter a chatroom from a list of available rooms, and receives updates for the specific chat room.
As I am lazy – for the client side I am going to recycle the client code (HTML, CSS, Javascript) from my tutorial "Creating a Chat Application using Java EE 7, Websockets and GlassFish 4".
On the server side we’re going to set up an HTTP server to serve the HTML file and the other web resources like CSS, Javascript etc listening on port 8080 and a websocket server listening on port 8090 (we also could have used on server-instance for both).
Project Setup and Dependencies
Using Apache Maven, there is an archetype we may use to speed up the project setup process: io.vertx:vertx-maven-archetype:2.0.0-final
This is my generated pom.xml (shortened, for the full descriptor file please have a look at my git repo):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hascode.tutorial</groupId>
<artifactId>vertx-websocket-chat</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>Vert.x Websocket Chat</name>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<vertx.pullInDeps>false</vertx.pullInDeps>
<module.name>${project.groupId}~${project.artifactId}~${project.version}</module.name>
<vertx.version>2.0.0-final</vertx.version>
<vertx.testtools.version>2.0.0-final</vertx.testtools.version>
<maven.compiler.plugin.version>3.0</maven.compiler.plugin.version>
<maven.resources.plugin.version>2.6</maven.resources.plugin.version>
<maven.clean.plugin.version>2.5</maven.clean.plugin.version>
<maven.vertx.plugin.version>2.0.0-final</maven.vertx.plugin.version>
<maven.surefire.plugin.version>2.14</maven.surefire.plugin.version>
<maven.failsafe.plugin.version>2.14</maven.failsafe.plugin.version>
<maven.surefire.report.plugin.version>2.14</maven.surefire.report.plugin.version>
<maven.javadoc.plugin.version>2.9</maven.javadoc.plugin.version>
<maven.dependency.plugin.version>2.7</maven.dependency.plugin.version>
</properties>
[..]
<dependencies>
<!--Vertx provided dependencies -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-platform</artifactId>
<version>${vertx.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.vertx</groupId>
<artifactId>vertx-maven-plugin</artifactId>
<version>${maven.vertx.plugin.version}</version>
<executions>
<execution>
<id>PullInDeps</id>
<phase>prepare-package</phase>
<goals>
<goal>pullInDeps</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.plugin.version}</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven.resources.plugin.version}</version>
<executions>
<execution>
<id>copy-mod-to-target</id>
<phase>process-classes</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<overwrite>true</overwrite>
<outputDirectory>target/mods/${module.name}</outputDirectory>
<resources>
<resource>
<directory>target/classes</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven.dependency.plugin.version}</version>
<executions>
<execution>
<id>copy-mod-dependencies-to-target</id>
<phase>process-classes</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>target/mods/${module.name}/lib</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
[..]
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/mod.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>assemble</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Chat Server Verticle
We’re creating one verticle to create a http server running on port 8080 and a websocket server running in port 8090.
First we need to inherit from org.vertx.java.platform.Verticle here and override the start method.
package com.hascode.tutorial.vertx_tutorial;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.vertx.java.core.Handler;
import org.vertx.java.core.buffer.Buffer;
import org.vertx.java.core.eventbus.EventBus;
import org.vertx.java.core.http.HttpServerRequest;
import org.vertx.java.core.http.RouteMatcher;
import org.vertx.java.core.http.ServerWebSocket;
import org.vertx.java.core.logging.Logger;
import org.vertx.java.platform.Verticle;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class WebserverVerticle extends Verticle {
@Override
public void start() {
final Pattern chatUrlPattern = Pattern.compile("/chat/(\\w+)");
final EventBus eventBus = vertx.eventBus();
final Logger logger = container.logger();
// 1) HTTP Server
// 2) Websockets Chat Server
}
}
HTTP Server
The following cheap HTTP server is there to serve the HTML file and the Javascript and CSS resources (like Bootstrap, jQuery etc..).
We’re using a RouteMatcher here to handle different URLs and basically that’s all.
RouteMatcher httpRouteMatcher = new RouteMatcher().get("/", new
Handler<HttpServerRequest>() {
@Override
public void handle(final HttpServerRequest request) {
request.response().sendFile("web/chat.html");
}
}).get(".*\\.(css|js)$", new Handler<HttpServerRequest>() {
@Override
public void handle(final HttpServerRequest request) {
request.response().sendFile("web/" + new File(request.path()));
}
});
vertx.createHttpServer().requestHandler(httpRouteMatcher).listen(8080, "localhost");
Websockets Server
The websocket server listens on port 8090 for connections.
First of all we’re rejecting every request that does not match our specified “path”. Otherwise we’re assigning the connection id to the chat room.
Vert.x offers a shared data structure that we’re using here to make this information available to all workers.
Afterwards we’re adding a close handler to remove the session from the data pool when the connection is closed.
Finally when the client pushes a message to the websocket server, we’re adding the current date to the chat message in the JSON format and we’re broadcasting the message to all registered chatters for the specific chatroom using the Vert.x EventBus and the connection id .
vertx.createHttpServer().websocketHandler(new Handler<ServerWebSocket>() {
@Override
public void handle(final ServerWebSocket ws) {
final Matcher m = chatUrlPattern.matcher(ws.path());
if (!m.matches()) {
ws.reject();
return;
}
final String chatRoom = m.group(1);
final String id = ws.textHandlerID();
logger.info("registering new connection with id: " + id + " for chat-room: " + chatRoom);
vertx.sharedData().getSet("chat.room." + chatRoom).add(id);
ws.closeHandler(new Handler<Void>() {
@Override
public void handle(final Void event) {
logger.info("un-registering connection with id: " + id + " from chat-room: " + chatRoom);
vertx.sharedData().getSet("chat.room." + chatRoom).remove(id);
}
});
ws.dataHandler(new Handler<Buffer>() {
@Override
public void handle(final Buffer data) {
ObjectMapper m = new ObjectMapper();
try {
JsonNode rootNode = m.readTree(data.toString());
((ObjectNode) rootNode).put("received", new Date().toString());
String jsonOutput = m.writeValueAsString(rootNode);
logger.info("json generated: " + jsonOutput);
for (Object chatter : vertx.sharedData().getSet("chat.room." + chatRoom)) {
eventBus.send((String) chatter, jsonOutput);
}
} catch (IOException e) {
ws.reject();
}
}
});
}
}).listen(8090);
Vert.x Module Configuration
This step is optional but it allows us to capsule the application as a Vert.x module by adding the following mod.json in src/main/resources:
{
"main":"com.hascode.tutorial.vertx_tutorial.WebserverVerticle",
"description":"hasCode.com Vert.x Websocket Chat Sample",
"author": "Micha Kops",
"homepage": "https://www.hascode.com/"
}
The Client Side
Nothing special here – we’re opening a new connection to the web socket at ws://0.0.0.0:8090/chat/CHATROOMNAME.
The user may send a JSON formatted message to the server, a callback is defined for updates from the server that updates the client’s view.
Finally when leaving a chat room, the websocket connection is closed.
The javascript (excerpt):
<script>
var wsocket;
var serviceLocation = "ws://0.0.0.0:8090/chat/";
var $nickName;
var $message;
var $chatWindow;
var room = '';
function onMessageReceived(evt) {
var msg = JSON.parse(evt.data); // native API
var $messageLine = $('<tr><td class="received">' + msg.received
+ '</td><td class="user label label-info">' + msg.sender
+ '</td><td class="message badge">' + msg.message
+ '</td></tr>');
$chatWindow.append($messageLine);
}
function sendMessage() {
var msg = '{"message":"' + $message.val() + '", "sender":"'
+ $nickName.val() + '", "received":""}';
wsocket.send(msg);
$message.val('').focus();
}
function connectToChatserver() {
room = $('#chatroom option:selected').val();
wsocket = new WebSocket(serviceLocation + room);
wsocket.onmessage = onMessageReceived;
}
function leaveRoom() {
wsocket.close();
$chatWindow.empty();
$('.chat-wrapper').hide();
$('.chat-signin').show();
$nickName.focus();
}
$(document).ready(function() {
$nickName = $('#nickname');
$message = $('#message');
$chatWindow = $('#response');
$('.chat-wrapper').hide();
$nickName.focus();
$('#enterRoom').click(function(evt) {
evt.preventDefault();
connectToChatserver();
$('.chat-wrapper h2').text('Chat # '+$nickName.val() + "@" + room);
$('.chat-signin').hide();
$('.chat-wrapper').show();
$message.focus();
});
$('#do-chat').submit(function(evt) {
evt.preventDefault();
sendMessage()
});
$('#leave-room').click(function(){
leaveRoom();
});
});
</script>
The html (excerpt):
<div class="container chat-signin">
<form class="form-signin">
<h2 class="form-signin-heading">Chat sign in</h2>
<label for="nickname">Nickname</label> <input type="text"
class="input-block-level" placeholder="Nickname" id="nickname">
<div class="btn-group">
<label for="chatroom">Chatroom</label> <select size="1"
id="chatroom">
<option>arduino</option>
<option>java</option>
<option>groovy</option>
<option>scala</option>
</select>
</div>
<button class="btn btn-large btn-primary" type="submit"
id="enterRoom">Sign in</button>
</form>
</div>
<div class="container chat-wrapper">
<form id="do-chat">
<h2 class="alert alert-success"></h2>
<table id="response" class="table table-bordered"></table>
<fieldset>
<legend>Enter your message..</legend>
<div class="controls">
<input type="text" class="input-block-level" placeholder="Your message..." id="message" style="height:60px"/>
<input type="submit" class="btn btn-large btn-block btn-primary"
value="Send message" />
<button class="btn btn-large btn-block" type="button" id="leave-room">Leave
room</button>
</div>
</fieldset>
</form>
</div>
Running the Application
Now it’s time to start up the application – this is done easy and very fast using maven and the following command:
mvn package vertx:runMod
Now you should be able to see a similar output and to play around with the chat using two browser instances – or alternatively please feel free to have a look at the screencast.
Screencast
This is the final running application emulating a chat using two different browsers as screencast on YouTube.
Creating a Fat Jar
If you’d like to create one single jar file with all dependencies and stuff packaged to run the full application, building a so called fat jar is your solution.
First of all you need to download the vertx binaries from the website – afterwards you’re able to build and run your fat jar like this:
$ mvn package
$ cd target/
$ vertx fatjar com.hascode.tutorial~vertx-websocket-chat~1.0.0
Attempting to make a fat jar for module com.hascode.tutorial~vertx-websocket-chat~1.0.0
Succeeded in making fat jar
$ java -jar vertx-websocket-chat-1.0.0-fat.jar
Jan 23, 2014 9:04:25 PM org.vertx.java.core.logging.impl.JULLogDelegate info
INFO: Succeeded in deploying module
Alternatively if you’re using Gradle you might use the gradle target and simply run
gradle fatjar
Client Implementation in Java
Please feel free to have a look at my article “Creating different Websocket Chat Clients in Java” for a client implementation in Java.
Circuit Breakers and Vert.x
Vert.x offers a circuit-breaker implementation and also integrates well with Hystrix, if interested, please feel free to read my tutorial: “/resilient-architecture-circuit-breakers-for-java-hystrix-vert-x-javaslang-and-failsafe-examples/[Resilient Architecture in Practice – Circuit Breakers for Java: Failsafe, Javaslang, Hystrix and Vert.x]”
Tutorial Sources
Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:
git clone https://github.com/hascode/vertx-websocket-chat.git
Websocket Chat Articles
I have written other tutorials about websocket server and client implementations, please feel to read further:
Article Updates
-
2018-06-01: Embedded YouTube video removed (GDPR/DSGVO).
-
2017-02-14: Link to circuit-breaker article added.
-
2016-10-29: Link to Go websocket chat implementation added.
-
2014-11-09: Article about websocket client implementation in Java linked.