Systems12 min read
Writing a non-blocking HTTP server
There is a particular kind of clarity that comes from re-implementing something most developers take for granted. For me, that thing was an HTTP/1.1 server in C++ — and the lessons stuck harder than any framework tutorial ever has.
The shape of the problem
Before any code, the problem looks deceptively small. Take bytes off a socket; assemble them into requests; produce responses. The protocol RFC is fewer than a hundred pages. How hard could it be?
Hard enough. The first thing you discover is that HTTP isn't a request-response protocol — it's a stream protocol that we interpret as request-response. Bytes arrive when they arrive. They might be a quarter of a header. They might be three pipelined requests at once. The server's job is to be a patient state machine that never assumes.
Choosing an event loop
I went with `epoll` on Linux. Not because it's the fastest — though it is — but because it forces you to think clearly about who owns what state. Every connection is a struct. Every read is a partial event. There is no thread to hide your sins behind.
int epfd = epoll_create1(0);
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);Edge-triggered mode is unforgiving. If you read once and stop, you'll miss the rest of the buffer and your client will hang. The mantra becomes: always drain.
A non-blocking server is mostly the practice of refusing to wait.
What I'd do differently
I tried to be clever about chunked transfer encoding too early. The grown-up move is to ship a working server with `Content-Length` first, then add chunking once the rest of the surface is stable. Premature generalization is the enemy of finishing.
- Build the simplest version end-to-end before optimizing any part
- Write tests against `curl` and a real browser early — not just unit tests
- When in doubt, log every state transition; remove later
What stays with me
After all of it, the part I think about most isn't the algorithm. It's that a webserver is a deeply social piece of software — it has to cooperate with browsers and proxies and CDNs that have their own opinions. Reading the protocol is one thing. Negotiating with reality is another.