A lightweight, modular HTTP server built from scratch in Java.
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).
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.
- Java 21+ (uses Virtual Threads via Project Loom)
- Docker (for running the official container image)
Client
↓
ServerSocket
↓
ConnectionHandler (Virtual Thread)
↓
Pipeline
↓
FilterChain
↓
Router
↓
Plugin
↓
HttpResponseWriter
↓
Client
- Listens on a configurable port
- Accepts incoming socket connections
- Spawns a virtual thread per request (
Thread.ofVirtual())
- Parses the HTTP request using
HttpParser - Creates a default
HttpResponse - Executes the
Pipeline
- Holds global filters
- Holds route-specific filters
- Creates and executes a
FilterChain - Executes the active plugin
Used for cross-cutting concerns such as:
- Logging
- Authentication
- Rate limiting
- Validation
- Compression
- Security headers
Responsible for generating the final HTTP response.
Custom HTTP request parser that:
- Parses request line
- Parses headers
- Handles
Content-Length - Extracts path and query parameters
Responsible for:
- Writing status line
- Writing headers
- Automatically setting
Content-Length - Writing response body
The official way to run the server is via Docker using GitHub Container Registry.
Docker must be installed and running.
docker login ghcr.io -u <your-github-username>Use your GitHub Personal Access Token (classic) as password.
docker pull ghcr.io/ithsjava25/project-webserver-juv25d:latestdocker run -p 8080:8080 ghcr.io/ithsjava25/project-webserver-juv25d:latesthttp://localhost:8080
The server runs on port 8080.
For development purposes, you can run the server directly from your IDE:
- Open the project.
- Run the class:
org.juv25d.App
- Open:
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/app.jar.
The StaticFilesPlugin serves files from:
src/main/resources/static/
| File | URL |
|---|---|
| index.html | / |
| css/styles.css | /css/styles.css |
| js/app.js | /js/app.js |
- 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
Filters intercept requests before they reach the plugin.
A filter can:
- Inspect or modify
HttpRequest - Inspect or modify
HttpResponse - Stop the chain (e.g., return 403)
- Continue processing by calling
chain.doFilter(req, res)
public interface Filter {
void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) throws IOException;
}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);
}
}pipeline.addGlobalFilter(new LoggingFilter(), 100);Lower order values execute first.
Route filters only execute when the request path matches a pattern.
/api/*→ matches paths starting with/api//login→ exact match/admin/*→ wildcard support (prefix-based)
pipeline.addRouteFilter(new JwtAuthFilter(), 100, "/api/*");Client → Filter 1 → Filter 2 → ... → Plugin → Response → Client
Plugins generate the final HTTP response.
They run after all filters have completed.
public interface Plugin {
void handle(HttpRequest req, HttpResponse res) throws IOException;
}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());
}
}pipeline.setPlugin(new HelloPlugin());Configuration is loaded from:
application-properties.yml
Example:
server:
port: 8080
root-dir: static
logging:
level: INFO- 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)
Additional technical documentation is available in the docs/ directory.
Contains architectural decisions and their reasoning.
docs/adr/
Main index:
docs/adr/README.md
Includes:
- Static file serving architecture
- ADR template
- Future architecture decisions
Advanced filter configuration examples:
docs/notes/
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
Built as a learning project to deeply understand HTTP, backend systems, and modular server architecture.