Build websocket app with Quarkus framework
What is Quarkus?
- Quarkus is kubernetes native java famework. This framework helps optimizing java for containers.
- Quarkus supports many framework like Spring Boot, RestEasy, etc.
- The project structure can be created using maven plugin, CLI or code.quarkus.io. In this blog I have used code.quarkus.io website.
- The documentation explains the structure of each example program.
Steps to create websocket project using the Quarkus
.
- Select websocket platform from the
https://code.quarkus.io
. This is similar to start.spring.io - Download the project.
- Download and install Quarkus CLI. I used Chocolatey package manager
choco install quarkus
- Update the
StartWebSocket.java
with the below content. - Update the
index.html
with the below content - To run the project use
quarkus dev
, then connect the deployed application by connecting from browser usinghttp://localhost:8080/
To build the jar, we need to use the Quarkus CLI quarkus build --native
.
- StartWebSocket.java
package org.acme;
import javax.enterprise.context.ApplicationScoped;
import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
//import static java.util.Objects.requireNonNull;
@ServerEndpoint("/start-websocket/{username}")
@ApplicationScoped
public class StartWebSocket {
Map<String, Session> sessions = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam("username") String username) {
sessions.put(username, session);
}
@OnClose
public void onClose(Session session, @PathParam("username") String username) {
sessions.remove(username);
broadcast("User " + username + " left");
}
@OnError
public void onError(Session session, @PathParam("username") String username, Throwable throwable) {
sessions.remove(username);
broadcast("User " + username + " left on error: " + throwable);
}
@OnMessage
public void onMessage(String message, @PathParam("username") String username) {
if (message.equalsIgnoreCase("_ready_")) {
broadcast("User " + username + " joined");
} else {
broadcast(">> " + username + ": " + message);
}
}
private void broadcast(String message) {
sessions.values().forEach(s -> {
s.getAsyncRemote().sendObject(message, result -> {
if (result.getException() != null) {
System.out.println("Unable to send message: " + result.getException());
}
});
});
}
}
- index.html should be placed under the
resources\META-INF\resources
. Uses the jquery library.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Quarkus Chat!</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly.min.css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly-additions.min.css">
<style>
#chat {
resize: none;
overflow: hidden;
min-height: 300px;
max-height: 300px;
}
</style>
</head>
<body>
<nav class="navbar navbar-default navbar-pf" role="navigation">
<div class="navbar-header">
<a class="navbar-brand" href="/">
<p><strong>>> Quarkus Chat!</strong></p>
</a>
</div>
</nav>
<div class="container">
<br/>
<div class="row">
<input id="name" class="col-md-4" type="text" placeholder="your name">
<button id="connect" class="col-md-1 btn btn-primary" type="button">connect</button>
<br/>
<br/>
</div>
<div class="row">
<textarea class="col-md-8" id="chat"></textarea>
</div>
<div class="row">
<input class="col-md-6" id="msg" type="text" placeholder="enter your message">
<button class="col-md-1 btn btn-primary" id="send" type="button" disabled>send</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/js/patternfly.min.js"></script>
<script type="text/javascript">
var connected = false;
var socket;
$( document ).ready(function() {
$("#connect").click(connect);
$("#send").click(sendMessage);
$("#name").keypress(function(event){
if(event.keyCode == 13 || event.which == 13) {
connect();
}
});
$("#msg").keypress(function(event) {
if(event.keyCode == 13 || event.which == 13) {
sendMessage();
}
});
$("#chat").change(function() {
scrollToBottom();
});
$("#name").focus();
});
var connect = function() {
if (! connected) {
var name = $("#name").val();
console.log("Val: " + name);
socket = new WebSocket("ws://" + location.host + "/start-websocket/" + name);
socket.onopen = function() {
connected = true;
console.log("Connected to the web socket");
$("#send").attr("disabled", false);
$("#connect").attr("disabled", true);
$("#name").attr("disabled", true);
$("#msg").focus();
};
socket.onmessage =function(m) {
console.log("Got message: " + m.data);
$("#chat").append(m.data + "\n");
scrollToBottom();
};
}
};
var sendMessage = function() {
if (connected) {
var value = $("#msg").val();
console.log("Sending " + value);
socket.send(value);
$("#msg").val("");
}
};
var scrollToBottom = function () {
$('#chat').scrollTop($('#chat')[0].scrollHeight);
};
</script>
</body>
</html>
Information about the docker folder
- This folder has different Dockerfile, which are optimized to support container. Based on requirement we can use the specific Dockerfile for image creation.
- The Dockerfile generated by the framework also provides steps to create the image using it.
- For example, to create image we need to use
./mvnw package -Pnative
command.
- For example, to create image we need to use
- The Dockerfile generated by the framework also provides steps to create the image using it.
- Below is
Dockerfile.native
file created by Quarkus - which uses RedHat universal base image (ubi-minimal).
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
- Dockerfile.native-micro file uses RedHat Quarkus custom image as base
FROM quay.io/quarkus/quarkus-micro-image:1.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
Output on how to connect to the WebSocket connection
- Connect using the browser
Using the command quarkus dev
Opened two browser and add the user01 and user02
After creating the user, then sending message will be broadcast when message received.
Accessing websocket from browser dev console
- Using the browser Dev console we can use below command in the Console
- Open the browser, type the
about:blank
in the address bar, and then use the Dev Console
- Open the browser, type the
let ws = new WebSocket("ws://localhost:8080/start-websocket/user03
ws.send("this message from user03 browser dev console")