A small, work-in-progress HTTP server written in C for exploring networking, concurrency, memory management, and POSIX system calls. The implementation is intentionally close to the operating system and is not production-ready.
The executable currently implements the first part of an HTTP server:
- Creates an IPv4 TCP socket, enables address and port reuse, binds to
0.0.0.0:8080, and listens with a backlog of 25. - Uses
epollto wait for incoming connections and readable client sockets. - Accepts clients and configures their sockets as non-blocking.
- Reads requests incrementally into an 8 KiB buffer.
- Parses an HTTP request line and up to 100 headers, including requests split across multiple reads.
- Looks up parsed request paths in the configured router, invokes the matching handler, and serializes the response.
- Writes the response back without blocking the event loop, switching the client
to
EPOLLOUTand closing the connection once every byte is sent. - Hands routes registered as threaded to a fixed-size worker pool; workers build the response and signal completion back to the event loop over a pipe.
The router is initialized with a /healthcheck route and supports route
registration, exact path lookup, and handler invocation. Unmatched paths return
404 Not Found with a JSON body, and a full worker queue returns 503 Service Unavailable. Response-building helpers are used by the executable, and the
thread pool is wired into the listener. The test suite covers router lookup,
request parsing, response construction and sending, server setup, thread-pool
task execution, logging output, and end-to-end listener tests that start the
server and speak HTTP over a socket.
The server is Linux-specific because it uses epoll. It requires GCC or
another GNU C17 compiler, GNU Make, and POSIX threads.
make
./bin/server.outIn another terminal, send a request with curl:
curl -v http://127.0.0.1:8080/healthcheckThe server logs the request, returns a JSON response, and closes the
connection. Stop it with Ctrl-C.
The port is currently fixed at 8080 in src/main.c; there is no command-line
configuration.
make # build bin/server.out
make debug # build debug/server.out with symbols and no optimization
make all # build both normal and debug executables
make run # build and run bin/server.out
make test # build and run all unit test suites
make clean # remove all generated files
make clean-bin
make clean-debugThe Makefile uses GNU C17, strict warnings treated as errors, automatic
dependency generation, and pthread support. Generated binaries, object
files, and dependency files are ignored by Git.
The parser currently uses fixed-size structures defined in
include/http/http.h:
| Item | Limit |
|---|---|
| Request buffer | 8192 bytes |
| Request method | 7 characters plus the terminator |
| Request target | 2047 characters plus the terminator |
| Protocol name | 15 characters plus the terminator |
| Header count | 100 |
| Header name | 63 characters plus the terminator |
| Header value | 1023 characters plus the terminator |
Request bodies, request Content-Length handling, keep-alive behavior,
graceful shutdown, and complete malformed-request handling are not implemented
yet.
| Path | Purpose |
|---|---|
src/main.c |
Starts the server on port 8080 |
src/server/server.c |
TCP socket setup, binding, and listening |
src/server/listener.c |
epoll loop, client acceptance, request parsing, response writing, and threaded-route dispatch |
src/http/parser.c |
Buffered request-line and header parsing |
src/http/response.c |
Response construction, JSON bodies, and sending helpers |
src/routes/router.c |
Route registration, exact path lookup, and handler invocation |
src/threadpool/threadpool.c |
Worker threads and bounded task queue used for threaded routes |
src/log/log.c |
Logging and errno helpers |
include/ |
Public interfaces and protocol data structures |
tests/ |
Assertion-based unit tests for routing, parsing, responses, server setup, thread-pool tasks, logging, and end-to-end listener behavior |
Makefile |
Build, run, test, debug, and cleanup targets |
- Treating TCP as a byte stream rather than assuming one
readis one request - Buffering incomplete input and finding HTTP CRLF line boundaries
- Converting untrusted bytes into bounded C structures
- Implementing a producer/consumer queue with
pthreadsynchronization - Managing ownership and lifetime across sockets, buffers, responses, and tasks
- Serializing structured data back into bytes for a socket
Likely next steps are to handle request bodies and Content-Length, support
keep-alive connection reuse, and improve graceful shutdown and cleanup paths.