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.
- 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
/uploadacceptsapplication/json, validates and pretty-prints the JSON, and writes it toresources/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.
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
- Java 21 (pom.xml is configured to compile with Java 21). If you use a different JDK (e.g., 17), update the
maven.compiler.releaseor thereleasetag in themaven-compiler-pluginconfiguration inpom.xml. - Maven (for building and packaging)
From the repository root run:
mvn clean packageThis 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
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 sizeParameters (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.
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.
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:
- Use the Maven dependency plugin to create a classpath and run with
java -cp. - Use
mvn dependency:copy-dependenciesto copy dependencies intotarget/dependency/and run with-classpathincluding the jar and the dependencies.
- Build the project (produces
target/HTTP-Server-1.0-SNAPSHOT.jar):
mvn clean package- Copy dependencies to
target/dependency/(optional, convenient for running):
mvn dependency:copy-dependencies -DoutputDirectory=target/dependencyAfter 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 20If 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.
- 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)
-
Listener thread:
- A
ServerSocketbound 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.
- A
-
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.
-
Request router and handlers:
- GET: serves static files from
resources/with basic MIME handling forhtmland binary attachments forpng/jpg/txt. - POST
/upload: acceptsapplication/json, parses it with Jackson, pretty-prints, and writes toresources/uploads/.
- GET: serves static files from
- Accept connection —
Socketconfigured with a socket timeout (idle timeout). - Worker loop — a worker will handle up to
KEEP_ALIVE_MAXrequests from the same connection if the client asks for keep-alive. - 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.
- Parse request line and headers
- Extracts method, path, version and headers into an
HttpRequeststructure.
- Extracts method, path, version and headers into an
- Validate Host header
- The server enforces that the Host header matches the configured
host:portorlocalhost:port/127.0.0.1:port.
- The server enforces that the Host header matches the configured
- Decide keep-alive
- Keep-alive is honored for HTTP/1.1 unless a
Connection: closeheader is present.
- Keep-alive is honored for HTTP/1.1 unless a
- Route and handle
- For
GETthe server resolves the path underresources/with normalization to avoid traversal attacks. - For
POST /uploadthe server reads theContent-Lengthbytes, parses JSON and writes touploads/.
- For
- Send response headers and body
- Headers include standard fields: Date, Server, Content-Length, Content-Type and optional Keep-Alive hint.
- Continue or close depending on keep-alive and server limits.
- Thread pool: fixed size configured by the
poolargument. 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).
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.
- Directory traversal: the server checks for
..and normalizes the requested path to ensure it stays underresources/. - 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-Lengthand 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.
- 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.
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.
- 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.
- 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_HOMEto a compatible JDK or change the release version inpom.xml. - Port already in use: pick another port or kill the process using the port (
lsof -i :8080).
pom.xmladjusted to produce a simple JAR viamaven-jar-pluginand to include aMain-Classentry in the manifest. Dependencies remain external and must be on the classpath at runtime.README.mdrewritten and expanded with architecture, internals, run/build instructions for a simple JAR, tuning, security, and debugging guidance.
- If you prefer a single-file runnable artifact, reintroduce the shade plugin.
- Add a small shell script
scripts/run.shto copy dependencies and run the server for convenience. - Add automated unit and integration tests to validate request parsing and file handling.