Skip to content

Prigoistic/JavaMultithreadedHTTPServer

Repository files navigation

Java Minimal HTTP Server

A small, single-file Java HTTP server providing basic static file serving and a JSON upload endpoint.

This project is implemented in src/main/java/com/arnavgpt/httpserver/HttpServer.java and is intentionally lightweight — no external web framework is required. It is suitable for learning, demos, or small internal tooling where a tiny HTTP server is useful.

Features

  • Serve static files from the resources/ directory (examples: index.html, images, and text files).
  • GET requests for HTML and common binary files (png/jpg/txt). HTML is served inline; other files are served as attachments.
  • POST /upload accepts application/json, validates and pretty-prints the JSON, and writes it to resources/uploads/ with a timestamped filename.
  • Basic HTTP/1.1 host validation and keep-alive handling.
  • Fixed-size thread pool with queueing and graceful 503 responses when the server is saturated.

Repository layout

pom.xml
src/
	main/
		java/com/arnavgpt/httpserver/HttpServer.java
resources/
	index.html
	about.html
	contact.html
	logo.jpg
	photo.jpg
	one.txt
	two.txt
	uploads/   <-- created at runtime, stores JSON uploads

Requirements

  • Java 21 (pom.xml is configured to compile with Java 21). If you use a different JDK (e.g., 17), update the maven.compiler.release or the release tag in the maven-compiler-plugin configuration in pom.xml.
  • Maven (for building and packaging)

Build

From the repository root run:

mvn clean package

This will compile the project and create a shaded JAR (fat JAR) under target/. The shade plugin in pom.xml sets the main class to com.arnavgpt.httpserver.HttpServer, so you can run the JAR directly.

Example artifact (name may vary with version):

target/HTTP-Server-1.0-SNAPSHOT-shaded.jar

Run

You can run the server directly from Maven or with the generated JAR.

  • With Maven (runs the main class):
mvn exec:java -Dexec.mainClass=com.arnavgpt.httpserver.HttpServer -Dexec.args="[port] [host] [pool]"
# Example: run on port 8080 (default) -> no args needed
  • With the shaded JAR:
java -jar target/HTTP-Server-1.0-SNAPSHOT-shaded.jar [port] [host] [pool]
# Examples:
# java -jar target/HTTP-Server-1.0-SNAPSHOT-shaded.jar         # defaults: 127.0.0.1:8080
# java -jar target/HTTP-Server-1.0-SNAPSHOT-shaded.jar 9000     # listen on 127.0.0.1:9000
# java -jar target/HTTP-Server-1.0-SNAPSHOT-shaded.jar 8080 0.0.0.0 20  # host and pool size

Parameters (positional):

  • port (int) — TCP port to listen on (default: 8080) HTTP Server — In-depth documentation

This document explains how the server works, how to build and run it as a simple (non-shaded) JAR, and dives into internal design, tuning, and debugging guidance. It's written for developers who want to understand or extend the code.

Repository overview

Source: src/main/java/com/arnavgpt/httpserver/HttpServer.java

Static assets: resources/ (served as the web root). The server creates resources/uploads/ at startup to store POSTed JSON payloads.

Build philosophy

This project is intentionally minimal. The pom.xml has been adjusted to produce a plain (simple) JAR containing only compiled project classes and resources. Third-party libraries (Jackson, Commons IO) remain as Maven dependencies and are not bundled into the JAR. This keeps the generated artifact small and avoids shading or merging dependency classes.

Running the plain JAR requires adding dependencies to the classpath. Two options are common:

  1. Use the Maven dependency plugin to create a classpath and run with java -cp.
  2. Use mvn dependency:copy-dependencies to copy dependencies into target/dependency/ and run with -classpath including the jar and the dependencies.

Build & produce a simple JAR

  1. Build the project (produces target/HTTP-Server-1.0-SNAPSHOT.jar):
mvn clean package
  1. Copy dependencies to target/dependency/ (optional, convenient for running):
mvn dependency:copy-dependencies -DoutputDirectory=target/dependency

Run the simple JAR with dependencies on the classpath

After copying dependencies, run:

java -cp target/HTTP-Server-1.0-SNAPSHOT.jar:target/dependency/* com.arnavgpt.httpserver.HttpServer [port] [host] [pool]
# macOS zsh: use : to separate classpath elements (or ; on Windows cmd)

Examples:

# default
java -cp target/HTTP-Server-1.0-SNAPSHOT.jar:target/dependency/* com.arnavgpt.httpserver.HttpServer

# custom port and pool
java -cp target/HTTP-Server-1.0-SNAPSHOT.jar:target/dependency/* com.arnavgpt.httpserver.HttpServer 9000 127.0.0.1 20

If you prefer a single executable JAR (fat JAR), re-introduce the maven-shade-plugin or use the maven-assembly-plugin — this repository intentionally creates a simple JAR by default.

Command-line arguments

  • port (int) — TCP port to listen on (default 8080)
  • host (string) — interface to bind to (default 127.0.0.1)
  • pool (int) — fixed worker threads (default 10)

High-level architecture

  1. Listener thread:

    • A ServerSocket bound to the configured host:port accepts incoming TCP connections.
    • For every accepted socket, the server creates a short-lived Runnable and submits it to a fixed ThreadPoolExecutor.
    • When both the pool and the bounded queue are saturated, the server synchronously responds with 503 Service Unavailable and closes the connection.
  2. Worker threads:

    • Each worker handles one socket for the life of the connection (can serve multiple HTTP requests if the client uses keep-alive).
    • Input and output are buffered (BufferedInputStream/BufferedOutputStream) and the server reads manually-parsed HTTP request headers and bodies.
  3. Request router and handlers:

    • GET: serves static files from resources/ with basic MIME handling for html and binary attachments for png/jpg/txt.
    • POST /upload: accepts application/json, parses it with Jackson, pretty-prints, and writes to resources/uploads/.

Request lifecycle (detailed)

  1. Accept connection — Socket configured with a socket timeout (idle timeout).
  2. Worker loop — a worker will handle up to KEEP_ALIVE_MAX requests from the same connection if the client asks for keep-alive.
  3. Read request headers
    • The server reads bytes until CRLFCRLF or a header size limit (8 KiB). If the headers exceed the limit or no terminator is found before EOF/timeout, the connection is closed.
  4. Parse request line and headers
    • Extracts method, path, version and headers into an HttpRequest structure.
  5. Validate Host header
    • The server enforces that the Host header matches the configured host:port or localhost:port/127.0.0.1:port.
  6. Decide keep-alive
    • Keep-alive is honored for HTTP/1.1 unless a Connection: close header is present.
  7. Route and handle
    • For GET the server resolves the path under resources/ with normalization to avoid traversal attacks.
    • For POST /upload the server reads the Content-Length bytes, parses JSON and writes to uploads/.
  8. Send response headers and body
    • Headers include standard fields: Date, Server, Content-Length, Content-Type and optional Keep-Alive hint.
  9. Continue or close depending on keep-alive and server limits.

Threading and resource model

  • Thread pool: fixed size configured by the pool argument. The implementation uses a bounded ArrayBlockingQueue for pending tasks.
  • Queue capacity: defined in the source (QUEUE_CAPACITY). If both active threads == pool size and queue is full, new connections receive a 503 response synchronously.
  • Per-connection limits: the server applies a max number of requests per connection (KEEP_ALIVE_MAX) and a socket idle timeout (SOCKET_TIMEOUT_MS).

Important constants (tuning)

See the top of HttpServer.java for the constants; important ones include:

  • REQUEST_HEADER_LIMIT (default 8192) — maximum bytes read when parsing headers. Raise this if you expect large headers.
  • SOCKET_TIMEOUT_MS (default 30_000) — socket read timeout to free idle connections.
  • KEEP_ALIVE_MAX (default 100) — maximum requests allowed on a persistent connection.
  • BUFFER (default 8192) — buffer size used when streaming files.
  • QUEUE_CAPACITY (default 100) — number of pending connections allowed in the executor queue.

Security considerations

  • Directory traversal: the server checks for .. and normalizes the requested path to ensure it stays under resources/.
  • Host header validation: only accepts Host headers that match the configured host:port or loopback equivalents. This avoids some host header attacks when the server is exposed publicly.
  • No TLS: do not run this directly on the public internet without a TLS terminator (e.g., reverse proxy) — plaintext HTTP exposes credentials and data.
  • No authentication or rate-limiting: add protections before using in multi-tenant or public deployments.
  • Input size validation: the server requires Content-Length and reads that many bytes; it does not support chunked encoding — malicious clients can still attempt to send very large bodies; consider an application-level size limit and pre-check Content-Length.

Debugging and observability

  • Logging: the server writes basic, human-readable logs to stdout. Add a proper logging framework (Logback/Log4j) if you need structured logs.
  • Thread dumps: if CPU is high or threads are stuck, produce a thread dump (jstack) and inspect worker thread stacks — look for blocked IO or long file reads.
  • Monitoring: integrate with a process supervisor (systemd/service) and external metrics if needed.

How to create an executable single JAR (optional)

If you need a standalone, runnable JAR with dependencies included, add the maven-shade-plugin back to pom.xml or use maven-assembly-plugin. That will produce a fat JAR that can be started with java -jar without specifying the classpath.

Extending and tests

  • To add more endpoints, factor out routing into a small Router class and create handler classes per route.
  • To add unit tests, introduce a test framework (JUnit) and add tests for path resolution, request parsing, and file serving logic. Integration tests can start the server on an ephemeral port and exercise the HTTP endpoints.

Troubleshooting

  • mvn not found: ensure Maven is installed and on your PATH. On macOS you can install via Homebrew: brew install maven.
  • Java version mismatch: set JAVA_HOME to a compatible JDK or change the release version in pom.xml.
  • Port already in use: pick another port or kill the process using the port (lsof -i :8080).

Summary of changes made in this repository

  • pom.xml adjusted to produce a simple JAR via maven-jar-plugin and to include a Main-Class entry in the manifest. Dependencies remain external and must be on the classpath at runtime.
  • README.md rewritten and expanded with architecture, internals, run/build instructions for a simple JAR, tuning, security, and debugging guidance.

Next recommended actions

  1. If you prefer a single-file runnable artifact, reintroduce the shade plugin.
  2. Add a small shell script scripts/run.sh to copy dependencies and run the server for convenience.
  3. Add automated unit and integration tests to validate request parsing and file handling.

About

A small, single-file Java HTTP server providing basic static file serving and a JSON upload endpoint.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published