A small, from-scratch HTTP/1.1 server built in C++ with Linux system calls. The goal is to learn the low-level pieces behind common servers like nginx: sockets, epoll, threads, and basic HTTP parsing. It is not production-ready, but it is a clear and honest implementation you can read end-to-end.
- Event-driven I/O with edge-triggered
epoll - A simple thread pool for blocking work (CGI, disk I/O)
- Zero-copy static file responses with
sendfile - Minimal HTTP/1.1 request parsing (GET, POST, DELETE)
- CGI execution with
fork,execve,pipe, anddup2 - A live dashboard that polls
/stats
- The main thread accepts connections and watches them with
epoll. - When a socket is ready, the request is parsed and routed.
- Blocking work is dispatched to a worker thread.
- Static files are served directly using
sendfile.
Linux (or WSL2) is required because of epoll and sendfile.
# Build
ulimit -n 65535
g++ server.cpp -o server -pthread -std=c++17
# Run
./serverOpen the UI:
/- UI page/stats- JSON stats for the dashboard/upload- POST endpoint for file upload demo/time- CGI demo (callstime_server.py)DELETE /uploaded_data.txt- delete demo file
The UI has a "Live Stress Test" button. It calls:
/trigger-stress?count=500
The server uses curl + xargs -P 100 to generate parallel requests from the server host itself. This avoids browser connection limits, so the dashboard counter jumps quickly.
Notes:
- The stress test is meant for local use only.
- Railway and similar platforms have rate limits. The UI caps stress-test options when deployed on Railway.
- No TLS/HTTPS
- No HTTP/2 or HTTP/3
- No virtual hosts
- No compression
- No access logs or rotation
And this http server is deployed in Railway.
flowchart TD
A["Clients (10k plus)"] -->|"TCP connection"| B["Main thread - epoll wait"]
B -->|"New connection?"| C["Accept and set non blocking"]
B -->|"Data ready?"| D["Remove from epoll and enqueue task"]
D --> E["Thread pool queue"]
E --> F["Worker thread 1"]
E --> G["Worker thread 2"]
E --> H["Worker thread 3"]
E --> I["Worker thread 4"]
F -->|"Process request"| J["Task type"]
J -->|"Static file"| K["Zero copy sendfile"]
J -->|"Dynamic content"| L["CGI fork exec"]
K --> M["Send response and close"]
L --> M
If you want to dig into the internals, start with server.cpp and HttpParser.h.
