From 7345808af9a94eaedebf067b5d286a2b26577d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mats=20R=C3=B6nnqvist?= Date: Fri, 13 Feb 2026 14:57:18 +0100 Subject: [PATCH 1/7] Add Maven Shade Plugin for building a fat JAR; update README with detailed project overview and usage instructions --- pom.xml | 21 ++ src/main/resources/static/README.md | 409 ++++++++++++++++++++++++++-- 2 files changed, 405 insertions(+), 25 deletions(-) diff --git a/pom.xml b/pom.xml index f88056db..db06950e 100644 --- a/pom.xml +++ b/pom.xml @@ -171,6 +171,27 @@ + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + + package + + shade + + + false + + + org.juv25d.App + + + + + + diff --git a/src/main/resources/static/README.md b/src/main/resources/static/README.md index 2e895331..04cc3b80 100644 --- a/src/main/resources/static/README.md +++ b/src/main/resources/static/README.md @@ -1,47 +1,406 @@ -# PLACEHOLDER +# 🚀 Java HTTP Server – Team juv25d -# Creating a New Filter +A lightweight, modular HTTP server built from scratch in Java. -Filters allow you to intercept and modify HTTP requests *before* they reach the plugin, and modify responses *before* they are sent back to the client. -They are executed in sequence through a `FilterChain`. +This project demonstrates how web servers and backend frameworks work internally — without using Spring, Tomcat, or other high-level frameworks. + +The server is distributed as a Docker image via GitHub Container Registry (GHCR). + +--- + +# 📌 Project Purpose + +The goal of this project is to deeply understand: + +- How HTTP works +- How requests are parsed +- How responses are constructed +- How middleware (filters) operate +- How backend frameworks structure request lifecycles +- How static file serving works +- How architectural decisions are documented (ADR) +- How Java services are containerized with Docker + +This is an educational backend architecture project. + +--- + +# ⚙ Requirements + +- Java 21+ (uses Virtual Threads via Project Loom) +- Docker (for running the official container image) + +--- + +# 🏗 Architecture Overview + +## Request Lifecycle + +``` +Client + ↓ +ServerSocket + ↓ +ConnectionHandler (Virtual Thread) + ↓ +Pipeline + ↓ +FilterChain + ↓ +Plugin + ↓ +HttpResponseWriter + ↓ +Client +``` + +--- + +## 🧩 Core Components + +### Server +- Listens on a configurable port +- Accepts incoming socket connections +- Spawns a virtual thread per request (`Thread.ofVirtual()`) + +### ConnectionHandler +- Parses the HTTP request using `HttpParser` +- Creates a default `HttpResponse` +- Executes the `Pipeline` + +### Pipeline +- Holds global filters +- Holds route-specific filters +- Creates and executes a `FilterChain` +- Executes the active plugin + +### Filters +Used for cross-cutting concerns such as: +- Logging +- Authentication +- Rate limiting +- Validation +- Compression +- Security headers + +### Plugin +Responsible for generating the final HTTP response. + +### HttpParser +Custom HTTP request parser that: +- Parses request line +- Parses headers +- Handles `Content-Length` +- Extracts path and query parameters + +### HttpResponseWriter +Responsible for: +- Writing status line +- Writing headers +- Automatically setting `Content-Length` +- Writing response body + +--- + +# 🐳 Running the Server (Official Method) + +The official way to run the server is via Docker using GitHub Container Registry. + +Docker must be installed and running. + +--- + +## Step 1 – Login to GHCR + +```bash +docker login ghcr.io -u +``` + +Use your GitHub Personal Access Token (classic) as password. + +--- + +## Step 2 – Pull the latest image + +```bash +docker pull ghcr.io/ithsjava25/project-webserver-juv25d:latest +``` + +--- + +## Step 3 – Run the container + +```bash +docker run -p 3000:3000 ghcr.io/ithsjava25/project-webserver-juv25d:latest +``` + +--- + +## Step 4 – Open in browser + +``` +http://localhost:3000 +``` + +The server runs on port **3000**. + +--- + +# 🛠 Running in Development (IDE) + +For development purposes, you can run the server directly from your IDE: + +1. Open the project. +2. Run the class: + +``` +org.juv25d.App +``` + +3. Open: + +``` +http://localhost:3000 +``` + +Note: Running the packaged JAR directly with `java -jar` is not supported, as the project is not built as a fat JAR (external dependencies such as SnakeYAML are not bundled). + +--- + +# 🌐 Static File Serving + +The `StaticFilesPlugin` serves files from: + +``` +src/main/resources/static/ +``` + +### Example Mapping + +| File | URL | +|------|------| +| index.html | `/` | +| css/styles.css | `/css/styles.css` | +| js/app.js | `/js/app.js` | + +### Security Features + +- Path traversal prevention +- MIME type detection +- 404 handling +- 403 handling +- Clean URLs (no `/static/` prefix) + +For full architectural reasoning, see: + +➡ `docs/adr/ADR-001-static-file-serving-architecture.md` + +--- + +# 🔄 Creating a Filter + +Filters intercept requests before they reach the plugin. A filter can: -- Inspect or modify the incoming `HttpRequest` -- Inspect or modify the outgoing `HttpResponse` -- Stop the chain (e.g., return a 403 or 429) -- Allow the chain to continue by calling `chain.doFilter(req, res)` + +- Inspect or modify `HttpRequest` +- Inspect or modify `HttpResponse` +- Stop the chain (e.g., return 403) +- Continue processing by calling `chain.doFilter(req, res)` --- ## Filter Interface -All filters must implement: - ```java public interface Filter { void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) throws IOException; } ``` -Example: LoggingFilter -## Creating a filter +--- + +## Example: LoggingFilter + +```java +public class LoggingFilter implements Filter { + @Override + public void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) throws IOException { + System.out.println(req.method() + " " + req.path()); + chain.doFilter(req, res); + } +} +``` + +--- + +## Registering a Global Filter + +```java +pipeline.addGlobalFilter(new LoggingFilter(), 100); +``` + +Lower order values execute first. + +--- + +# 🎯 Route-Specific Filters + +Route filters only execute when the request path matches a pattern. + +### Supported Patterns + +- `/api/*` → matches paths starting with `/api/` +- `/login` → exact match +- `/admin/*` → wildcard support (prefix-based) + +--- + +## Example + +```java +pipeline.addRouteFilter(new JwtAuthFilter(), 100, "/api/*"); +``` + +--- + +## Execution Flow + +``` +Client → Filter 1 → Filter 2 → ... → Plugin → Response → Client +``` + +--- + +# 🧠 Creating a Plugin + +Plugins generate the final HTTP response. + +They run after all filters have completed. + +--- + +## Plugin Interface + +```java +public interface Plugin { + void handle(HttpRequest req, HttpResponse res) throws IOException; +} +``` + +--- + +## Example: HelloPlugin + +```java +public class HelloPlugin implements Plugin { + + @Override + public void handle(HttpRequest req, HttpResponse res) throws IOException { + res.setStatusCode(200); + res.setStatusText("OK"); + res.setHeader("Content-Type", "text/plain"); + res.setBody("Hello from juv25d server".getBytes()); + } +} +``` + +--- + +## Registering a Plugin + +```java +pipeline.setPlugin(new HelloPlugin()); +``` + +--- + +# ⚙ Configuration + +Configuration is loaded from: + +``` +application-properties.yml +``` + +Example: + +```yaml +server: + port: 3000 + root-dir: static + +logging: + level: INFO +``` + +--- + +# 📦 Features + +- Custom HTTP request parser (`HttpParser`) +- Custom HTTP response writer (`HttpResponseWriter`) +- Mutable HTTP response model +- Filter chain architecture +- Plugin system +- Static file serving +- MIME type resolution +- Path traversal protection +- Virtual threads (Project Loom) +- YAML configuration (SnakeYAML) +- Dockerized distribution +- Published container image (GHCR) + +--- + +# 📚 Documentation & Architecture Decisions + +Additional technical documentation is available in the `docs/` directory. + +## Architecture Decision Records (ADR) + +Contains architectural decisions and their reasoning. + +``` +docs/adr/ +``` + +Main index: + +``` +docs/adr/README.md +``` -- Create a new class in src/main/java/.../filters/ -- Implement the Filter interface -- Add your logic inside doFilter -- Decide whether to continue the chain or stop it +Includes: -## Register your filter (src/org.example/App.java) +- Static file serving architecture +- ADR template +- Future architecture decisions + +--- + +## Technical Notes + +Advanced filter configuration examples: -Register your filter using: ``` - Pipeline pipeline = new Pipeline(); - pipeline.addFilter(new LoggingFilter()); +docs/notes/ ``` -## Filter execution flow +--- + +# 🎓 Educational Value + +This project demonstrates: + +- How web servers work internally +- How middleware pipelines are implemented +- How static file serving works +- How architectural decisions are documented +- How Java services are containerized and distributed + +--- + +# 👥 Team juv25d -Client → - Filter 1 → Filter 2 → ... → Filter N → - Plugin → Response → - back through filters → Client +Built as a learning project to deeply understand HTTP, backend systems, and modular server architecture. From 9e03435f06ff652ae3e23285f2151b18fdb8624c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mats=20R=C3=B6nnqvist?= Date: Fri, 13 Feb 2026 14:59:19 +0100 Subject: [PATCH 2/7] Update project version to 1.0.2-beta in pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index db06950e..80f2e211 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.juv25d JavaHttpServer - 1.0.0-beta + 1.0.2-beta 25 From df68f1c09d53d87e35e2fd92fbc45fde3e1a8605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mats=20R=C3=B6nnqvist?= Date: Fri, 13 Feb 2026 15:04:05 +0100 Subject: [PATCH 3/7] Update README to reflect fat JAR packaging using Maven Shade Plugin --- README.md | 2 +- src/main/resources/static/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 04cc3b80..42b35656 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ org.juv25d.App http://localhost:3000 ``` -Note: Running the packaged JAR directly with `java -jar` is not supported, as the project is not built as a fat JAR (external dependencies such as SnakeYAML are not bundled). +Note: The project is packaged as a fat JAR using the Maven Shade Plugin, so you can run it with `java -jar target/JavaHttpServer-1.0.2-beta.jar`. --- diff --git a/src/main/resources/static/README.md b/src/main/resources/static/README.md index 04cc3b80..42b35656 100644 --- a/src/main/resources/static/README.md +++ b/src/main/resources/static/README.md @@ -163,7 +163,7 @@ org.juv25d.App http://localhost:3000 ``` -Note: Running the packaged JAR directly with `java -jar` is not supported, as the project is not built as a fat JAR (external dependencies such as SnakeYAML are not bundled). +Note: The project is packaged as a fat JAR using the Maven Shade Plugin, so you can run it with `java -jar target/JavaHttpServer-1.0.2-beta.jar`. --- From 9788610bf4e5da5404e1df311d4fa2c21d8f7b4f Mon Sep 17 00:00:00 2001 From: WHITEROSE Date: Sat, 14 Feb 2026 00:05:32 +0100 Subject: [PATCH 4/7] fix: specify final jar name so the DockerBuild picks the correct one, rollback pom version --- pom.xml | 15 ++------------- .../java/org/juv25d/plugin/StaticFilesPlugin.java | 1 + 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index 80f2e211..74a6053e 100644 --- a/pom.xml +++ b/pom.xml @@ -58,19 +58,6 @@ maven-install-plugin 3.1.4 - - org.apache.maven.plugins - maven-jar-plugin - 3.5.0 - - - - true - org.juv25d.App - - - - org.apache.maven.plugins maven-resources-plugin @@ -182,6 +169,8 @@ shade + app + false false diff --git a/src/main/java/org/juv25d/plugin/StaticFilesPlugin.java b/src/main/java/org/juv25d/plugin/StaticFilesPlugin.java index 2fe3de15..00ae8012 100644 --- a/src/main/java/org/juv25d/plugin/StaticFilesPlugin.java +++ b/src/main/java/org/juv25d/plugin/StaticFilesPlugin.java @@ -29,3 +29,4 @@ public void handle(HttpRequest request, HttpResponse response) throws IOExceptio response.setBody(staticResponse.body()); } } + From 3ecc5362e5f00b3dc568d6380878140d111f4e29 Mon Sep 17 00:00:00 2001 From: WHITEROSE Date: Sat, 14 Feb 2026 00:16:17 +0100 Subject: [PATCH 5/7] rollback POM version to original --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 74a6053e..b87ce149 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.juv25d JavaHttpServer - 1.0.2-beta + 1.0-SNAPSHOT 25 From 1f43675eba239b9466cc2bb6a051f9a821f9190b Mon Sep 17 00:00:00 2001 From: WHITEROSE Date: Sat, 14 Feb 2026 00:18:06 +0100 Subject: [PATCH 6/7] update README with dynamic tag and correct port number --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 42b35656..740f310a 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ docker pull ghcr.io/ithsjava25/project-webserver-juv25d:latest ## Step 3 – Run the container ```bash -docker run -p 3000:3000 ghcr.io/ithsjava25/project-webserver-juv25d:latest +docker run -p 8080:8080 ghcr.io/ithsjava25/project-webserver-juv25d:latest ``` --- @@ -139,10 +139,10 @@ docker run -p 3000:3000 ghcr.io/ithsjava25/project-webserver-juv25d:latest ## Step 4 – Open in browser ``` -http://localhost:3000 +http://localhost:8080 ``` -The server runs on port **3000**. +The server runs on port **8080**. --- @@ -160,10 +160,10 @@ org.juv25d.App 3. Open: ``` -http://localhost:3000 +http://localhost:8080 ``` -Note: The project is packaged as a fat JAR using the Maven Shade Plugin, so you can run it with `java -jar target/JavaHttpServer-1.0.2-beta.jar`. +Note: The project is packaged as a fat JAR using the Maven Shade Plugin, so you can run it with `java -jar target/JavaHttpServer-.jar`. --- @@ -327,7 +327,7 @@ Example: ```yaml server: - port: 3000 + port: 8080 root-dir: static logging: From e0670b2c123d98a0c22855d06dd22a96bd9b768f Mon Sep 17 00:00:00 2001 From: WHITEROSE Date: Sat, 14 Feb 2026 00:22:54 +0100 Subject: [PATCH 7/7] update README with the dynamic specified jar file generated from maven-shaded --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 740f310a..86871819 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ org.juv25d.App http://localhost:8080 ``` -Note: The project is packaged as a fat JAR using the Maven Shade Plugin, so you can run it with `java -jar target/JavaHttpServer-.jar`. +Note: The project is packaged as a fat JAR using the Maven Shade Plugin, so you can run it with `java -jar target/app.jar`. ---