Skip to content
012024· Systems · C++

Tez

A from-scratch HTTP/1.1 server in C++17 — parsing, routing, threading and caching written by hand, on a Boost.Asio I/O core.

C++17Boost.Asiothread poolHTTP/1.1Docker
~782
lines of C++
core src/ (~904 with headers)
v1.0.0
tagged release
CI green on main
523
Docker Hub pulls
image ramogh2404/tez
~10.9k
req/s, localhost
ApacheBench smoke test only — -c10, n=100; not a sustained load test
01

The problem

Most "I built a web server" projects either wrap a framework that does all the real work, or stop at a toy that handles one request and exits. Tez sets a harder bar: implement the full HTTP/1.1 request lifecycle — parse, route, respond — by hand, with persistent connections, a real concurrency model and caching, while still standing on a production-grade async I/O foundation rather than reinventing the event loop.

02

The approach

Tez delegates only the socket and event layer to Boost.Asio (io_context, tcp::acceptor, async_accept, async signal handling) and writes everything above it by hand. Requests are parsed manually with Content-Length body handling; routing goes through a JSON-configured table (nlohmann/json) or a static file server. The HTTP/1.1 response — Date, Server, Connection, Keep-Alive, Content-Length — is assembled by hand. Concurrency runs on a hand-rolled fixed-size thread pool (std::vector<std::thread> + std::queue guarded by a mutex/condition_variable, a templated enqueue() returning std::future via std::packaged_task), and a dual LRU layer (100-entry response cache, 50-entry file cache, 60s TTL) sits behind mutex guards. GoogleTest covers the router, middleware, file server and response builder.

03

Key decisions

The load-bearing decision is the boundary: build on Boost.Asio for sockets and events instead of hand-coding an epoll/kqueue loop, while hand-writing HTTP parsing, routing, response building, the thread pool and caching. That keeps the educational surface where it's defensible — protocol and concurrency mechanics I can explain line by line — without sinking the budget into an event loop Boost already does correctly. A second deliberate choice is error_code over exceptions on the hot path, with RAII for cleanup: the ThreadPool destructor joins every worker, sockets are make_shared and lifetime-managed, so the fast path stays allocation- and exception-light.

04

Outcome

Tez is a public, MIT-licensed repo with a tagged v1.0.0 release and a green CI/CD pipeline. It implements GET/POST/PUT/DELETE, keep-alive (timeout 5s, max 1000 requests/connection), static serving with path-traversal protection via std::filesystem::weakly_canonical, and enforced limits (10 MB body, 8 KB header). On measured performance the honest scope is a localhost smoke test: the committed ApacheBench files ran only 100 requests at concurrency 10 and show ~10,897 req/s on the cached GET route with sub-millisecond mean latency — but at that scale every percentile reports 1 ms, so these are smoke-test figures, not sustained-load numbers, and are presented as such.

Request flow
Client
Library — not hand-written
Boost.Asio I/O
io_context · async_accept
Hand-written in C++17
Request parser
manual HTTP/1.1 · Content-Length
Thread pool
std::thread + queue + CV → future
Router
Static files
/static/* · traversal guard
Endpoints
/health · /echo · /api/data
LRU cache
resp 100 · file 50 · 60s TTL
Response builder
manual headers · keep-alive
Client

Everything except the Boost.Asio edge is hand-written. Committed benchmarks are an ApacheBench smoke test (-c10, n=100, localhost) — not a sustained load test.