[To be honest.. I was too lazy to write all this // so this is claude made ;)]
A fully functional HTTP/1.1 web server written from scratch in C++17, without any external server libraries. Built as a portfolio project demonstrating advanced systems programming concepts including multithreading, network programming, TLS encryption, middleware design patterns, and HTTP protocol implementation.
- TCP Server — raw socket programming with
epoll-ready architecture - TLS/HTTPS — OpenSSL integration via
SSLSocketinheriting fromSocket, with full handshake and encrypted I/O - Thread Pool — concurrent request handling with work queue and graceful shutdown
- HTTP Parser — state-based parsing of HTTP/1.1 requests including headers, query strings, and body
- Router — fast hash-map based routing with support for multiple HTTP methods and static file serving
- HTTP Response — structured response builder with automatic status text resolution
- Middleware Pipeline — Chain of Responsibility pattern with recursive lambda dispatch
- Logger Middleware — per-request timing using
std::chrono::steady_clock - CORS Middleware — preflight
OPTIONShandling andAccess-Control-*headers - Rate Limiter — sliding-window IP-based rate limiting with
std::mutexthread safety - Static File Server — serves files from
src/static/with MIME type detection - Keep-Alive — persistent connections with configurable timeout via
SO_RCVTIMEO - RAII throughout — all resources (sockets, threads, SSL objects) managed automatically via C++ destructors
Client Request (HTTPS)
↓
Acceptor Thread (net/Acceptor) — accept() + extract client IP
↓
SSLSocket (net/SSLSocket) — TLS handshake via OpenSSL
↓
Thread Pool (threadpool/ThreadPool)
↓
HTTP Parser (http/HttpParser)
↓
Keep-Alive Loop — do/while with SO_RCVTIMEO timeout
↓
Middleware Pipeline (middleware/Middleware)
├── Rate Limiter (utils/RateLimiter) — 429 if exceeded
├── CORS — OPTIONS early return, Access-Control headers
└── Logger — method + path + duration
↓
Router (router/Router) — exact match or /static/ prefix
↓
Handler → HttpResponse (http/HttpResponse)
↓
SSLSocket::send() — encrypted response → Client
webserver/
├── src/
│ ├── net/
│ │ ├── Socket.h / Socket.cpp # RAII TCP socket wrapper (fd + client IP), virtual send/recv
│ │ ├── SSLSocket.h / SSLSocket.cpp # TLS socket inheriting Socket, OpenSSL integration
│ │ └── Acceptor.h / Acceptor.cpp # bind, listen, accept → SSLSocket
│ ├── http/
│ │ ├── HttpRequest.h / .cpp # Parsed request object
│ │ ├── HttpResponse.h / .cpp # Response builder + toString()
│ │ └── HttpParser.h / .cpp # Raw bytes → HttpRequest
│ ├── router/
│ │ └── Router.h / Router.cpp # Method+path → handler dispatch
│ ├── middleware/
│ │ └── Middleware.h / Middleware.cpp # Pipeline + recursive Next dispatch
│ ├── threadpool/
│ │ └── ThreadPool.h / .cpp # Worker threads + work queue
│ ├── utils/
│ │ ├── Logger.h / Logger.cpp # Timestamped console logger
│ │ └── RateLimiter.h / .cpp # IP-based sliding window rate limiter
│ ├── static/ # Static files served under /static/*
│ └── main.cpp
├── tests/
├── CMakeLists.txt
└── README.md
- Ubuntu 22.04 (or any Linux with epoll support)
- GCC 11+
- CMake 3.22+
- OpenSSL (
libssl-dev)
sudo apt install libssl-devopenssl req -x509 -newkey rsa:4096 -keyout src/key.pem -out src/cert.pem -days 365 -nodesgit clone https://github.com/yohanan400/webserver.git
cd webserver
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/bin/webserverNote: Set the working directory to
src/so relative paths (static/,cert.pem,key.pem) resolve correctly.
In CLion: Run/Debug Configurations → Working directory →.../webserver/src
router.addRoute("GET", "/api/user",
[](const HttpRequest& req, HttpResponse& response, const std::shared_ptr<Socket>& socket) {
response.setCode(200);
response.setBody(R"({"message": "Hello!"})");
response.setHeaders({
{"Content-Type", "application/json"},
{"Content-Length", std::to_string(response.getBody().size())}
});
});mpl.use([](const HttpRequest& req, HttpResponse& res,
const std::shared_ptr<Socket>& socket, const Next& next) {
// runs before handler
next();
// runs after handler
});Place any file under src/static/ and access it via:
GET /static/filename.ext
Supported MIME types: html, css, js, json, xml, txt, png, jpg, jpeg, gif, svg, pdf, zip, mp3, mp4.
# HTTPS request (skip certificate verification for self-signed)
curl -k https://localhost:5555/api/user
# Verbose (shows TLS handshake + headers)
curl -kv https://localhost:5555/api/user
# Static file
curl -k https://localhost:5555/static/index.html
# CORS preflight
curl -k -X OPTIONS https://localhost:5555/api/user -v
# Trigger rate limiter (run 101 times)
for i in $(seq 1 101); do curl -sk https://localhost:5555/api/user; doneSSLSocket inherits from Socket and overrides the virtual send()/recv() methods with SSL_write()/SSL_read(). The rest of the codebase uses std::shared_ptr<Socket> — no changes needed anywhere else. SSL_CTX is a static member shared across all connections; each connection gets its own SSL* object.
Socket destructor calls close(_fd). SSLSocket destructor calls SSL_shutdown() + SSL_free() before the base destructor closes the fd. No manual cleanup anywhere.
Each client connection runs a do/while loop in its thread. SO_RCVTIMEO is set on the socket so recv() returns after 5 seconds of inactivity with errno == EAGAIN, cleanly closing the connection.
Worker threads sleep via std::condition_variable when the queue is empty — zero CPU usage while idle. A std::atomic<bool> signals shutdown without a mutex.
Routes are stored as "METHOD:path" keys in std::unordered_map, giving O(1) lookup. Static file requests fall through to a /static/ prefix check.
Middlewares are stored as a std::vector<std::function<...>>. Dispatch uses a recursive lambda with an index — each middleware calls next() to advance the chain. This is the Chain of Responsibility pattern.
Uses a sliding window: each IP maps to {count, window_start_time}. On each request, if the time window has expired the counter resets; otherwise it increments. Protected by std::mutex + std::lock_guard for thread safety.
The CORS middleware handles OPTIONS preflight with an early return (204, no next() call) and adds Access-Control-* headers to all other responses before passing to the next middleware.
| Concept | Where |
|---|---|
| Raw TCP sockets | net/Socket, net/Acceptor |
| TLS/SSL | net/SSLSocket, OpenSSL |
| Inheritance + virtual dispatch | Socket → SSLSocket |
| RAII | Socket destructor, SSLSocket destructor, lock_guard |
| Thread pool | threadpool/ThreadPool |
std::mutex + std::condition_variable |
ThreadPool work queue |
std::mutex + std::lock_guard |
RateLimiter |
std::atomic |
ThreadPool::_stop |
| Move semantics | Socket(Socket&&), SSLSocket(SSLSocket&&), lambda captures |
shared_ptr |
Socket lifetime across threads |
SO_RCVTIMEO |
Keep-Alive timeout |
| Hash map routing | Router::_handlers_map |
| HTTP/1.1 parsing | HttpParser |
| Chain of Responsibility | MiddlewarePipeline |
| Recursive lambda | MiddlewarePipeline::execute |
std::chrono::steady_clock |
Logger middleware timing |
| MIME type detection | Static file handler |
setsockopt |
SO_REUSEADDR, SO_RCVTIMEO |
- TCP Server + Thread Pool
- HTTP Parser + Router
- Middleware Pipeline
- Logger / CORS / Rate Limiter middlewares
- Static File Server
- Keep-Alive with timeout (
SO_RCVTIMEO) - TLS/HTTPS via OpenSSL
-
epollnon-blocking I/O - Benchmarking vs nginx with
wrk
MIT