A multithreaded HTTP/1.1 server implemented in C, demonstrating systems programming, concurrency primitives, and low-level networking.
- Serves files from the working directory
- Returns:
- 200 OK — file exists
- 404 Not Found — file missing
- Creates or overwrites files
- Handles arbitrary binary data
- Uses Content-Length for body parsing
- Returns:
- 201 Created — new file
- 200 OK — overwrite
- Configurable with -t <num_threads>
- Listener thread accepts connections
- Worker threads pop requests from a thread-safe queue
- Allows concurrent GETs on the same file
- Ensures PUT gets exclusive access
- Prevents writer starvation
- Implemented using:
- pthread_mutex_t
- pthread_cond_t can_read
- pthread_cond_t can_write
- read_until() for safe header parsing
- read_n_bytes() + pass_n_bytes() for PUT bodies
- Clean socket setup and teardown
- Systems programming with C, POSIX sockets, and file I/O
- Concurrency and synchronization (mutexes, condition variables, rwlocks)
- Design and implementation of a multithreaded server
- HTTP request parsing and robust error handling
- Reproducible integration testing workflows
- CI via GitHub Actions
- Experience with Linux tooling, Makefiles, and debugging
.
├── httpserver.c # Core HTTP logic (GET/PUT), parsing, routing
├── queue.c / queue.h # Thread-safe job queue for worker threads
├── rwlock.c / rwlock.h # Writer-priority reader–writer lock
├── helper_funcs.c/.h # Socket helpers + robust IO helpers
├── Makefile
└── tests/
└── integration/
├── test_cli.sh
├── test_endpoints.sh
├── test_put_handler.sh
└── test_concurrency.sh
└── unit/
├── test_queue.c
├── test_rwlock.cmake./httpservermake clean./httpserver 8080./httpserver -t 4 8080curl http://127.0.0.1:8080/somefileAll integration tests live in tests/integration/. Run all tests:
make testThere is a repeatable k6 benchmark wrapper that:
- starts the server in a temporary working directory
- creates one benchmark file via
PUT - runs concurrent
GETrequests against that file - cleans up the temporary server directory on exit
Run it with:
make benchmark-k6Common overrides:
PORT=8084 THREADS=8 VUS=100 DURATION=30s FILE_SIZE_BYTES=262144 make benchmark-k6Requirements:
k6installed and available onPATH
- Listener thread continuously accepts connections
- Each connection’s file descriptor is pushed into a bounded thread-safe queue
- Worker threads pop FDs and process requests concurrently
Each file name is associated with:
- A rwlock_t *
- A reference counter
- A heap-allocated URI string Worker behavior:
- GET → reader_lock(), read file, reader_unlock()
- PUT → writer_lock(), write/overwrite, writer_unlock()
- Ensures PUT operations get exclusive access.
- Allows multiple concurrent GET requests when no writer is waiting.
- Prevents writer starvation through explicit writer-priority logic.
- Add DELETE support
- Improve modularity and reduce duplication
- Expand test coverage for concurrency and edge cases
- Add graceful shutdown for worker threads
This project is licensed under the MIT License. See the LICENSE file for details.