Categories
Development Java

Quarkus in Codespace

Set up Quarkus in Codespace environment

Setup

Create a repository “workshop”:

Open codespace and setup Linux in Terminal:

sudo apt update && \
sudo apt upgrade -y && \
sudo apt install httpie -y

Setup Quarkus Project

Quarkus Homepage

Getting started → 4. Bootstrapping the project → Maven

copy and change ArtifactId to workshop

cd ..
# pwd -> /workspaces
mvn io.quarkus.platform:quarkus-maven-plugin:3.10.2:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=workshop

We can not just delete the workshop folder, because GIT information are inside this folder.

Workaround:

cd /workspaces/workshop/

mvn io.quarkus.platform:quarkus-maven-plugin:3.10.2:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=workshop

mv workshop/* .
ls -lisah workshop/
# hidden files/directory still there
mv workshop/.* .
ls -lisah workshop/
# now everthing has moved
rmdir workshop

Stage, commit and push to repository:

Open GreetingResource, this will force Visual Code to ask to install Extension Pack for Java:

Go to Explorer → Java Projects → Import Projects

It takes a minute or two to show our workshop project:

Start Quarkus

./mvnw quarkus:dev

In Ports Port 5005 is automatically added, but not Port 8080.

We need to add Port 8080 manually.

Click on the Globus Icon and Quarkus welcome page opens:

Go to Visit the dev UI → Endpoints

Click on /hello:

Test from terminal

Open new Terminal

## "&& echo" for additional linebreak
curl localhost:8080/hello && echo
http localhost:8080/hello

WebSockets with Quarkus

Quarkus Guide for using WebSockets

Setup ChatSocket

First we need to install extensions:

./mvnw quarkus:add-extension -Dextensions='websockets'
./mvnw quarkus:add-extension -Dextensions='websockets-next'

Create simple ChatSocket:

package org.acme;

import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;

@WebSocket(path = "/chatsocket")
public class ChatSocket {
    @OnTextMessage
    public String onMessage(String userMessage){
        return "You said: " + userMessage;
    }
}

Test from terminal

npm install -g wscat
wscat -c ws://localhost:8080/chatsocket

wscat -c ws://localhost:8080/chatsocket
> Hello World!
< You said: Hello World!

Build html client

mkdir -p src/main/resources/META-INF/resources
touch src/main/resources/META-INF/resources/chat.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Chat Example</title>
    <style>
        #chat {
            resize: none;
            overflow: hidden;
            min-width: 70%;
            min-height: 300px;
            max-height: 300px;
            overflow-y: scroll;
        }
        #msg {
            min-width: 40%;
        }
    </style>
</head>
<body>
    <h1>WebSocket Chat Example</h1>
    <p id="message">Connecting...</p>
    <br/>
    <div class="container">
        <br/>
        <div class="row">
            <textarea id="chat"></textarea>
        </div>
        <div class="row">
            <input id="msg" type="text" placeholder="enter your message">
            <button id="send" type="button" disabled>send</button>
        </div>
    
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script>
        var connected = false;
        var socket;

        $( document ).ready(function() {
            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) {
                socket = new WebSocket('wss://' + location.host + '/chatsocket');
                socket.onopen = function(m) {
                    connected = true;
                    console.log("Connected to the web socket");
                    $("#send").attr("disabled", false);
                    $("#connect").attr("disabled", true);
                    $("#name").attr("disabled", true);
                    $("#chat").append("[Chatbot] Howdy, how may I help you? \n");
                    $("#msg").focus();
                    $("#message").text('Connected');
                };
                socket.onmessage = function(m) {
                    console.log("Got message: " + m.data);
                    $("#message").text('Received: ' + m.data);
                    $("#chat").append("[Chatbot] " + m.data + "\n");
                    scrollToBottom();
                };
                socket.onclose = function(event) {
                    console.log("Disconnected");
                    $("#message").text('Disconnected');
                    $("#chat").append("[Chatbot] Disconnected" + "\n");
                    scrollToBottom();
                };
                socket.onerror = function(error) {
                    console.log("Error: " + error.message);
                    $("#message").text('Error: ' + error.message);
                    $("#chat").append("[Chatbot] Error: " + error.message + "\n");
                    scrollToBottom();
                };
            }
        };

        var sendMessage = function() {
            if (connected) {
                var value = $("#msg").val();
                console.log("Sending " + value);
                $("#chat").append("[You] " + value + "\n")
                socket.send(value);
                $("#msg").val("");
            }
        };

        var scrollToBottom = function () {
            $('#chat').scrollTop($('#chat')[0].scrollHeight);
        };

    </script>
</body>
</html>

CONTEXTS AND DEPENDENCY INJECTION Example

Add a ChatService for the 'logic' and inject it into the Chat Socket:

package org.acme;

import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

@ApplicationScoped
public class ChatService {
    protected void startup(@Observes StartupEvent event) { 
        System.out.println("Startuuuuuuuuuup event");
    }

    public String chat(String message) {
        return message + " you said.";
    }
}
package org.acme;

import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
import jakarta.inject.Inject;

@WebSocket(path = "/chatsocket")
public class ChatSocket {
    @Inject
    ChatService chatService;

    @OnTextMessage
    public String onMessage(String userMessage){
        return chatService.chat(userMessage);
    }
}

Build and run native Image

Building a Native Executable - Quarkus

Build:

# Ich muss vermutlich nur den zweiten Befehl ausführen?
# ./mvnw install -Dnative 
./mvnw package -Dnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true

Open another terminal and see how much codespace machine is sweating:

htop

Run:

target/workshop-1.0.0-SNAPSHOT-runner

The project generation has provided a Dockerfile.native-micro in the src/main/docker directory.

# build
docker build -f src/main/docker/Dockerfile.native-micro -t deringo/workshop .

# run
docker run -i --rm -p 8080:8080 deringo/workshop
Categories
Development

WebSockets with Node.js

WebSockets are a protocol that provides full-duplex communication channels over a single, long-lived connection. They are designed for real-time, event-driven web applications and allow for low-latency communication between a client (typically a web browser) and a server. Here are some key points about WebSockets:

  1. Full-Duplex Communication: Unlike HTTP, which is request-response based, WebSockets allow for two-way communication where both client and server can send and receive messages independently of each other.
  2. Persistent Connection: WebSocket connections are persistent, meaning they remain open as long as both the client and server agree to keep the connection alive. This reduces the overhead associated with establishing new connections.
  3. Low Latency: WebSockets are ideal for scenarios requiring real-time updates because they reduce the latency associated with polling or long-polling techniques.
  4. Protocol: WebSockets are established by upgrading an HTTP/HTTPS connection using a WebSocket handshake, switching the protocol from HTTP to WebSocket.
  5. Use Cases: Common use cases include live chat applications, real-time notifications, collaborative editing, online gaming, and any application requiring real-time data updates.

Node.js in Codespace

I want to test WebSockets with Node.js in GitHub Codespace.

Node Version Manager (nvm)

# test installation
command -v nvm
nvm


# check version
nvm ls
       v18.20.1
       v20.12.1
->       system
default -> 20 (-> v20.12.1)
iojs -> N/A (default)
unstable -> N/A (default)
node -> stable (-> v20.12.1) (default)
stable -> 20.12 (-> v20.12.1) (default)
lts/* -> lts/iron (-> v20.12.1)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.24.1 (-> N/A)
lts/erbium -> v12.22.12 (-> N/A)
lts/fermium -> v14.21.3 (-> N/A)
lts/gallium -> v16.20.2 (-> N/A)
lts/hydrogen -> v18.20.1
lts/iron -> v20.12.1


# check node version
node --version
v20.12.1

Node.js

# install LTS
nvm install --lts
Installing latest LTS version.
Now using node v20.14.0 (npm v10.7.0)


# check version
nvm ls


# check node version
node --version
v20.14.0

Some npm commands

# Show installed Nodes
nvm ls
# Show available versions
nvm ls-remote
# Install latest version
nvm install node
# Install LTS version
nvm install --lts
# Install a specific version (list available -> example 16.20.2)
nvm install 16.20.2
# Use a specific version
nvm use 16.20.2
# Show npm version
npm --version

Simple Website with Node.js

To serve an HTML page using Node.js, we can use the built-in http module.

Create an HTML file

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Node.js HTML Server</title>
</head>
<body>
    <h1>Hello, World!</h1>
    <p>This is a simple HTML page served by Node.js.</p>
</body>
</html>

Create a Node.js script to serve the HTML file

const http = require('http');
const fs = require('fs');
const path = require('path');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
    if (req.method === 'GET' && req.url === '/') {
        const filePath = path.join(__dirname, 'index.html');
        fs.readFile(filePath, (err, data) => {
            if (err) {
                res.statusCode = 500;
                res.setHeader('Content-Type', 'text/plain');
                res.end('Internal Server Error');
            } else {
                res.statusCode = 200;
                res.setHeader('Content-Type', 'text/html');
                res.end(data);
            }
        });
    } else {
        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Not Found');
    }
});

server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}/`);
});

Run the Node.js server

node server.js

Test Node.js server in terminal

curl localhost:3000
http localhost:3000

Test Node.js server in browser

WebSocket in Codespace

To create a Node.js server that provides both an HTTP server for serving an HTML page and a WebSocket server for real-time communication, we can use the ws library for WebSockets.

Install the ws library

npm install ws

Create the HTML file

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Example</title>
</head>
<body>
    <h1>WebSocket Example</h1>
    <p id="message">Connecting...</p>
    <script>
        const socket = new WebSocket('ws://localhost:3000');

        socket.onopen = function(event) {
            document.getElementById('message').innerText = 'Connected';
            socket.send('Hello Server');
        };

        socket.onmessage = function(event) {
            document.getElementById('message').innerText = 'Received: ' + event.data;
        };

        socket.onclose = function(event) {
            document.getElementById('message').innerText = 'Disconnected';
        };

        socket.onerror = function(error) {
            document.getElementById('message').innerText = 'Error: ' + error.message;
        };
    </script>
</body>
</html>

Create the Node.js server

const http = require('http');
const fs = require('fs');
const path = require('path');
const WebSocket = require('ws');

const hostname = '127.0.0.1';
const port = 3000;

// Create HTTP server
const server = http.createServer((req, res) => {
    if (req.method === 'GET' && req.url === '/') {
        const filePath = path.join(__dirname, 'index.html');
        fs.readFile(filePath, (err, data) => {
            if (err) {
                res.statusCode = 500;
                res.setHeader('Content-Type', 'text/plain');
                res.end('Internal Server Error');
            } else {
                res.statusCode = 200;
                res.setHeader('Content-Type', 'text/html');
                res.end(data);
            }
        });
    } else {
        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Not Found');
    }
});

// Create WebSocket server
const wss = new WebSocket.Server({ server });

wss.on('connection', ws => {
    console.log('Client connected');

    ws.on('message', message => {
        console.log(`Received: ${message}`);
        ws.send('Hello Client');
    });

    ws.on('close', () => {
        console.log('Client disconnected');
    });
});

server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}/`);
});

Run the Node.js server

node server.js

Test Node.js server in terminal

curl localhost:3000
http localhost:3000

Test Node.js server in browser

Brower Fix

We need to adjust the address of the WebSocket.

When setting up a WebSocket connection from the client-side script within the HTML file, the WebSocket URL must match the address and port where the WebSocket server is running. This URL should include the WebSocket protocol (ws:// or wss:// for secure connections)

Adjustment:

const socket = new WebSocket('wss://symmetrical-disco-g454xqrq9pqvfw6pr-3000.app.github.dev/:3000');

OK, this works, so make it a little more dynamic:

const socket = new WebSocket('wss://' + location.host + '/:3000');

Test WebSocket from terminal

Unfortunately, curl and httpie do not natively support WebSocket protocols. To test WebSocket connections using command-line we can use wscat, which is specifically designed for WebSocket communication.

npm install -g wscat

Using wscat to Test WebSocket Connections:

wscat -c ws://localhost:3000

Connected (press CTRL+C to quit)
> hello
< Hello Client

Build a Chat App

Let's build a "Chat App"

Server

We enhance the server to dynamically answer to a message:

[...]
     ws.on('message', message => {
        console.log(`Received: ${message}`);
        ws.send('Hello ' + message);
    });
[...]

Amazing!

Client

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Chat Example</title>
    <style>
        #chat {
            resize: none;
            overflow: hidden;
            min-width: 70%;
            min-height: 300px;
            max-height: 300px;
            overflow-y: scroll;
        }
        #msg {
            min-width: 40%;
        }
    </style>
</head>
<body>
    <h1>WebSocket Chat Example</h1>
    <p id="message">Connecting...</p>
    <br/>
    <div class="container">
        <br/>
        <div class="row">
            <textarea id="chat"></textarea>
        </div>
        <div class="row">
            <input id="msg" type="text" placeholder="enter your message">
            <button id="send" type="button" disabled>send</button>
        </div>
    
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script>
        var connected = false;
        var socket;

        $( document ).ready(function() {
            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) {
                socket = new WebSocket('wss://' + location.host + '/:3000');
                socket.onopen = function(m) {
                    connected = true;
                    console.log("Connected to the web socket");
                    $("#send").attr("disabled", false);
                    $("#connect").attr("disabled", true);
                    $("#name").attr("disabled", true);
                    $("#chat").append("[Chatbot] Howdy, how may I help you? \n");
                    $("#msg").focus();
                    $("#message").text('Connected');
                };
                socket.onmessage = function(m) {
                    console.log("Got message: " + m.data);
                    $("#message").text('Received: ' + m.data);
                    $("#chat").append("[Chatbot] " + m.data + "\n");
                    scrollToBottom();
                };
                socket.onclose = function(event) {
                    console.log("Disconnected");
                    $("#message").text('Disconnected');
                    $("#chat").append("[Chatbot] Disconnected" + "\n");
                    scrollToBottom();
                };
                socket.onerror = function(error) {
                    console.log("Error: " + error.message);
                    $("#message").text('Error: ' + error.message);
                    $("#chat").append("[Chatbot] Error: " + error.message + "\n");
                    scrollToBottom();
                };
            }
        };

        var sendMessage = function() {
            if (connected) {
                var value = $("#msg").val();
                console.log("Sending " + value);
                $("#chat").append("[You] " + value + "\n")
                socket.send(value);
                $("#msg").val("");
            }
        };

        var scrollToBottom = function () {
            $('#chat').scrollTop($('#chat')[0].scrollHeight);
        };

    </script>
</body>
</html>

Awesome!

Also 'Chatbot' is working from terminal: