I Built a Small HTTP Server in C to Understand How the Web Actually Works
Source: Dev.to
What I Wanted (and What I Didn’t)
This project was never about building a production server.
I wasn’t trying to compete with Nginx. I only wanted answers to very basic questions:
- What does an HTTP request really look like?
- How does a server decide what file to return?
- What happens between
accept()and a browser rendering a page?
That’s it.
What This Server Actually Does
Here’s the exact scope — nothing more, nothing less:
- Listens on port 8080
- Handles GET requests only
- Serves static files (HTML, text, images)
- Uses POSIX threads (one client = one thread)
- Sends proper
Content-Typeheaders - Returns a plain 404 when a file doesn’t exist
It’s small enough to understand in one sitting.
Code Structure (Because One File Is a Nightmare)
I didn’t want everything dumped into main.c, so I split things up:
main.c→ socket setup,bind,listen, accept looprequest.c→ reads the request and handles the clienthttp.c→ builds HTTP responsesutils.c→ MIME types and URL decoding
Nothing fancy. Just separation of responsibility. This alone made debugging way less painful.
The First “Oh Damn” Moment
When I printed the raw HTTP request to the terminal for the first time, it finally clicked:
GET /index.html HTTP/1.1
Host: localhost:8080
That’s it. No objects. At that point, HTTP stopped feeling mysterious.
Parsing Without Overthinking
I didn’t write a full HTTP parser. I used a small regex to extract the file path after GET /, decoded it, and moved on.
Is it fragile? Yes. Over‑engineering would’ve killed the whole point of this project.
Building the Response Manually
Another underrated realization: a valid response is literally:
status line
headers
(empty line)
body
Once I hard‑coded that flow, everything made sense. When a file wasn’t found, I sent back a simple 404.
MIME Types: Small Detail, Big Lesson
At one point, HTML worked… but images didn’t. Turns out browsers really care about Content-Type. So I added a small extension → MIME mapping:
.html→text/html.jpg→image/jpeg.png→image/png
Suddenly, everything rendered correctly. That tiny bug taught me more than a dozen blog posts ever did.
Why C?
Because C doesn’t hide anything. If something works, it’s because you made it work. Writing this in C forced me to:
- understand sockets properly
- manage memory carefully
- read actual system calls instead of abstractions
And honestly, it made me appreciate modern frameworks way more.
What This Server Is Bad At (On Purpose)
Let’s be clear — this server does not handle:
POSTrequests- HTTPS
- Keep‑alive connections
- Security
- Real‑world traffic
And that’s fine. This was a learning project, not a startup.
Final Thoughts
If you’ve never built a server without a framework, I think you should try it once. Not to replace your tools, and you don’t need to finish it. For me, this project removed a lot of mental fog around HTTP. And that alone made it worth it.
Thanks for reading 🙌