A modern, high-performance HTTP web server built from scratch in Java
Features • Quick Start • Configuration
- 🚀 High Performance - Virtual threads for handling thousands of concurrent connections
- 📁 Static File Serving - HTML, CSS, JavaScript, images, PDFs, fonts, and more
- 🎨 Smart MIME Detection - Automatic Content-Type headers for 20+ file types
- ⚙️ Flexible Configuration - YAML or JSON config files with sensible defaults
- 🔒 Security First - Path traversal protection and input validation
- 🐳 Docker Ready - Multi-stage Dockerfile for easy deployment
- ⚡ HTTP/1.1 Compliant - Proper status codes, headers, and responses
- 🎯 Custom Error Pages - Branded 404 pages and error handling
| Tool | Version | Purpose |
|---|---|---|
| ☕ Java | 21+ | Runtime environment |
| 📦 Maven | 3.6+ | Build tool |
| 🐳 Docker | Latest | Optional - for containerization |
┌─────────────────────────────────────────────┐
│ Ready to launch your web server? │
│ Follow these simple steps! │
└─────────────────────────────────────────────┘
git clone git clone https://github.com/ithsjava25/project-webserver-juv25g.git
cd project-webservermvn clean compileOption A: Run directly with Maven (recommended for development)
mvn exec:java@runOption B: Run compiled classes directly
mvn clean compile
java -cp target/classes org.example.AppOption C: Using Docker
docker build -t webserver .
docker run -p 8080:8080 -v $(pwd)/www:/www webserverThe server will start on the default port 8080 and serve files from the www/ directory.
Open your browser and navigate to:
http://localhost:8080
The server can be configured using a YAML or JSON configuration file located at:
src/main/resources/application.yml
server:
port: 8080
rootDir: "./www"
logging:
level: "INFO"{
"server": {
"port": 8080,
"rootDir": "./www"
},
"logging": {
"level": "INFO"
}
}| Property | Type | Default | Description |
|---|---|---|---|
server.port |
Integer | 8080 |
Port number the server listens on (1-65535) |
server.rootDir |
String | "./www" |
Root directory for serving static files |
logging.level |
String | "INFO" |
Logging level (INFO, DEBUG, WARN, ERROR) |
If no configuration file is provided or values are missing, the following defaults are used:
- Port: 8080
- Root Directory: ./www
- Logging Level: INFO
project-webserver/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── org/example/
│ │ │ ├── App.java # Main entry point
│ │ │ ├── TcpServer.java # TCP server implementation
│ │ │ ├── ConnectionHandler.java # HTTP request handler
│ │ │ ├── StaticFileHandler.java # Static file server
│ │ │ ├── config/ # Configuration classes
│ │ │ ├── http/ # HTTP response builder & MIME detection
│ │ │ ├── httpparser/ # HTTP request parser
│ │ │ └── filter/ # Filter chain (future feature)
│ │ └── resources/
│ │ └── application.yml # Configuration file
│ └── test/ # Unit tests
├── www/ # Web root (static files)
│ ├── index.html
│ ├── pageNotFound.html # Custom 404 page
│ └── ... # Other static files
├── pom.xml
└── README.md
Place your static files in the www/ directory (or the directory specified in server.rootDir).
The server automatically detects and serves the correct Content-Type for:
Text & Markup:
- HTML (
.html,.htm) - CSS (
.css) - JavaScript (
.js) - JSON (
.json) - XML (
.xml) - Plain text (
.txt)
Images:
- PNG (
.png) - JPEG (
.jpg,.jpeg) - GIF (
.gif) - SVG (
.svg) - WebP (
.webp) - ICO (
.ico)
Documents:
- PDF (
.pdf)
Fonts:
- WOFF (
.woff) - WOFF2 (
.woff2) - TrueType (
.ttf) - OpenType (
.otf)
Media:
- MP4 video (
.mp4) - WebM video (
.webm) - MP3 audio (
.mp3) - WAV audio (
.wav)
Unknown file types are served as application/octet-stream.
The server applies the following URL transformations:
| Request URL | Resolved File |
|---|---|
/ |
index.html |
/about |
about.html |
/contact |
contact.html |
/styles.css |
styles.css |
/page.html |
page.html |
Note: URLs ending with / are resolved to index.html, and URLs without an extension get .html appended automatically.
If a requested file doesn't exist, the server returns:
pageNotFound.html(if it exists in the web root)- Otherwise: Plain text "404 Not Found"
To customize your 404 page, create www/pageNotFound.html.
Returned when a path traversal attack is detected (e.g., GET /../../etc/passwd).
The server validates all file paths to prevent directory traversal attacks:
✅ Allowed: /index.html
✅ Allowed: /docs/guide.pdf
❌ Blocked: /../../../etc/passwd
❌ Blocked: /www/../../../secret.txt
All blocked requests return 403 Forbidden.
mvn testTest coverage includes:
- HTTP request parsing
- Response building
- MIME type detection
- Configuration loading
- Static file serving
- Path traversal security
docker build -t webserver .
docker run -d -p 8080:8080 -v $(pwd)/www:/www --name my-webserver webserver# Compile the project
mvn clean compile
# Run with nohup for background execution
nohup java -cp target/classes org.example.App > server.log 2>&1 &
# Or use systemd (create /etc/systemd/system/webserver.service)Directory structure:
www/
├── index.html
├── styles.css
├── app.js
└── images/
└── logo.png
Access:
- Homepage:
http://localhost:8080/ - Stylesheet:
http://localhost:8080/styles.css - Logo:
http://localhost:8080/images/logo.png
application.yml:
server:
port: 3000
rootDir: "./public"Access at: http://localhost:3000/
application.yml:
server:
rootDir: "./dist"Server will serve files from dist/ instead of www/.
- TcpServer accepts incoming TCP connections
- ConnectionHandler creates a virtual thread for each request
- HttpParser parses the HTTP request line and headers
- StaticFileHandler resolves the file path and reads the file
- HttpResponseBuilder constructs the HTTP response with correct headers
- Response is written to the client socket
The project includes a filter chain interface for future extensibility:
- Request/response filtering
- Authentication
- Logging
- Compression
Error: Address already in use
Solution: Change the port in application.yml or kill the process using port 8080:
# Linux/Mac
lsof -ti:8080 | xargs kill -9
# Windows
netstat -ano | findstr :8080
taskkill /PID <PID> /FSolution: Check that the file is in the correct directory (www/ by default) and that the filename matches exactly (case-sensitive on Linux/Mac).
Solution: This should not happen with the current implementation. The server uses byte[] internally to preserve binary data. If you see this issue, please report it as a bug.
- Fork the repository
- Create a feature branch (
git checkout -b feature/new-feature) - Commit your changes (
git commit -m 'Add new feature') - Push to the branch (
git push origin feature/new-feature) - Open a Pull Request