I built this HTTP server from scratch to understand how HTTP actually works. No frameworks, no libraries (except std), just raw TCP streams and manual parsing. It's been really eye-opening to see what happens under the hood when you make a web request.
I've used HTTP for years but never really understood the details. Frameworks hide all the interesting stuff - how requests are parsed, how headers work, how responses are formatted. So I decided to build a server myself and figure it out.
Turns out HTTP is pretty straightforward once you break it down. You read bytes from a TCP stream, parse the request line (method, path, version), read headers until you hit a blank line, then read the body if there is one. Responses are just text formatted in a specific way.
This server handles the basics:
- Parsing HTTP requests from raw TCP streams
- Routing requests to handler functions
- A simple middleware system
- Serving static files
- Proper error handling with HTTP status codes
- Handling multiple connections with threads
Just need Rust installed. Then:
cargo runIt'll start on http://127.0.0.1:8080. Watch the console - it logs every request so you can see what's happening.
The interesting parts:
Request Parsing (src/http/request.rs): This was the trickiest part. You read the request line (like GET /hello HTTP/1.1), then keep reading lines until you hit a blank line (that's the end of headers), then read the body if Content-Length says there is one. Headers are normalized to lowercase for easy lookup.
Response Building (src/http/response.rs): Responses are just text. Status line, headers, blank line, then body. The format is strict - \r\n for line endings, specific header format, etc.
Router (src/router.rs): Simple hash map that matches method + path to a handler function. No fancy regex or wildcards - just exact matching to keep it clear.
Middleware (src/middleware.rs): Each middleware gets the request and a next function. You can modify the request, call next, then modify the response. The chain executes in order.
Server (src/server.rs): Listens on TCP, spawns a thread for each connection. Each thread parses the request, finds a route, runs middleware, and sends the response back.
The code is pretty straightforward - I tried to keep it simple so you can actually read through it and understand what's happening.
Use curl or your browser. Here are some examples:
# Basic requests
curl http://127.0.0.1:8080/
curl http://127.0.0.1:8080/hello
curl http://127.0.0.1:8080/api/status
# POST with a body
curl -X POST -H "Content-Type: text/plain" -d "Hello World" http://127.0.0.1:8080/api/data
# PUT and DELETE
curl -X PUT -H "Content-Type: text/plain" -d "Updated" http://127.0.0.1:8080/api/data
curl -X DELETE http://127.0.0.1:8080/api/data
# Auth example (try without the header first, then with it)
curl http://127.0.0.1:8080/api/secret
curl -H "Authorization: Bearer secret-token" http://127.0.0.1:8080/api/secret
# Custom status code (418 I'm a teapot)
curl http://127.0.0.1:8080/api/customOr just open http://127.0.0.1:8080/ in your browser. Check the server console to see the request details being logged.
There are a few example endpoints set up in main.rs:
GET /- Returns some HTMLGET /hello- Plain text responseGET /api/status- Just returns "Server is running"POST /api/data- Echoes back whatever you send in the body (returns 201)PUT /api/data- Returns 204 (no content, just status)DELETE /api/data- Also returns 204GET /api/custom- Returns 418 (I'm a teapot, because why not)GET /api/secret- Checks for an Authorization header, returns 401 if missingGET /maintenance- Returns 503
If a route doesn't match, it falls back to trying to serve a static file from the current directory. So you can drop a file in the project folder and access it directly.
src/
main.rs # Sets up routes and starts the server
server.rs # TCP listener, spawns threads, handles connections
router.rs # Maps (method, path) to handler functions
handlers.rs # Static file serving (with path traversal checks)
middleware.rs # Middleware chain - each one wraps the next
error.rs # Error types for parsing and IO
http/
request.rs # Parses HTTP from TCP stream
response.rs # Builds HTTP response strings
method.rs # GET, POST, etc.
status.rs # Status codes like 200, 404, 500
The server logs every request to stdout so you can watch what's happening. You'll see the method, path, version, user agent, and what response was sent back.
This is a learning project, not something you'd deploy. Missing features:
- Keep-alive connections (closes after each request)
- Chunked transfer encoding
- Query parameters or path variables
- Async/await (uses threads instead)
- HTTPS/TLS
- HTTP/2 or HTTP/3
I kept it simple on purpose. The code is straightforward and you can actually read through it and understand what's happening. If you want to learn more, the HTTP/1.1 RFC (RFC 7230) is the definitive source, though it's pretty dense. Wireshark is also great for seeing the raw packets.
If you want to extend it, good next steps would be adding query parameter parsing, keep-alive support, or switching to async. But honestly, just reading through the code and understanding how requests get parsed and responses get built is the main point.