GrabDuck

Creating a Websocket Chat Application with Vert.x and Java

:

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.

Vert.x Websocket Chat

Vert.x Websocket Chat

 

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, Javascripts 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

New Vert.x Project using a Maven Archetype

New Vert.x Project using a Maven Archetype

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": "http://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:

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.

Vert.x Websocket Chat

Vert.x Websocket Chat

Screencast

This is the final running application emulating a chat using two different browsers.

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

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 in Practice – Circuit Breakers for Java: Failsafe, Javaslang, Hystrix and Vert.x

Tutorial Sources

Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/vertx-websocket-chat.git

Resources

Websocket Chat Articles

I have written other tutorials about websocket server and client implementations, please feel to read further:

Article Updates

  • 2014-11-09: Article about websocket client implementation in Java linked.
  • 2016-10-29: Link to Go websocket chat implementation added.
  • 2017-02-14: Link to circuit-breaker article added.

Tags: , , , , , , ,