serve some html to the browser
so my idea is pretty simple, lets try to build an http server
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 be able to "hear" what the thing is receiving. So to do it
forever we can execute .incoming() to pick up on the incoming streams being
attached to the socket
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 passing it to a buffer to be able to read it line by line right now we only care about the first line of the response because it has the method, route it's requesting & the protocol. I will use that later 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(())
}We will leave routing for tomorrow for now I will only serve local index.html
for that I'm going to set path that strips the absolute root first.
We can verify that it is a file and they client is making a GET request because
I felt like it. Then we check the file, if there is no issues we output the html.
We will need to check for the file type in the future for time being it is being
ignored. The response need at least the payload size, it's content type and the header
should show the estatus with the protocol. At least that's what I've found out so far.
Then we just 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}")
}
}
}
}