Skip to content

faseey/Multithreaded-Web-Server-from-Scratch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HTTP Server from Scratch

A multi-threaded HTTP server built from scratch in Java — no frameworks, no Netty, no Spring. Raw sockets, manual HTTP parsing, thread pool concurrency.

Architecture Diagram


What it does

  • Accepts TCP connections on a configurable port
  • Parses raw HTTP/1.1 requests byte-by-byte from the socket InputStream
  • Routes HEAD``GET and POST requests to static files or template-injected HTML
  • Serves files from a configurable webroot directory
  • Returns structured HTTP responses with correct status codes, headers, and body
  • Handles malformed requests, missing files, and server errors gracefully

Architecture

The server is structured in two clear zones separated by the syscall boundary.

OS kernel (host)

The kernel owns everything below your Java code:

  • TCP/IP stack — reassembles inbound packets into a byte stream, segments outbound bytes into packets
  • Accept queue — holds completed TCP connections (after 3-way handshake) until your code calls accept()
  • Socket send/recv buffersOutputStream.write() copies bytes here; the kernel handles NIC timing
  • VFS / Filesystem — services FileInputStream.read() syscalls, reads from SSD
  • NIC driver — hardware interface to the network wire

Your Java code never talks to the NIC or SSD directly. It makes syscalls and the kernel handles the rest.

JVM / user space (inside Docker container)

ServerListenerThread
  └── calls accept() in a loop
  └── submits each Socket to the thread pool

ThreadPool (Executors.newFixedThreadPool(500))
  └── selects an idle thread
  └── executes HttpConnectionWorkerThread

HttpConnectionWorkerThread
  ├── HttpParser
  │   ├── parseRequestLine()   → method, target, HTTP version
  │   ├── parseHeaders()       → name:value pairs
  │   └── parseBody()          → form-urlencoded or JSON
  ├── HttpRequest              → structured result
  ├── Router
  │   ├── GET  → static file or template injection
  │   ├── POST → body params
  │   └── HEAD/DELETE → 501 Not Implemented
  ├── WebRootHandler           → reads files from webroot (SSD via syscall)
  ├── HttpResponse.build()     → assembles status line + headers + body bytes
  └── OutputStream.write()     → copies to kernel send buffer → TCP → NIC → wire

HTTP lives only in user space

HTTP is constructed twice and parsed twice — symmetrically:

Side Outbound Inbound
Client Browser builds GET /index.html HTTP/1.1\r\n... Browser parses response headers + body
Server HttpResponse.build() assembles response bytes HttpParser reconstructs request from raw bytes

Between those two moments it is just a TCP byte stream. The kernel is completely blind to HTTP.

Docker

The container wraps only the JVM. The OS kernel is the host kernel — shared, not virtualized. Syscalls from inside the container go directly to the host kernel. There is no container kernel.

┌─ Server machine ──────────────────────────┐
│  ┌─ Docker container ──────────────────┐  │
│  │  JVM — your Java server code        │  │
│  └─────────────────────────────────────┘  │
│                                            │
│  OS kernel (host, shared)                 │
│  TCP/IP · Sockets · VFS · NIC driver      │
│                                            │
│  Hardware: NIC · SSD                      │
└────────────────────────────────────────────┘

Configuration

Edit src/main/resources/http.json:

{
  "port": 8080,
  "webroot": "/path/to/your/webroot"
}

ConfigurationManager reads this at startup and passes port to ServerSocket and webroot to WebRootHandler.


Running

# Build
mvn clean package

# Run directly
java -jar target/httpserver.jar

# Run in Docker
docker build -t httpserver .
docker run -p 8080:8080 httpserver

HTTP support

Method Status
GET Supported — static files + template injection
POST Supported — application/json and application/x-www-form-urlencoded
HEAD 501 Not Implemented
DELETE 501 Not Implemented
PATCH 501 Not Implemented

Error handling

Code Condition
400 Bad Request Malformed request line, headers, or body
404 Not Found File not found in webroot
500 Internal Server Error File read failure
501 Not Implemented Unsupported HTTP method
505 HTTP Version Not Supported Non HTTP/1.1 request

Known limitations

  • readAllBytes() loads entire file into heap — not suitable for large file serving
  • No HTTPS / TLS
  • No persistent connections (HTTP keep-alive)
  • No chunked transfer encoding
  • WebRootHandler is shared across all 500 threads — must remain stateless

Package structure

src/main/java/tech/fasih/
├── http/
│   ├── HttpParser                  # raw stream → HttpRequest
│   ├── HttpRequest                 # parsed request model
│   ├── HttpResponse                # response builder
│   ├── HttpMessage                 # base message type
│   ├── HttpHeaders                 # header map
│   ├── HttpMethod                  # enum: GET, POST, HEAD …
│   ├── HttpVersion                 # enum: HTTP/1.1
│   ├── HttpStatusCode              # enum: 200, 400, 404 …
│   ├── HttpParsingException        # bad request signal
│   └── BadHttpVersionException     # 505 signal
└── httpserver/
    ├── config/                     # ConfigurationManager, http.json model
    ├── core/                       # ServerListenerThread, WorkerThread, WebRootHandler
    ├── utils/                      # Json helper
    └── Httpserver.java             # entry point

About

A multithreaded HTTP server built from scratch in Java. Handles HEAD ,GET and POST requests, parses headers, query parameters, and request bodies (JSON and URL-encoded). Uses a thread pool for concurrent connections and is tested with JUnit 5.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors