Commit 7428c7a6 authored by Mohamad Bashar Desoki's avatar Mohamad Bashar Desoki

restructure and add docker compose file

parent 36389172
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
\ No newline at end of file
<component name="ArtifactManager">
<artifact type="jar" name="simpleWebApp:jar">
<output-path>$PROJECT_DIR$/out/artifacts/simpleWebApp_jar</output-path>
<root id="archive" name="simpleWebApp.jar">
<element id="module-output" name="simpleWebApp" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jsoup/jsoup/1.12.1/jsoup-1.12.1.jar" path-in-jar="/" />
</root>
</artifact>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ArtifactsWorkspaceSettings">
<artifacts-to-build>
<artifact name="simpleWebApp:jar" />
</artifacts-to-build>
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="ad904d3c-917a-41ec-8997-0c459fd2925c" name="Changes" comment="add haproxy cfg1">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Class" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 1
}</component>
<component name="ProjectId" id="2tREiXPX513LksO5LzaY0KttqM7" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.ShowReadmeOnStart": "true",
"SHARE_PROJECT_CONFIGURATION_FILES": "true",
"git-widget-placeholder": "master",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "D:/HIAST Library/Teaching/ADS/2025/HAProxy/New folder/simple-web-app/src/haproxy",
"project.structure.last.edited": "Artifacts",
"project.structure.proportion": "0.15",
"project.structure.side.proportion": "0.2850575"
}
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="D:\HIAST Library\Teaching\ADS\2025\HAProxy\New folder\simple-web-app\src\haproxy" />
</key>
</component>
<component name="RunManager">
<configuration default="true" type="JetRunConfigurationType">
<module name="simple-web-app" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
<module name="simple-web-app" />
<option name="filePath" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="ad904d3c-917a-41ec-8997-0c459fd2925c" name="Changes" comment="" />
<created>1740306614046</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1740306614046</updated>
</task>
<task id="LOCAL-00001" summary="simple web app">
<option name="closed" value="true" />
<created>1740307109999</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1740307109999</updated>
</task>
<task id="LOCAL-00002" summary="add haproxy cfg">
<option name="closed" value="true" />
<created>1740310829735</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1740310829735</updated>
</task>
<task id="LOCAL-00003" summary="add haproxy cfg1">
<option name="closed" value="true" />
<created>1740310859230</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1740310859230</updated>
</task>
<option name="localTasksCounter" value="4" />
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="simple web app" />
<MESSAGE value="add haproxy cfg" />
<MESSAGE value="add haproxy cfg1" />
<option name="LAST_COMMIT_MESSAGE" value="add haproxy cfg1" />
</component>
</project>
\ No newline at end of file
# Simple Web Server for HAProxy Lab
This project provides a basic web server implementation written in Java. It is designed to be used as a practical example for lab exercises involving HAPROXY, a popular open-source load balancer and proxy server.
# Multi-Service Web Application with HAProxy
## Table of Contents
1. [Introduction](#introduction)
2. [Features](#features)
3. [Getting Started](#getting-started)
* [Running the Web Server](#running-the-web-server)
4. [HAProxy Configuration](#haproxy-configuration)
5. [Usage Examples with HAProxy](#usage-examples-with-haproxy)
This project sets up a multi-service web application environment using Docker Compose. It includes three instances of a web application (app1, app2, app3) and an HAProxy load balancer to distribute traffic among them.
## Project Structure
## Introduction
```
.
├── docker-compose.yml
├── haproxy
│ ├── Dockerfile
│ └── haproxy.cfg
└── webapp
├── Dockerfile
└── ... (other web application files)
```
The Simple Web Server is intended to be deployed as part of a HAProxy proxy lab setup. This server can serve a simple HTML page and respond to status check requests, making it an excellent candidate for load balancing experiments using HAProxy.
## Services
## Features
- **app1**: First instance of the web application, configured to listen on port 9001.
- **app2**: Second instance of the web application, configured to listen on port 9002.
- **app3**: Third instance of the web application, configured to listen on port 9003.
- **haproxy**: HAProxy load balancer that distributes incoming traffic to app1, app2, and app3.
- Serves a static HTML page.
- Allows configuration of the server name via command-line arguments.
- Responsive to health checks for load balancing applications (using `/status` endpoint).
## Prerequisites
Before running this project, ensure you have Docker and Docker Compose installed on your machine. You can download them from the [official Docker website](https://www.docker.com/get-started).
## Getting Started
## Building and Running the Services
### Running the Web Server
1. **Navigate to the project directory**:
```sh
cd /path/to/your/project
```
To run a server instance, you need to specify `PORT_NUMBER` and `SERVER_NAME`:
2. **Build the Docker images**:
```sh
docker-compose build
```
```sh
java -jar SimpleWebServer.jar [PORT_NUMBER] [SERVER_NAME]
```
3. **Start the services**:
```sh
docker-compose up -d
```
Or build and run using Maven:
4. **Verify the services are running**:
```sh
docker-compose ps
```
```sh
mvn exec:java -Dexec.mainClass=org.ds.Main -Dexec.args="[PORT_NUMBER] [SERVER_NAME]"
```
## Configuring HAProxy
Replace `[PORT_NUMBER]` with an actual port number (e.g., `8080`) and `[SERVER_NAME]` with your desired server name (e.g., `Server1`).
The HAProxy configuration file (`haproxy.cfg`) is located in the `haproxy` directory. This file defines how traffic is distributed among the web application instances. You can modify this file to change the load balancing behavior or add more backend servers.
## HAProxy Configuration
Example configuration snippet:
```cfg
global
maxconn 500
Here is a sample HAPROXY configuration for load balancing across instances of the Simple Web Server:
defaults
mode http
timeout connect 10s
timeout client 50s
timeout server 50s
```conf
frontend http_front
frontend http-in
bind *:80
default_backend servers
default_backend application_nodes
backend servers
backend application_nodes
balance roundrobin
server srv1 127.0.0.1:8081 check
server srv2 127.0.0.1:8082 check
option httpchk GET /status
http-check expect string "Server is alive"
server server01 app1:9001 check inter 1s
server server02 app2:9002 check inter 2s
server server03 app3:9003 check inter 2s
listen stats
bind *:83
stats enable
stats uri /
```
In this example:
- Instances of the Simple Web Server are run on `127.0.0.1` with ports `8081` and `8082`.
- The HAPROXY frontend listens on port 80 for incoming HTTP requests.
- Requests are distributed to the instances using a round-robin algorithm.
## Accessing the Services
## Usage Examples with HAProxy
- **Web Application**: You can access the web application directly by navigating to `http://localhost:9001`, `http://localhost:9002`, or `http://localhost:9003`.
- **HAProxy**: The HAProxy load balancer will be accessible at `http://localhost`.
For your lab setup:
1. Start two Simple Web Server instances:
## Stopping the Services
To stop the services, run:
```sh
java -jar SimpleWebServer.jar 8081 Server1 &
java -jar SimpleWebServer.jar 8082 Server2 &
docker-compose down
```
2. Configure and start HAPROXY using the sample configuration provided above.
3. Access any of the server instances through your browser at `http://localhost`. You should see requests distributed between `Server1` and `Server2`.
FROM maven:3.6.1-jdk-11 AS MAVEN_TOOL_CHAIN_CONTAINER
RUN mkdir src
COPY src /tmp/src
COPY ./pom.xml /tmp/
WORKDIR /tmp/
RUN mvn package
RUN ls -la /tmp
FROM openjdk:11
COPY --from=MAVEN_TOOL_CHAIN_CONTAINER /tmp/target/webapp-1.0-SNAPSHOT-jar-with-dependencies.jar /tmp/
WORKDIR /tmp/
ENTRYPOINT ["java","-jar", "webapp-1.0-SNAPSHOT-jar-with-dependencies.jar"]
CMD ["80", "Server Name"]
\ No newline at end of file
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.ds</groupId>
<artifactId>simpleWebApp</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.12.1</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package org.ds;
public class Main {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("java -jar (jar name) PORT_NUMBER SERVER_NAME");
}
int currentServerPort = Integer.parseInt(args[0]);
String serverName = args[1];
WebServer webServer = new WebServer(currentServerPort, serverName);
webServer.startServer();
}
}
\ No newline at end of file
package org.ds;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
public class WebServer {
private static final String STATUS_ENDPOINT = "/status";
private static final String HOME_PAGE_ENDPOINT = "/";
private static final String HTML_PAGE = "/index.html";
private final int port;
private HttpServer server;
private final String serverName;
public WebServer(int port, String serverName) {
this.port = port;
this.serverName = serverName;
}
public void startServer() {
try {
this.server = HttpServer.create(new InetSocketAddress(port), 0);
} catch (IOException e) {
e.printStackTrace();
return;
}
server.createContext(STATUS_ENDPOINT, this::handleStatusCheckRequest);
server.createContext(HOME_PAGE_ENDPOINT, this::handleHomePageRequest);
server.setExecutor(Executors.newFixedThreadPool(8));
System.out.println(String.format("Started server %s on port %d ", serverName, port));
server.start();
}
private void handleHomePageRequest(HttpExchange exchange) throws IOException {
if (!exchange.getRequestMethod().equalsIgnoreCase("get")) {
exchange.close();
return;
}
System.out.println(String.format("%s received a request", this.serverName));
exchange.getResponseHeaders().add("Content-Type", "text/html");
exchange.getResponseHeaders().add("Cache-Control", "no-cache");
byte[] response = loadHtml(HTML_PAGE);
sendResponse(response, exchange);
}
/**
* Loads the HTML page to be fetched to the web browser
*
* @param htmlFilePath - The relative path to the html file
* @throws IOException
*/
private byte[] loadHtml(String htmlFilePath) throws IOException {
InputStream htmlInputStream = getClass().getResourceAsStream(htmlFilePath);
if (htmlInputStream == null) {
return new byte[]{};
}
Document document = Jsoup.parse(htmlInputStream, "UTF-8", "");
String modifiedHtml = modifyHtmlDocument(document);
return modifiedHtml.getBytes();
}
/**
* Fills the server's name and local time in theHTML document
*
* @param document - original HTML document
*/
private String modifyHtmlDocument(Document document) {
Element serverNameElement = document.selectFirst("#server_name");
serverNameElement.appendText(serverName);
return document.toString();
}
private void handleStatusCheckRequest(HttpExchange exchange) throws IOException {
if (!exchange.getRequestMethod().equalsIgnoreCase("get")) {
exchange.close();
return;
}
System.out.println("Received a health check");
String responseMessage = "Server is alive\n";
sendResponse(responseMessage.getBytes(), exchange);
}
private void sendResponse(byte[] responseBytes, HttpExchange exchange) throws IOException {
exchange.sendResponseHeaders(200, responseBytes.length);
OutputStream outputStream = exchange.getResponseBody();
outputStream.write(responseBytes);
outputStream.flush();
outputStream.close();
}
}
Manifest-Version: 1.0
Main-Class: org.ds.Main
<!DOCTYPE html>
<html>
<head>
<title>Simple Web APP</title>
<meta http-equiv="cache-control" content="no-cache"/>
</head>
<body style="background: #e6f3ff;">
<h1 style="color:blue; text-align: center; font-style: bold" id="server_name_title">Welcome to </h1>
<h1 style="color:#00b4ff; text-align: center; font-style: italic" id="server_name"></h1>
</body>
</html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment