Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

custom-web-server

A lightweight HTTP web server built from scratch in Java — no frameworks, no servlet containers. The server is implemented directly on top of the Java NIO APIs (ServerSocketChannel and Selector), with hand-written HTTP parsing and response serialization.

This project is a deep dive into how web servers actually work under the hood: the reactor pattern, non-blocking I/O, HTTP message parsing, and request routing.

How it works

Client ──TCP──▶ ServerSocketChannel ──▶ Selector (reactor thread)
                                              │
                                    accept / read / write events
                                              │
                                    HttpDecoder (raw text → HttpRequest)
                                              │
                                    HttpHandler (route dispatch)
                                              │
                                    RequestRunner (your controller lambda)
                                              │
                                    ResponseWriter (HttpResponse → raw text)
                                              │
                                    SocketChannel (response written back)

TcpEventHandler runs a single-threaded event loop. For every selection key it:

  1. Accepts new client connections (optionally gated by a simple rate limiter), registers them for read/write, and tracks them by a UUID.
  2. Reads incoming bytes into a buffer, decodes them into an HttpRequest, and executes the matching route handler synchronously.
  3. Writes the serialized response back to the channel and closes the connection.

Features

  • Raw Java NIO reactor — a single selector thread multiplexes all connections using SelectionKey interest sets (OP_ACCEPT | OP_READ | OP_WRITE).
  • Framework-free HTTP stack — the HTTP request/response lifecycle (parsing, routing, serialization) is implemented by hand; there is no Tomcat, Netty, or servlet API.
  • Fluent routing API — register handlers as lambdas:
    EventWebServer server = new EventWebServer(8888);
    server.handleRoute(HttpMethod.GET, "/records", request ->
        new HttpResponse.Builder()
            .setStatusCode(200)
            .setEntity("Plain text entity")
            .addHeader("Content-Type", "text/plain")
            .build());
    server.start();
  • Hand-written HTTP decoder — parses the request line, headers, and body from raw bytes into a typed HttpRequest model.
  • Response writer — serializes HttpResponse objects (status line, headers, entity) back into HTTP wire format.
  • Correct status semantics — unknown routes return 404 Not Found, malformed requests return 400 Bad Request.
  • Connection tracking — every accepted connection is registered under a UUID with bidirectional lookups, and responses are staged per connection before being flushed.
  • Simple rate limiter applied to new connection accepts.
  • Graceful shutdown hook — stop() closes the selector and server channel.
  • Unit tests for the HTTP model and request lifecycle (JUnit 4 & 5).

Tech stack

  • Java 21
  • Maven
  • JUnit 4 / JUnit Jupiter (tests)

Getting started

git clone https://github.com/haithembenkhalef/custom-web-server.git
cd custom-web-server
mvn compile exec:java -Dexec.mainClass="com.webby.App"

Current limitations

Being an educational from-scratch implementation, it deliberately trades completeness for clarity:

  • Request processing is synchronous on the selector thread — a slow handler blocks the whole event loop.
  • One ByteBuffer read per request: requests larger than the buffer or split across TCP segments are not reassembled.
  • HTTP/1.0-style semantics: the connection is closed after each response (no keep-alive or pipelining).
  • No chunked transfer encoding, no Expect: 100-continue, no compression.
  • The rate limiter is a simple accept gate, not a per-client token bucket.

Roadmap

Planned improvements, roughly in order:

  • Offload request handling to a worker ExecutorService (pool objects already exist but are not wired in) so slow routes never block the reactor.
  • Ring-buffer pipeline (LMAX Disruptor) — the original design goal: publish read events onto a ring buffer and consume them with dedicated handlers, removing locks and garbage from the hot path. The dependency and the events package are already in place for this.
  • Full request reassembly — accumulate partial reads until the full request (headers + Content-Length body) has arrived.
  • HTTP/1.1 keep-alive & pipelining — persistent connections, correct Connection header handling, and ordered pipelined responses.
  • Proper per-client rate limiting (token bucket) instead of a global accept gate.
  • Content negotiation & richer routing — path parameters, wildcards, filters/middleware.
  • Configuration — port, buffer sizes, and pool sizes via file or CLI args instead of hard-coded values.
  • Structured logging & metrics — request counts, latency, active connections.
  • Test coverage — integration tests over real sockets and decoder edge-case tests.

Why this project

Most developers use web servers every day without ever seeing what happens behind the scene. This repository exists to close that gap: every layer — socket multiplexing, HTTP parsing, routing, serialization — is explicit, small, and readable, making it a useful reference for anyone learning network programming or building high-performance Java services.

About

A lightweight HTTP web server built from scratch in Java on raw NIO (Selector reactor pattern) — hand-written HTTP parsing, routing, and response serialization, with no frameworks or servlet containers. Deep-dive into how web servers work under the hood.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages