serve some html to the browser
the goal: build an http server from scratch
first we need a listener that attaches to a socket. i'm using rust which obfuscate
the bare minimums. you can get a tcpstream to listen to by binding a host and port
let host = "127.0.0.1";
let port = "8080";
let listener = TcpListener::bind(format!("{host}:{port}")).unwrap();next we need to "hear" what the socket receives. .incoming() picks up on the incoming streams being attached to the socket, running forever
listener.incoming().for_each(|stream| match stream {
Ok(mut stream) => {
handle_connection(&stream).unwrap();
handle_response(&mut stream);
},
Err(e) => println!("> couldn't get client: {e:?}"),
});reading the stream is easier when passing it to a buffer. right now we only care about the first line of the response because it has the method, route, and protocol. that's enough to decide what to do with the request.
fn handle_connection(stream: &TcpStream) -> std::io::Result<()> {
let mut reader = BufReader::new(stream);
let mut buf = String::new();
println!("> receiving query...");
reader.read_line(&mut buf)?;
let parts = buf.split(" ").collect::<Vec<&str>>();
let method = parts[0];
let segment = parts[1];
println!("> method: {method}");
println!("> segment: {segment}");
println!("> end of message");
Ok(())
}routing comes later. for now i only serve a local index.html by stripping the absolute root from the path. the handler verifies it's a file and a GET request, then reads the file and outputs html. file type checking is ignored for now. the response needs at least the payload size, content type, and the status line with the protocol. then write and flush to the client.
fn handle_response(stream: &mut TcpStream, req: Request) {
let path = Path::new(&req.segment).strip_prefix("/").unwrap();
println!("> loading query: {:?}", path);
if path.is_file() && req.method == "GET" {
match fs::read_to_string(path) {
Ok(content) => {
let content_type = "text/html";
let content_len = content.len();
let response = format!(
"HTTP/1.1 200 OK\n\
Content-Type: {content_type}\n\
Content-Length: {content_len}\n\n\
{content}"
);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
Err(e) => {
let content = "Not found";
let content_type = "text/plain";
let content_len = content.len();
let response = format!(
"HTTP/1.1 404 ERROR\n\
Content-Type: {content_type}\n\
Content-Length: {content_len}\n\n\
{content}"
);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
println!("> {e}")
}
}
}
}
