/sirius-chat-sample

Provides a set of tutorials and sample code which uses the SIRIUS framework to build a basic chat server.

Primary LanguageJava

SIRIUS Chat Sample

This is an illustration project used to showcase how to setup and use the SIRIUS libraries.

The task at hands is creating a small web server which provides a chat room for clients which are connected via web sockets. The task itself is split up into challenges - some build up onto each other, others are more like side quests which provide some functionality but can also be skipped.

Also feel free to have a look around, place breakpoints, figure out how the surrounding framework works and most importantly HAVE FUN 😄

SIRIUS

SIRIUS is split up into distinct modules to provide a broad range of use-cases. The modules are:

Pre-Requisites

You should have the following Software stack installed in order to execute these challenges:

Once everything is installed, launch IntelliJ and choose to create a New Project from Version Control, and give the official Github URL for this project:

https://github.com/scireum/sirius-chat-sample.git

📃 A Popup will show up suggesting to Import as a Maven Project. Click Import and/or enable the Auto-Import option.

Virtual Machine

As an alternative to installing the software stack above, you can also try this exercise using a ready-made Virtual Machine (aprox. 4.2GB) containing everything pre-installed under an Ubuntu Linux distribution. Download the Virtual Machine appliance from here and run it with VirtualBox. MD5 and SHA checksums also available.

Introduction

To save some time, the project is already setup and also contains some scaffolding for the challenges. This project can be built using Apache Maven and should be developed using Jetbrains IntelliJ IDEA.

To understand the inner workings first have a look at the pom.xml which tells maven how to build this project. Then consult the src/main/resources/application.conf and develop.conf to understand the configuration of the application itself as well as the developer config which is used when starting the application locally.

(The system configuration is accessible via either Sirius.getSettings() or by using the ConfigValue annotation. An example is the WebServer provided by sirius-web, which uses the configuration to determine which port to open etc.)

Speaking of which, SIRIUS applications can be started using a Java Application Run configuration as follows:

  • Main Class: sirius.kernel.Setup
  • VM Options:-Ddebug=true -Dide=true

📃 one more very important file (src/main/resources/component.marker). Although this file is empty, is it essential, as it is used by the Classpath scanner of the Dependency Injector to discover all classpath roots which participate in the SIRIUS system.

Challenges

Ok, we're almost good to go. Before diving into the first challenge, start a Debugger using the Setup run configuration to make sure that the base system is operational. This will start a Docker container for Redis and one for Elasticsearch which are required for later challenges (the stack is configured in docker-compose.yml). Once a message like System is UP and RUNNING appears in the console, you should be able to view the chat UI: http://localhost:9000

📃 you can also have a look at the system state via http://localhost:9000/system/state and even monitor some details via http://localhost:9000/system/console (e.g. by executing the http command).

💡 Each challenge has a unique ID (such as CHALLENGE-1). You can search in all files ("Find in Path") using this ID to spot all relevant code positions. You will also find the appropriate Class.solution file which will assist in case you're in trouble.

You can also checkout the solution branch and jump directly to the final working solution.

In the diagrams below you will find notes like this diagram giving you a hint to where you should white some code. 😉

Hello Myself (CHALLENGE-1)

The first step is to make the server respond to incoming chat messages. These messages will simply be sent back to the client. But first, let's find out where the client comes from. Head over to the ChatClientController and read the JavaDoc - then come back here.

Now that we know how the client is delivered, we can start the challenge. The client connects via a "web socket" which is more or less a persistent HTTP connection which exchanges messages (called) frames. (You can review the client code required to open a web socket in JavaScript - but that's not required for our challenge - client.js). For each web socket being connected, the WebSocketDispatcher will detect this and initialize a ChatSession. Head over to the session class to read the provided docs and finally to start some coding! 💻

diagram

Once the challenge is completed, you should be able to chat with yourself. 👏

Hello Everyone (CHALLENGE-2)

For the second challenge, head to ChatSessionRegistry and implement the missing parts. Then switch to ChatSession and ChatUplink to call the appropriate methods.

diagram

Once this is all done, restart the debugger and open two or more tabs (or let others in the network connect to your computer). You now have ONE server which can handle multiple users! 👏

Hello Neighbourhood (CHALLENGE-3)

The third challenge starts by extending ChatUplink with ChatClusterUplink and broadcasting the message to subscribers instead of sending it only to the local sessions registered.

The ChatClusterUplink is already prepared to publish the message to Redis, but you will have to complete the code in order to forward to message the registered sessions.

The fun starts when you and other participants share the same Redis installation, which makes all computers running the application as part of the same cluster.

We've configured Redis to expose port 16379 by default. Simply select one member to share his Redis container and update the redis.pools.system.host in the instance.conf.

💡 ifconfig -a on Mac/Unix or ipconfig /ALL on Windows are good commands to determine your IP address (and as always, the good ol' Google for other ways)

diagram

Now restart the application and MULTIPLE servers (with their own users) can now participate in the same chat! 👏

Ahoi World (CHALLENGE-4)

Now it is time to create a Docker image with our application. In real life, apps do not run under IDEs 😛

Execute the Maven package Lifecycle, switch to the IntelliJ Terminal and execute the following command from the project's root folder: docker build --rm -t sirius-chat .

This command uses the provided Dockerfile containing instructions to pack the compiled application inside an image which can be executed stand-alone.

To launch your fresh created container, you can use this command: switch localhost to your machine's IP address or another shared host if needed docker run -d --name sirius-chat-container --rm -p 80:80 -e REDIS_HOST=CHANGE_ME -e REDIS_PORT=16379 -e ES_HOSTS=localhost:19200 sirius-chat

After started, the application should be available at http://localhost. 👏

To stop it, run docker stop sirius-chat-container

Side-Quest: HA-Setup (SIDE-QUEST-1)

This challenge gives a small glance at how a high availability system looks like. The goal is to start 2 or more instances of our chat app under a docker container, with a traefik container performing the load-balancing between them.

Read the Quick Start Guide at traefik.io and fill up the docker-compose.yml file in in order to setup a load-balancer for our chat.

To start the set, head to the ha folder and issue: docker-compose up -d To start 2 instances of the sirius-chat you can use: docker-compose up -d --scale sirius-chat-sample=2

diagram

Now when browsing the application via http://localhost, you should be able to stop one of the containers on-the-fly and still be able to chat as you will be automatically redirected to another running instance! ✌️

Side-Quest: Chat-Bots (SIDE-QUEST-2)

To add chat bots, have a look at the ChatBot interface and the two examples (HelpBot, CalcBot).

To invoke the chat bots, jump back to the ChatSession and invoke the bots in "handleChatMessage" instead of directly forwarding the message (there is a descriptive comment there already).

Once that works, make sure to have some fun by either modifying the existing bots or by providing your own. Maybe add a command to report how many people are in the chat, or when user X wrote the last message (both would require to extend ChatSessionRegistry a bit). ✌️

Side-Quest: Rate-Limiting (SIDE-QUEST-3)

Now that our precious chat server is running, we need to protect it from the cruel outside world. sirius-biz provides a simple but effective firewall which relies on redis to ensure some rate limiting (e.g. user X doesn't perform action Y more often than 1000 times per 1 minute). The class is named Isenguard and an example of using it can be found in the ChatClientController.

Two places are naturally interesting to rate limiting ChatSession.handleChatMessage to handle the number of incoming chat message and ChatSession.onWebsocketOpened to handle the number of connection attempts. Obtain a reference to Isenguard using a static field + @Part and enforce a proper rate limit. Play around with the settings in application.conf (requires a restart) to ensure that they work.

Note that Isenguard provides a bunch of additional methods. You can use Isenguard.isRateLimitReached and its callbacks and notify everyone that someone reached its limit. Or you can provide a custom ChatBot which reveals the current limit status for the caller by using Isenguard.getRateLimitInfo.

Side-Quest: Search (SIDE-QUEST-4)

Searching and sorting is all that computers do 😄 Therefore we also want to provide a way to search in the history of our chat room. Luckily, sirius-biz and sirius-db provide connectivity to all sorts of databases (MariaDB, MongoDB, Elasticsearch). The latter is interesting for us, as it provides a complete fulltext search engine out of the box. Have a look at SearchableChatMessage which is the entity class (a Java class which defines the fields to be stored in Elastic). The SearchMessagesController is also readily available and provides a tiny UI to search in elastic. Also have a look (and later play around) with the template which is used to actually render the search output search.html.pasta.

To ingest data into Elastic, jump to the ChatUplink and implement the TODOs in distributeMessage. After restarting the debugger, new chat message should be visible in the search UI. 👏

💡 Just like with Redis: if you share the same Elasticsearch, the messages from everyone will be visible and searcheable to every server and its users.

Side-Quest: Who am I? (SIDE-QUEST-5)

First we want to enhance the client.js to retrieve an additional information from the user's browser (e.g. the user agent header) and send it over the web socket. We also want to store this info into our SearchableChatMessage so we have to include a new field and ultimately display it by by editing the search.html.pasta. You can look at the name field and how it is handled if you need any help.

Side-Quest: JavaScript Debugging (SIDE-QUEST-6)

The info button located in the top right in the chat window is not working! Can you use the Developer Tools provided by your browser to find and fix the error in the client.js file?

Side-Quest: Java Debugging (SIDE-QUEST-7)

If you navigate to the menu point SIDE-QUEST-7, you will get an error. Can you fix it so you get a list of 100 Colors when calling this route? You can start by searching the system log for the exception that occured.


Made with all the ❤️ in the world by scireum in Remshalden.