diff --git a/src/main.rs b/src/main.rs index a961913..ab7b6f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,72 +1,82 @@ // #![feature(if_let_guard)] -use std::{ collections::HashMap, io::{ BufRead, BufReader, Write }, net::TcpListener }; +use std::collections::HashMap; use anyhow::Result; +use tokio::{ + io::{ AsyncBufReadExt, AsyncWriteExt, BufReader, Lines }, + net::{ TcpListener, TcpStream } +}; + mod utils; use utils::*; -fn main() -> Result<()> { - let listener = TcpListener::bind("127.0.0.1:4221").unwrap(); +#[tokio::main] +async fn main() -> Result<()> { + let listener = TcpListener::bind("127.0.0.1:4221").await.unwrap(); - for stream in listener.incoming() { - match stream { - Ok(mut stream) => { - let buf_reader = BufReader::new(&mut stream); - - let mut data = buf_reader - .lines() - .map(|result| result.unwrap()) - .take_while(|line| !line.is_empty()); - - let (_method, path, _ver) = { - let start_line = data.next().ok_or(E::InvalidRequest)?; // should be 500; - let mut parts = start_line.split_whitespace().map(ToOwned::to_owned); - let method = parts.next().ok_or(E::InvalidRequest)?; - let path = parts.next().ok_or(E::InvalidRequest)?; - let ver = parts.next().ok_or(E::InvalidRequest)?; - - (method, path, ver) - }; - - let headers = Headers::parse(data); - - - let response = match path.trim_start_matches("/") { - "" => Response::Empty, - "user-agent" => Response::TextPlain(headers.get("User-Agent")), - // p if let Some(echo) = p.strip_prefix("/echo/") => Response::TextPlain(echo), // a nicer way to do that, not available in stable yet - p if p.starts_with("echo/") => Response::TextPlain(p.trim_start_matches("echo/")), - _ => Response::_404, - }; - - println!("accepted new connection"); - - let _ = stream.write(response.build().as_bytes()); - let _ = stream.flush(); - - } - Err(e) => { - println!("error: {}", e); - } - } + loop { + let Ok((socket, info)) = listener.accept().await else { continue; } ; + println!("Connection from {:?} accepted, processing…", info); + let _ = process(socket).await; } +} + + +async fn process (mut stream: TcpStream) -> Result<()> { + let buf_reader = BufReader::new(&mut stream); + + let mut data = buf_reader + .lines(); + + let (_method, path, _ver) = { + let start_line = data.next_line().await?.ok_or(E::InvalidRequest)?; // should be 500; + let mut parts = start_line.split_whitespace().map(ToOwned::to_owned); + let method = parts.next().ok_or(E::InvalidRequest)?; + let path = parts.next().ok_or(E::InvalidRequest)?; + let ver = parts.next().ok_or(E::InvalidRequest)?; + + (method, path, ver) + }; + + + let headers = Headers::parse(data.into_inner()).await; + + let response = match path.as_str() { + "/" => Response::Empty, + "/user-agent" => Response::TextPlain(headers.get("User-Agent")), + // p if let Some(echo) = p.strip_prefix("/echo/") => Response::TextPlain(echo), // a nicer way to do that, not available in stable yet + p if p.starts_with("/echo/") => Response::TextPlain(p.trim_start_matches("/echo/")), + _ => Response::_404, + }; + + println!("{:?}", response); + + let _ = stream.write_all(response.build().as_bytes()).await; + let _ = stream.flush().await; + Ok(()) } - +#[derive(Debug, Clone)] pub struct Headers (HashMap); impl Headers { - pub fn parse (lines: impl Iterator) -> Self { - Self(HashMap::from_iter(lines.filter_map(|line| - line.split_once(":") - .map(|(a, b)| ( - a.trim().to_lowercase(), - b.trim().to_owned() - )) - ))) + pub async fn parse (mut reader: BufReader<&'_ mut TcpStream>) -> Self { + let mut map = HashMap::new(); + let mut buf = String::new(); + while let Ok(_) = reader.read_line(&mut buf).await { + if let Some((k, v)) = buf.split_once(":") { + println!("{:?}", (k, v)); + map.insert(k.trim().to_lowercase(), v.trim().to_owned()); + } else { + break; + } + + buf.clear(); + } + Self(map) } pub fn get <'a> (&'a self, key: &str) -> &'a str {