A minimal HTTP/1.1 static file server in a single C file. It accepts TCP connections, parses GET request lines, maps URL paths to the filesystem, and streams file contents back with correct headers. A live access log prints every request with status code, byte count, and response time. Concurrent requests are handled via fork().
Part of the Corg-Labs collection of single-file C programs.
- TCP socket lifecycle:
socket→bind→listen→accept GETandHEADmethod support; 405 for everything else%xxURL decoding and directory-traversal (..) protection- Automatic
index.htmlserving for directory paths - MIME type table covering HTML, CSS, JS, JSON, PNG, JPEG, SVG, WASM, and more
fork()-per-connection concurrency; zombie reaping viaSIGCHLD
int lfd = socket(AF_INET, SOCK_STREAM, 0);
int yes = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons((uint16_t)port),
.sin_addr.s_addr = INADDR_ANY,
};
bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
listen(lfd, 64);SO_REUSEADDR lets the port be rebound immediately after restart.
pid_t pid = fork();
if (pid == 0) {
close(lfd); /* child doesn't need listener */
handle_client(cfd, root);
close(cfd);
exit(0);
}
close(cfd); /* parent closes its copy */SIGCHLD is caught by a handler that calls waitpid(-1, NULL, WNOHANG) to reap zombies.
char method[16], raw_path[1024], proto[16];
sscanf(req, "%15s %1023s %15s", method, raw_path, proto);URL percent-encoding is decoded: %xx hex sequences are converted to their byte values. Any path containing .. is rejected with 403.
static const mime_entry_t MIME_TABLE[] = {
{ "html", "text/html; charset=utf-8" },
{ "js", "application/javascript" },
{ "png", "image/png" },
{ NULL, "application/octet-stream" }, /* fallback */
};strrchr finds the file extension; the table is scanned with strcasecmp.
snprintf(hdr, sizeof(hdr),
"HTTP/1.1 200 OK\r\nContent-Type: %s\r\n"
"Content-Length: %lld\r\nConnection: close\r\n\r\n",
mime, (long long)st.st_size);
send_all(cfd, hdr, hlen);
char buf[8192];
while ((n = read(ffd, buf, sizeof(buf))) > 0)
send_all(cfd, buf, n);send_all loops until every byte is written, handling short writes.
gcc http.c -o http
./http # port 8080, serve current directory
./http 9090 /var/www # custom port and root
Open http://localhost:8080/ in a browser. The terminal shows a live access log.
- TCP socket lifecycle (
socket,bind,listen,accept) - HTTP/1.1 request and response format
fork()-per-connection concurrency and zombie reaping- URL percent-decoding and path sanitisation
- MIME type resolution from file extensions
- Reliable write loops for streaming file content
Standard C library + POSIX (sys/socket.h, sys/stat.h, sys/wait.h). No external libraries.