Building a Single-Threaded Web Server
TCP Connection Handling
use std::net::TcpListener; let listener = TcpListener::bind("127.0.0.1:7878")?; for stream in listener.incoming() { let stream = stream?; handle_connection(stream); }
Similar to Node.js server.listen()
, but blocking by default.
HTTP Request Structure
GET / HTTP/1.1
Host: 127.0.0.1:7878
User-Agent: Mozilla/5.0...
[optional body]
- Method + URI + Version
- Headers until empty line
- Optional body
Basic Response
fn handle_connection(mut stream: TcpStream) { let mut buffer = [0; 1024]; stream.read(&mut buffer)?; let response = "HTTP/1.1 200 OK\r\n\r\nHello, World!"; stream.write(response.as_bytes())?; stream.flush()?; }
Serving Files
fn handle_connection(mut stream: TcpStream) { let mut buffer = [0; 1024]; stream.read(&mut buffer)?; let get = b"GET / HTTP/1.1\r\n"; let (status_line, filename) = if buffer.starts_with(get) { ("HTTP/1.1 200 OK", "hello.html") } else { ("HTTP/1.1 404 NOT FOUND", "404.html") }; let contents = fs::read_to_string(filename)?; let response = format!( "{}\r\nContent-Length: {}\r\n\r\n{}", status_line, contents.len(), contents ); stream.write(response.as_bytes())?; stream.flush()?; }
Performance Limitation
// Simulate slow endpoint if buffer.starts_with(b"GET /sleep HTTP/1.1\r\n") { thread::sleep(Duration::from_secs(5)); }
Problem: Single-threaded blocking - all requests wait for slow operations.
Node.js difference: Event loop handles I/O asynchronously on single thread, while this blocks the entire server.
Next: Thread pool for concurrent request handling.