𒀭 Lothal · Indus Valley · HTTP/1.1
Built from the
socket up.

A production-grade HTTP/1.1 web server written in Modern C++ using WinSock. No framework. No abstraction borrowed. Served, explained, and demonstrated right here.

8 HTTP Methods
5 Middleware Layers
RAM File Cache
GZ Gzip Compression
206 Byte Range Support
C++ Modern Standard
The City · Section 01
01 ◆ LOTHAL

What is Lothal?

Lothal is a production-grade HTTP/1.1 web server built entirely from scratch in Modern C++, communicating directly with the Windows networking stack through WinSock. No framework. No borrowed abstraction. Every layer — from the TCP socket that listens for connections, to the request parser that decodes raw bytes into an HTTP message, to the middleware pipeline that processes each request through its stages — was implemented by hand.

It is an educational project with production ambitions. The architecture mirrors what you would find in a real-world server — a thread pool, an in-memory file cache, ETag-based caching, Gzip compression, chunked transfer encoding, byte range support, and a full middleware pipeline. The goal: understand how the modern web actually works by building it.

The project is named after Lothal — the ancient Indus Valley port city, excavated in Gujarat. It was one of the world's first planned dockyards, where ships arrived, goods were inspected, sorted, and dispatched through carefully engineered channels. An HTTP server works the same way. A request arrives. It is inspected. It passes through channels. It is sorted. A response departs.

"The best way to understand a port city is to watch a ship arrive."

What's implemented

TCP Server

Raw WinSock socket, bind, listen, accept — all from scratch

HTTP Parser

Parses request line, headers, query params, body, route params

Middleware Pipeline

Composable middleware: Logger, Auth, CORS, Rate Limiter, Exception

Thread Pool

Multi-threaded request handling with a configurable pool

RAM File Cache

In-memory caching with ETag and Last-Modified support

Gzip Compression

Content-Encoding: gzip when client sends Accept-Encoding

Chunked Transfer

Transfer-Encoding: chunked for streaming responses

Byte Ranges

206 Partial Content — enables video streaming

Dynamic Router

Compiled route patterns, named params, priority-based matching

The Dockyard · Section 02
02 ≋ DOCKYARD

How a request travels through Lothal

When a browser sends an HTTP request, it is like a merchant ship entering the ancient port. Watch every step — from the TCP handshake at the harbor gates to the response departing back across the water.

Request Journey
◈ Live Inspection
Raw HTTP Request
// Click "Send a Live Request" to see the actual bytes...
Server Response
// Response will appear here...

Anatomy of an HTTP Request

Incoming raw bytes — what Lothal's parser actually receives HTTP/1.1
GET /api/hello HTTP/1.1          ← Request Line: method + path + version
Host: lothal.local               ← Required header: target server
Accept: text/plain, */*            ← What formats the client understands
Accept-Encoding: gzip, deflate  ← Compression support
Connection: keep-alive             ← Reuse the TCP connection
Authorization: Bearer lothal-demo  ← Auth token for /api/* routes
                                    ← Empty line marks end of headers
                                    ← Body follows (empty for GET)

TCP Handshake

SYN → SYN-ACK → ACK. Three packets before a single byte of HTTP is sent.

Socket Accept

accept() returns a client socket. The thread pool picks it up immediately.

recv() Loop

Raw bytes arrive. Lothal reads until the \r\n\r\n header boundary.

Request Parsing

Method, path, version, headers, query string, body — all extracted in a single pass.

Pipeline → Router

Middleware runs in order. If all pass, the router matches the path to a handler.

send() Response

The built HTTP response is written back to the socket. Keep-Alive? Stay open.

The Channels · Section 03
03 ≋ CHANNELS

The Middleware Pipeline

The Harappan dockyard used a system of channels to route water and goods. Lothal's middleware pipeline works the same way. Each request flows through a series of gates. Each gate can inspect, modify, or stop the request.

ExceptionMiddleware

Catches unhandled exceptions. Prevents crashes from reaching the socket.

LoggerMiddleware

Records every request: method, path, status, duration.

CorsMiddleware

Adds CORS headers. Handles OPTIONS preflight automatically.

AuthMiddleware

Guards /api/* routes. Requires Bearer token.

RateLimitMiddleware

Sliding window. Max 500 requests per 10 seconds per client.

StaticFileMiddleware

Serves /public/ files with MIME detection and caching.

Trade Routes · Section 04
04 ⌘ ROUTES

The Dynamic Router

Ancient trade routes were memorized patterns — routes from the dockyard to the workshops, to the granary, to the city gates. Lothal's router compiles your path patterns at startup and matches incoming requests in microseconds.

Route Compilation

⌘ How Lothal compiles /users/:id into a regex C++
// RouteCompiler.cpp — pattern → regex conversion
// Pattern:  /users/:id/posts/:postId
// Becomes:  ^/users/([^/]+)/posts/([^/]+)$
// Params:   {0: "id", 1: "postId"}

string RouteCompiler::compile(const Route& route) {
    string pattern = "^";
    for (auto& segment : route.segments) {
        if (segment.starts_with(':')) {
            pattern += "([^/]+)";  // Named capture
        } else {
            pattern += segment;
        }
        pattern += '/';
    }
    pattern += "$";
    return pattern;
}

Route Priority

P1

Static Exact

/api/hello — highest priority. No wildcards.

P2

Named Param

/users/:id — one variable segment.

P3

Wildcard

/files/* — matches anything. Lowest priority.

The Workshops · Section 05
05 ⊗ WORKSHOPS

HTTP Methods Playground

The workshops of Lothal processed goods by type — copper, ceramics, beads. HTTP methods define the type of operation. Try every method. Watch Lothal respond.

Auth Token: Bearer lothal-demo
Required for /api/* routes — already included below
Request Headers
Request Body
GET

Retrieve

Read data. Safe, idempotent. The most common method.

POST

Create

Send data to create a resource. Not idempotent.

PUT

Replace

Replace a resource entirely. Idempotent.

PATCH

Update

Partial update. Only the changed fields.

DEL

Delete

Remove a resource. Idempotent.

HEAD

Headers Only

Same as GET but no body. Check if resource exists.

OPT

CORS Preflight

Ask the server what methods it allows. Used by browsers.

QUERY

Custom Method

Lothal's own extension. A GET with a structured query body.

The Granary · Section 06
06 ▦ GRANARY

Caching System

The Harappan granary stored grain so the city didn't have to import it every day. Lothal's RAM cache stores files in memory — so the disk doesn't get touched on every request. ETag and Last-Modified tell the browser when grain is still fresh.

Cache Hit vs Cache Miss — Live Demo
LIVE
◈ Cold Request (Cache Miss)

First request — file read from disk, full 200 response

⊕ Warm Request (Cache Hit)

Second request — server returns 304, zero bytes body

ETag

A fingerprint of the file. If it matches If-None-Match, server returns 304.

Last-Modified

File modification date. Browser sends If-Modified-Since next time.

Cache-Control

max-age, no-cache, private — fine-grained cache policy.

304 Not Modified

The most elegant response: headers only, no body. Zero bandwidth for unchanged files.

The Engineers · Section 07
07 ⬡ ENGINEERS

Performance Features

The Harappan engineers built the world's first known dockyard, water channels, and drainage systems. Lothal's engineers built compression, streaming, partial content, and concurrent request handling.

Request the same content with and without Accept-Encoding: gzip. Watch the size difference.

Without Gzip
With Gzip

Lothal's /stream endpoint sends 100 messages using Transfer-Encoding: chunked. Each chunk arrives independently — no Content-Length needed.

// Chunks will appear here as they arrive...

HTTP Range: bytes=X-Y requests. Lothal returns only the requested portion with status 206 Partial Content.

Select a byte range from the file:

Lothal uses a thread pool to handle concurrent requests. The /slow endpoint takes 5 seconds. Fire multiple simultaneously — they run in parallel.

With Connection: keep-alive, the TCP connection stays open between requests. Watch multiple requests flow through a single connection.

The Watchtower · Section 08
08 ✦ WATCHTOWER

Security & Logging

The watchtower guarded the dockyard. Every arriving ship was inspected. Suspicious vessels turned away. Records kept. Lothal's security layer works the same way.

Lothal limits /api/* to 500 requests per 10 seconds per client. Click rapidly to trigger the 429.

All /api/* routes require a Bearer token. Try with and without it.

Without Auth
With Auth

Lothal's CORS middleware adds the appropriate headers to every response. Try an OPTIONS preflight.

The /crash endpoint throws an intentional C++ exception. The ExceptionMiddleware catches it and returns a clean 500.

All requests you make on this page are logged by Lothal's LoggerMiddleware. Tracked live.

LIVE REQUESTS · This Session
No requests yet. Try the playground above.
The Blueprint · Section 09
09 ▦ BLUEPRINT

Architecture & Source

LOTHAL — REQUEST LIFECYCLE Browser (Client) │ ▼ TCP SYN → SYN-ACK → ACK WinSock accept() ← server.cpp:Server::start() │ ▼ Thread Pool picks up the connection recv() raw bytes ← socket.cpp │ ▼ Parse request line + headers + body HttpRequest::parse() ← HttpRequest.cpp │ ▼ Run middleware chain MiddlewarePipeline::run() ← MiddlewarePipeline.cpp │ ├── ExceptionMiddleware try { next() } catch { 500 } ├── StaticFileMiddleware serve /public/ before routing ├── LoggerMiddleware log method + path + status ├── CorsMiddleware add CORS headers, handle OPTIONS ├── AuthMiddleware /api/* → require Bearer token └── RateLimitMiddleware sliding window per client IP │ ▼ Router matches path → handler Router::handle() ← router.cpp (RouteCompiler + RouteMatcher) │ ▼ Handler populates HttpResponse HttpResponse::build() ← HttpResponse.cpp │ ├── Gzip if Accept-Encoding: gzip ├── Chunked if setChunked(true) ├── ETag / 304 conditional GET └── Byte Range / 206 Range: bytes=X-Y │ ▼ send() response bytes Keep-Alive? Connection: keep-alive → loop │ ▼ Browser receives response

Source File Map

.cpp
server.cpp

TCP socket lifecycle: bind, listen, accept, thread dispatch

.cpp
HttpRequest.cpp

HTTP/1.1 request parser: request line, headers, body, params

.cpp
HttpResponse.cpp

Response builder: status, headers, body, chunked, gzip

.cpp
router.cpp

Route registration, priority sorting, and dispatch

.cpp
RouteCompiler.cpp

Converts /users/:id patterns to compiled regex

.cpp
RouteMatcher.cpp

Matches incoming path against compiled routes, extracts params

.cpp
MiddlewarePipeline.cpp

Composes the middleware chain, manages the next() call

.cpp
ThreadPool.cpp

Work queue + worker threads. N configurable worker threads.

.cpp
FileCache.cpp

RAM-based file cache. Stores file bytes + metadata in memory

.cpp
Compression.cpp

Gzip compression using zlib. Applied before sending

.cpp
ChunkedResponse.cpp

Encodes body into HTTP chunked transfer encoding

.cpp
ETag.cpp

Generates ETag fingerprint. Handles If-None-Match → 304

.cpp
StaticFileMiddleware.cpp

Serves /public/ with MIME detection, caching, ranges

.cpp
AuthMiddleware.cpp

Guards /api/* paths. Checks Authorization header

.cpp
RateLimitMiddleware.cpp

Per-IP sliding window counter. Returns 429 when exceeded

.cpp
CorsMiddleware.cpp

Adds CORS headers. Responds to OPTIONS preflight

.cpp
Logger.cpp

File + console logger. Thread-safe. Timestamps every entry

.cpp
MimeTypes.cpp

Extension → MIME type mapping. ~30 types including binary

.conf
lothal.conf

Config: port, threads, document root, keep-alive, gzip

.cpp
HttpRange.cpp

Parses Range: header, validates ranges, serves 206 partial

Why these choices?

Thread Pool instead of one thread per connection

Creating a thread for every connection is expensive. A pool with N threads handles N concurrent requests with zero allocation overhead per request. Lothal's pool is configured via lothal.conf.

RAM Cache instead of disk reads per request

Disk I/O is orders of magnitude slower than RAM. Static files (HTML, CSS, JS, images) are loaded once into an in-memory map. Subsequent requests are served in nanoseconds.

Custom Router instead of a regex library

The router compiles patterns at startup into a sorted, prioritized list. At request time, matching is a sequential scan through pre-compiled patterns — fast, predictable, and controllable.

WinSock instead of a cross-platform socket library

The goal was to understand sockets at the OS level. WinSock is the Windows native API — the actual layer between your code and the network driver. No abstraction was used intentionally.

Lothal

"This website is served by Lothal itself."

⊕ Back to Top ⌥ GitHub Repository

Named after Lothal, the ancient Indus Valley port city excavated in Gujarat, India (2400 BCE).

Port Architect · Shantanu Gopal Vispute