This is a minimalist static web server written in C. I’m building it to practice and demonstrate systems-level programming (sockets, basic HTTP, file I/O, portability) while delivering a product that’s dead-simple to run for serving static websites.
- Listens on a TCP port (default: 8080)
- Parses an HTTP request line to determine the requested path
- Serves files from the repository (e.g.,
src/index.htmland others) - Writes a minimal HTTP response header with
Content-Type - Streams file contents efficiently to the client
- Sends basic error responses (400/404/500/413)
- Quick, dependency-free static hosting: run a single binary (or Docker container) and serve a folder of HTML/CSS/JS/images.
- Low overhead for local dev, demos, CI previews, or constrained environments where Node/Python stacks are heavy.
- Serve a static site (HTML/CSS/JS/images) from a document root
- Change the port and doc root easily
- Run it directly as a native binary, or via Docker for zero-setup
- Create a socket and bind to
PORT. listen()andaccept()incoming connections.- Read the HTTP request bytes into a buffer.
- Parse the path; default to
index.htmlwhen no file is provided. - Open the file, stat it, write response headers, then send the file body.
- Close the client socket and repeat.
src/
main.c // current single-file implementation
index.html // example content
include/ // header files will live here as I refactor
I’ll gradually abstract out the implementation from src/main.c into modules in src/ with matching headers in include/ (e.g., http.c/.h, server.c/.h, mime.c/.h, etc.).
From repo root:
gcc -Wall -Wextra -O2 -std=c11 src/main.c -o main
./main
Then visit http://localhost:8080 in a browser or:
curl -i http://localhost:8080
Environment variables (optional):
PORT=9090 ./main # change the port
DOC_ROOT=./src ./main # change the document root (future option)
When modules are split into multiple files, I’ll compile with the include directory:
gcc -I include -Wall -Wextra -O2 -std=c11 src/*.c -o webserver
./webserver
- Replace unsafe extension-to-int casting with safe string-based MIME detection.
- Harden extension parsing: null checks before
strlen, ensure proper null-termination. - Check return values for all
write()calls; handle partial writes. - Ensure correct socket is used for error responses in the request loop.
- Basic path normalization to prevent directory traversal (e.g.,
..).
- Introduce modules:
server.c/.h: socket setup, accept loophttp.c/.h: request parsing, response formattingmime.c/.h: extension → MIME lookupfile_handler.c/.h: file open/stat/send logic
- Add
include/for public headers and compile with-I include. - Add a
Makefilefor reliable builds.
- Add support for more HTTP methods (or explicitly enforce GET only).
- Normalize and sanitize paths (disallow
.., collapse//, limit path length). - Configurable document root and port (env vars or config file).
- Logging (access and error logs).
- Introduce a thread pool to handle requests concurrently (I’ll start with a fixed pool size and a lock-protected work queue).
- Make shared state (if any) thread-safe with mutexes.
- Graceful shutdown (signal handling).
- Sensible limits (max headers/line length, request timeout).
- More comprehensive error pages.
- Add simple integration tests (e.g., shell-based curl tests).
- Static analysis and sanitizers:
-fsanitize=address,undefinedin debug builds. - CI setup (optional) to build and run basic tests.
- I’ll prefer small, focused modules with clear interfaces in
include/. - I always check system call return values (
socket,bind,listen,accept,read,write,open,fstat, etc.). - I keep buffers sized conservatively and ensure explicit null-termination.
- I use
constwhere appropriate; keep functionsstaticif they’re private to a.cfile. - I optimize for clarity first; network I/O will dominate performance.
MIT (or your choice). Add a LICENSE file if needed.