Get started with Vert.x
This tutorial helps you integrate Vert.x with Hazelcast and use Hazelcast for distributed session management and other distributed data structures.
In this tutorial, you will
-
start with a simple Vert.x Hello World application
-
add vertx-hazelcast module and enable distributed session management
-
use
io.vertx.core.shareddata.Counter
data structure to implement a unique id generator
Prerequisites
-
Java 17 or newer
-
Maven 3.9+
-
httpie client
Create a new project
-
Go to start.vertx.io, change the artifact id to
messages
, the version to 5.0.0, and generate a new project. -
Extract the project and build it using:
$ mvn clean package
and start the application using:
java -jar target/messages-1.0.0-SNAPSHOT-fat.jar
You should see output similar to the following:
HTTP server started on port 8888
Aug 29, 2024 2:22:38 PM io.vertx.launcher.application.VertxApplication
INFO: Succeeded in deploying verticle
Storing Data in Session
Go to the MainVerticle.java
file and replace the contents of the start method with the following:
This tutorial uses 2-space indentation, which is customary for Vertx projects due to the high number of nested callbacks. |
public void start() {
// Create a Router
Router router = router(vertx);
// Create local SessionStore
SessionStore store = LocalSessionStore.create(vertx);
// Use the SessionStore to handle all requests
router.route()
.handler(SessionHandler.create(store));
router.route(HttpMethod.PUT, "/").handler(context -> {
context.request().bodyHandler(body -> {
List<String> messages = getMessagesFromSession(context);
JsonObject json = body.toJsonObject();
String message = json.getString("message");
messages.add(message);
putMessagesToSession(context, messages);
context.json(
new JsonObject()
.put("messages", messages)
);
});
});
// Create the HTTP server
vertx.createHttpServer()
// Handle every request using the router
.requestHandler(router)
// Start listening
.listen(8888)
// Print the port
.onSuccess(server ->
System.out.println(
"HTTP server started on port " + server.actualPort()
)
);
}
private static List<String> getMessagesFromSession(RoutingContext context) {
String messages = context.session().get("messages");
if (messages == null) {
return new ArrayList<>();
} else {
return new ArrayList<>(Arrays.asList(messages.split(",")));
}
}
private void putMessagesToSession(RoutingContext context, List<String> messages) {
context.session().put("messages", String.join(",", messages));
}
$ http put localhost:8888 message=Hello\ World!
HTTP/1.1 200 OK
content-length: 29
content-type: application/json
set-cookie: vertx-web.session=ed22f77473a7f613c9305431a62832a6; Path=/
{
"messages": [
"Hello World!"
]
}
Execute another request with the cookie:
$ http put localhost:8888 'Cookie:vertx-web.session=ed22f77473a7f613c9305431a62832a6' message=Hello\ World\ 2!
HTTP/1.1 200 OK
content-length: 46
content-type: application/json
{
"messages": [
"Hello World!",
"Hello World 2!"
]
}
Distributed Sessions
Let’s modify the code, so we can start multiple instances easily - the application will start on the defined port, and when the port is not available it will search for another port:
Add the following method to the MainVerticle.java
class:
private int findFreePort(int from) {
for (int port = from; port < from + 100; port++) {
try {
new ServerSocket(port).close();
return port;
} catch (IOException e) {
// port not available, try next
}
}
throw new RuntimeException("Could not find an available port");
}
and use it in the start
method:
...
int port = findFreePort(8888);
// Create the HTTP server
vertx.createHttpServer()
// Handle every request using the router
.requestHandler(router)
// Start listening
.listen(port)
...
Now, we can start two instances:
$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar
HTTP server started on port 8888
Aug 30, 2024 9:09:44 AM io.vertx.launcher.application.VertxApplication
INFO: Succeeded in deploying verticle
...
$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar
HTTP server started on port 8889
Aug 30, 2024 9:09:47 AM io.vertx.launcher.application.VertxApplication
INFO: Succeeded in deploying verticle
and we can see the session is not shared between the instances. Here is the request to the first instance:
$ http PUT localhost:8888 message="Hello world"
HTTP/1.1 200 OK
content-length: 28
content-type: application/json
set-cookie: vertx-web.session=00f219c166ca50727d23eaaf9fe54229; Path=/
{
"messages": [
"Hello world"
]
}
and here is the request to the 2nd instance. Notice the different port and that we use the cookie we received, but the data does not contain the previous message.
$ http PUT localhost:8889 message="Hello world 2" 'Cookie: vertx-web.session=00f219c166ca50727d23eaaf9fe54229'
HTTP/1.1 200 OK
content-length: 30
content-type: application/json
set-cookie: vertx-web.session=a1486c5ed6416972fdc356e4d91d2397; Path=/
{
"messages": [
"Hello world 2"
]
}
We will fix that by using a Hazelcast Cluster Manager. This resides in the io.vertx:vertx-hazelcast
module, which is maintained by the Vert.x team with contributions from Hazelcast and is based on Hazelcast Community Edition.
Change the following part of the start
method:
// Create local SessionStore
SessionStore store = LocalSessionStore.create(vertx);
to the following:
// Create clustered SessionStore
SessionStore store = ClusteredSessionStore.create(vertx);
and from now on we will start the application with -server
parameter, which tells Vert.x to look for a cluster manager implementation.
We also need to provide a Hazelcast configuration file, and create a file cluster.xml in the src/main/resources
directory:
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config
https://www.hazelcast.com/schema/config/hazelcast-config-5.5.xsd">
<network>
<join>
<multicast enabled="true"/>
</join>
</network>
<multimap name="__vertx.subs">
<backup-count>1</backup-count>
<value-collection-type>SET</value-collection-type>
</multimap>
<map name="__vertx.haInfo">
<backup-count>1</backup-count>
</map>
<map name="__vertx.nodeInfo">
<backup-count>1</backup-count>
</map>
Now rebuild the project and start the application. You will see more verbose output as Hazelcast prints its own startup logs:
$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar -cluster
...
HTTP server started on port 8888
...
Members {size:2, ver:2} [
Member [192.168.0.10]:5701 - e29f0362-f9a9-4708-b6e5-1a6067b5aa39 this
Member [192.168.0.10]:5702 - 74014573-a18a-44f2-9ca7-fd90b70dcb43
]
...
and
$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar -cluster
...
HTTP server started on port 8889
...
Members {size:2, ver:2} [
Member [192.168.0.10]:5701 - e29f0362-f9a9-4708-b6e5-1a6067b5aa39
Member [192.168.0.10]:5702 - 74014573-a18a-44f2-9ca7-fd90b70dcb43 this
]
...
Putting two messages into different instances while using the same cookie, we see that the session is shared between the instances.
$ http PUT localhost:8888 message="Hello world"
HTTP/1.1 200 OK
content-length: 31
content-type: application/json
set-cookie: vertx-web.session=1ab47cb96731123135f25ec7b67efd64; Path=/
{
"messages": [
"",
"Hello world"
]
}
$ http PUT localhost:8889 message="Hello world 2" 'Cookie: vertx-web.session=674806546c690674962f279670abefcf'
HTTP/1.1 200 OK
content-length: 44
content-type: application/json
{
"messages": [
"Hello world",
"Hello world 2"
]
}
Using Counter
Replace this part of the code at the end of the start()
method:
context.json(
new JsonObject()
.put("messages", messages)
);
with the following:
context.vertx()
.sharedData()
.getCounter("requestId")
.onSuccess(counter -> {
counter.incrementAndGet()
.onSuccess(requestId -> {
context.json(
new JsonObject()
.put("requestId", requestId)
.put("messages", messages)
);
});
});
When you now try the application, you can see the response contains an additional field named requestId
and its value increments for every request.
$ http PUT localhost:8888 message="Hello world"
HTTP/1.1 200 OK
content-length: 42
content-type: application/json
set-cookie: vertx-web.session=d9fb4cada5c0fc625089a38f3de13e3c; Path=/
{
"messages": [
"Hello world"
],
"requestId": 1
}