HTTP/1.0 Web Server from Scratch
Build a web server in C from scratch, using only the operating system's socket functions and no HTTP library. It listens for connections, reads the raw request text, finds the file that was asked for, and sends back a proper HTTP response with the right headers, then grows from handling one client at a time to serving many by starting a child process for each connection with fork(). It returns correct errors (400, 404, 405 and 500), blocks attempts to escape the web folder with ../, and logs every request. Run it only on your own machine. You'll learn TCP sockets (socket, bind, listen and accept), the HTTP protocol, parsing untrusted input safely in C, file handling, processes and signals, and testing a server.
- Estimate
- ~12.5h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
sys/socket.h · fork() · sys/stat.h
Project roadmap
- 01
Open a listening socket
~1.5hWrite server.c that creates a TCP socket, sets SO_REUSEADDR, binds to a port given as an argument (on 127.0.0.1 by default), and listens. Accept one connection at a time, write a fixed Hello response, and close. Check every system call's return value and print errors with perror. Done when: curl http://localhost:8080 prints the fixed response.
- 02
Parse the request
~2.5hRead the request into a fixed-size buffer until the blank line that ends the headers (\r\n\r\n), handling requests that arrive in several pieces and rejecting headers larger than the buffer with 431 or 400. Parse the request line into the method, path and version with careful bounds checks, never trusting the input. Accept GET and HEAD and answer others with 405. Done when: valid requests are parsed, and malformed or oversized ones get the right error.
- 03
Serve files
~2.5hDecode percent-encoded characters in the path, map / to index.html, and build the file path under the document root. Use realpath and check the result still starts with the document root, returning 403 otherwise. Open the file, get its size with fstat, and send the status line, Content-Type (from the extension), Content-Length and a blank line, then the file in chunks, looping until every byte is written. Done when: a browser can view a small website with CSS and images served by your server.
- 04
Serve many clients with fork
~2hAfter accept, fork a child process that handles the connection and exits, while the parent closes its copy of the connection and keeps accepting. Reap finished children with a SIGCHLD handler calling waitpid in a loop with WNOHANG, so no zombie processes pile up. Set a read timeout (SO_RCVTIMEO) so a slow client can't hold a process forever. Done when: several clients are served at the same time, and ps shows no zombie processes.
- 05
Return proper errors
~1hSend small HTML error pages with the right status for 400, 403, 404, 405 and 500, including a correct Content-Length. Make sure errors in the child never crash the parent. Done when: each error case returns its status and page.
- 06
Log requests
~1hLog each request as one line: time, client IP (from inet_ntop), method, path, status and bytes sent, appended to a log file opened with O_APPEND so lines from different children don't mix. Done when: every request appears in the log with the right status.
- 07
Test it
~2hWrite a test script using curl and printf through nc: normal files, HEAD, missing files, traversal attempts (curl --path-as-is with /../ and %2e%2e), unsupported methods, a request sent in pieces, an oversized header, and many clients at once (for example with ab or a loop in the background). Build with -Wall -Wextra and run with AddressSanitizer. Done when: every test passes with no sanitizer errors.
Resources
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack