| use std::{io, collections::HashMap, env::args, os::unix::fs::MetadataExt}; |
| use bytes::BytesMut; |
| use tokio::{net::{TcpListener, TcpStream}, io::{AsyncReadExt, AsyncWriteExt}, fs::File}; |
| |
| |
| use itertools::Itertools; |
| use nom::AsChar; |
| |
| #[derive(Debug)] |
| enum BodyTypes { |
| File(File), |
| String(String) |
| } |
| |
| async fn process_socket(mut stream: TcpStream) -> Result<(), &'static str> { |
| println!("accepted new connection"); |
| let mut request: HashMap<String, String> = HashMap::new(); |
| let mut line_num: usize = 0; |
| loop { |
| let mut line = String::default(); |
| |
| loop { |
| let mut buf: [u8; 1] = [0; 1]; |
| stream.read_exact(&mut buf).await.unwrap(); |
| |
| let character = buf[0].as_char(); |
| if character == '\n' { |
| break |
| } else if character != '\r' { |
| line.push(character); |
| } |
| } |
| |
| if line_num == 0 { |
| let mut splits = line.split(' '); |
| request.insert("method".to_owned(), splits.next().unwrap().to_owned()); |
| request.insert("path".to_owned(), splits.next().unwrap().to_owned()); |
| } else { |
| let mut splits = line.split(":"); |
| let key = splits.next().unwrap(); |
| let value = splits.next().unwrap_or_default().trim(); |
| request.insert(key.to_owned(), value.to_owned()); |
| } |
| |
| if request.get("path").unwrap() != "/user-agent" || request.contains_key("User-Agent") { break }; |
| line_num += 1; |
| } |
| |
| let mut path = request.get("path").unwrap().split("/"); |
| |
| let response_status: String; |
| let mut response_headers: Vec<String> = vec![]; |
| let response_body: BodyTypes; |
| |
| path.next().unwrap(); |
| |
| match path.next().unwrap() { |
| "" => { |
| response_status = "200 OK".into(); |
| response_body = BodyTypes::String("".into()); |
| }, |
| "echo" => { |
| response_status = "200 OK".into(); |
| response_body = BodyTypes::String(path.join("/")); |
| response_headers.push("Content-Type: text/plain".into()); |
| }, |
| "user-agent" => { |
| response_status = "200 OK".into(); |
| response_body = BodyTypes::String(request.get("User-Agent").unwrap().into()); |
| response_headers.push("Content-Type: text/plain".into()); |
| }, |
| "files" => { |
| let file_name = path.join("/"); |
| let args: Vec<String> = args().collect(); |
| let dir_arg = args.iter().find_position(|item| **item == "--directory"); |
| let file_path = if dir_arg.is_some() { |
| args[dir_arg.unwrap().0+1].to_owned() + "/" + file_name.as_str() |
| } else { |
| return Err("No directory Given".into()) |
| }; |
| let maybe_file = File::open(file_path).await; |
| |
| match maybe_file { |
| Ok(file) => { |
| response_status = "200 OK".into(); |
| response_body = BodyTypes::File(file); |
| response_headers.push("Content-Type: application/octet-stream".into()); |
| }, |
| Err(_) => { |
| response_status = "404 Not Found".into(); |
| response_body = BodyTypes::String("".into()); |
| } |
| } |
| }, |
| _ => { |
| response_status = "404 Not Found".into(); |
| response_body = BodyTypes::String("".into()); |
| } |
| } |
| |
| match response_body { |
| BodyTypes::String(ref content) => { |
| response_headers.push(format!("Content-Length: {}", content.len())); |
| }, |
| BodyTypes::File(ref file) => { |
| response_headers.push(format!("Content-Length: {}", file.metadata().await.unwrap().size())); |
| } |
| } |
| |
| stream.write(format!("HTTP/1.1 {response_status}\r\n{}\r\n\r\n", response_headers.join("\r\n")).as_bytes()).await.unwrap(); |
| |
| match response_body { |
| BodyTypes::String(ref content) => { |
| stream.write(content.as_bytes()).await.unwrap(); |
| }, |
| BodyTypes::File(mut file) => { |
| loop { |
| let mut buf = BytesMut::with_capacity(1024); |
| |
| let read = file.read_buf(&mut buf).await.unwrap(); |
| |
| if read == 0 { |
| break; |
| } |
| |
| stream.write(&mut buf).await.unwrap(); |
| } |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| #[tokio::main] |
| async fn main() -> io::Result<()> { |
| let listener = TcpListener::bind("127.0.0.1:4221").await?; |
| let mut futures = vec![]; |
| |
| loop { |
| let (socket, _) = listener.accept().await?; |
| futures.push(tokio::spawn(process_socket(socket))); |
| } |
| } |