Webserv
Completed (42 Curriculum)An RFC-compliant HTTP/1.1 web server engineered from scratch in C++ using nonblocking POSIX sockets and multiplexed I/O.
01The Systems Challenge
As part of the 1337 / 42 school curriculum, Webserv required building a fully functional HTTP/1.1 server from the ground up without using any external web libraries, frameworks, or modern C++ convenience abstractions.
The primary architectural challenge was ensuring the entire server process never blocked on any socket or file descriptor. A slow or malicious client transmitting one byte every few seconds must never stall other concurrent connections, degrade throughput, or exhaust server resources.
02Request Lifecycle & State Machine
The diagram below illustrates how connections flow through the single-threaded multiplexer.
+───────────────────────────────────────────────────────────────+
│ NONBLOCKING EVENT MULTIPLEXER │
│ POSIX poll() / select() loop │
+───────────────────────────────┬───────────────────────────────+
│
┌────────────────────┴───────────────────┐
▼ ▼
[POLLIN on Listen Socket] [POLLIN on Client Socket]
│ │
accept() nonblocking recv() nonblocking chunk
create ClientContext │
▼
+─────────────────────────────+
│ HTTP/1.1 STATE MACHINE │
│ - Parse Request Line │
│ - Parse Headers map │
│ - Handle chunked/transfer │
+──────────────┬──────────────+
│
▼
[Route & Virtual Host Match]
│
┌──────────────────────────────┴──────────────────────────────┐
│ │
▼ ▼
[Static File Request] [CGI Script Request]
│ │
Open file nonblocking fork() child process
Stream chunks to buffer dup2() stdin/stdout pipes
│ execve() script runner
│ │
└──────────────────────────────┬──────────────────────────────┘
│
▼
[POLLOUT on Client Socket]
send() buffered response
close / keep-alive timeout* Every step in the connection lifecycle is strictly nonblocking. Socket readiness drives state transitions without worker threads or blocking I/O calls.
03Architecture & Core Components
1. Multiplexed Event Loop
The server maintains an array of monitored file descriptors passed to poll(). Sockets are flagged with O_NONBLOCK via fcntl(). When an event fires, the loop dispatches to the corresponding socket context without spawning worker threads.
2. Deterministic State Machine Parser
Because network data arrives in arbitrary fragmentation, the HTTP parser operates as a resumable state machine:
- REQUEST_LINE: Extracts HTTP method (GET, POST, DELETE), URI, and protocol version.
- HEADERS: Parses key-value header mappings, validating Host headers and Content-Length.
- BODY / CHUNKED: Handles chunked transfer encoding (
Transfer-Encoding: chunked) by parsing hex chunk lengths until the terminating 0-chunk.
3. CGI Execution Pipeline
Dynamic content (e.g., Python or PHP scripts) is executed by creating child processes using fork() and execve(). File descriptors are remapped with dup2() to unidirectional POSIX pipes, allowing the parent server to stream request bodies to CGI stdin and nonblockingly read generated HTML from CGI stdout.
04Tradeoffs & Constraints
Writing in C++98 meant no std::unique_ptr, no move semantics, and no standard threading library. Resource management relied on strict RAII patterns to guarantee that open file descriptors, dynamically allocated connection buffers, and child processes were properly closed and waited on during timeout or disconnect events.
05Verification & Delivery Status
Webserv was thoroughly evaluated under peer review and automated stress-testing using tools like Siege to verify that the server does not leak memory or hang under sustained load. The full source code and configuration syntax are available on GitHub.