Back to Projects

webserv

an http server, from scratch with 0 dependencies!

  • C++
  • Makefile

Overview

webserv is a toy HTTP/1.0 and HTTP/1.1 server for Linux, written in C++98 with no external libraries. I built it to explore what sits underneath a web framework: parsing a byte stream, managing connections, mapping requests to files, and producing correct HTTP responses. It uses one non-blocking epoll event loop and an nginx-inspired configuration file, and can host multiple virtual servers, serve static sites, accept uploads, delete resources, redirect requests, and execute CGI programs.

Visit the repository for source and setup instructions.

Collaborators

webserv was a collaborative project built with:

What I Built

My work focused on the request-handling path: turning bytes received from a client into a routed request and, ultimately, a complete HTTP response.

Details

Continuity despite unpredictable input

TCP is a byte stream: one recv() may contain half a request line, several headers, or part of the next request. The parser therefore keeps its phase and per-request progress between reads instead of assuming that a whole request arrives at once. It advances only over bytes it has successfully understood, reports back the number of bytes that were consumed so that the ring buffer can get rid of the already consumed data in the connection buffer then resumes at the same point when more data becomes available. This also makes HTTP keep-alive possible: once a response is finished, the context is reset for the next request without losing unread bytes already buffered from the connection.

Non-blocking work as a state machine

The server does no waiting inside request handling. Socket I/O is performed only after epoll signals that a descriptor is readable or writable, while parsing, routing, and response generation move the connection through explicit read, process, write, and close states. A handler that still needs a request body yields control back to the event loop rather than blocking another connection. Parser and CGI timeouts provide an escape hatch for clients or programs that stop making progress.

Efficient, bounded buffering

Input is consumed from a reusable connection buffer, avoiding a new allocation for every network read. Small request bodies remain in memory, while larger bodies spill into a temporary file and are exposed to handlers through the same reader interface. Responses use the same idea: generated pages can be kept in memory, but static files are streamed. The result is predictable memory use even when serving files or accepting uploads that are much larger than the socket buffer.

Validate before touching the filesystem

Routing is more than matching a URI to a directory. The request target is percent-decoded and normalized before a route is selected; malformed escapes, attempts to walk above the root, and symlinks that resolve outside it are rejected. Method, body-framing, body-size, and upload permissions are checked before a handler accesses a resource. Keeping these decisions in one dispatch step makes the individual handlers small and gives errors a consistent HTTP response.

Responses are protocol messages, not just output

Each handler builds a complete HTTP response: a status code, headers, and a body whose framing agrees with the request's HTTP version and connection preferences. Static resources receive a MIME type, Content-Length, and modification time; redirects carry a Location; and keep-alive is negotiated through Connection rather than assumed. Internal failures are mapped to the appropriate client-visible status—such as 404, 403, 405, 413, or 504—so a malformed request or unavailable resource produces a useful, standards-shaped response instead of a dropped connection.

Notes

Build with make, then run ./webserv [configuration-file]. This is a learning project, not a production server: it is intentionally limited to C++98 and POSIX/Linux facilities—no Boost or other HTTP libraries—so parsing, buffering, response framing, and file handling are all implemented in the repository. The connection loop enforces request and body-progress timeouts and caps the number of requests served on one connection, which keeps keep-alive clients from holding resources forever.

Sources / Further Reading