Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 1 | use std::{io, collections::HashMap, env::args, os::unix::fs::MetadataExt}; |
| 2 | use bytes::BytesMut; |
| 3 | use tokio::{net::{TcpListener, TcpStream}, io::{AsyncReadExt, AsyncWriteExt}, fs::File}; |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 4 | |
codecrafters-bot | 6706158 | 2024-01-10 17:35:39 +0000 | [diff] [blame] | 5 | |
Samuel Shuert | 62c526f | 2024-01-10 15:30:45 -0500 | [diff] [blame] | 6 | use itertools::Itertools; |
Samuel Shuert | f2efffb | 2024-01-10 16:14:01 -0500 | [diff] [blame] | 7 | use nom::AsChar; |
Samuel Shuert | 62c526f | 2024-01-10 15:30:45 -0500 | [diff] [blame] | 8 | |
Samuel Shuert | 9c09d87 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 9 | #[derive(Debug)] |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 10 | enum BodyTypes { |
| 11 | File(File), |
| 12 | String(String) |
| 13 | } |
| 14 | |
| 15 | async fn process_socket(mut stream: TcpStream) -> Result<(), &'static str> { |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 16 | println!("accepted new connection"); |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 17 | let mut request: HashMap<String, String> = HashMap::new(); |
| 18 | let mut line_num: usize = 0; |
| 19 | loop { |
| 20 | let mut line = String::default(); |
Samuel Shuert | e4ee873 | 2024-01-10 14:42:09 -0500 | [diff] [blame] | 21 | |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 22 | loop { |
| 23 | let mut buf: [u8; 1] = [0; 1]; |
| 24 | stream.read_exact(&mut buf).await.unwrap(); |
Samuel Shuert | 3d880f7 | 2024-01-10 14:48:08 -0500 | [diff] [blame] | 25 | |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 26 | let character = buf[0].as_char(); |
| 27 | if character == '\n' { |
| 28 | break |
| 29 | } else if character != '\r' { |
| 30 | line.push(character); |
Samuel Shuert | f10dce9 | 2024-01-10 14:31:46 -0500 | [diff] [blame] | 31 | } |
| 32 | } |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 33 | |
| 34 | if line_num == 0 { |
| 35 | let mut splits = line.split(' '); |
| 36 | request.insert("method".to_owned(), splits.next().unwrap().to_owned()); |
| 37 | request.insert("path".to_owned(), splits.next().unwrap().to_owned()); |
| 38 | } else { |
Samuel Shuert | bdb676c | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 39 | let mut splits = line.split(":"); |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 40 | let key = splits.next().unwrap(); |
Samuel Shuert | b96d06c | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 41 | let value = splits.next().unwrap_or_default().trim(); |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 42 | request.insert(key.to_owned(), value.to_owned()); |
| 43 | } |
| 44 | |
Samuel Shuert | 5d78333 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 45 | if request.get("path").unwrap() != "/user-agent" || request.contains_key("User-Agent") { break }; |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 46 | line_num += 1; |
| 47 | } |
| 48 | |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 49 | let mut path = request.get("path").unwrap().split("/"); |
| 50 | |
| 51 | let response_status: String; |
| 52 | let mut response_headers: Vec<String> = vec![]; |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 53 | let response_body: BodyTypes; |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 54 | |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 55 | path.next().unwrap(); |
| 56 | |
| 57 | match path.next().unwrap() { |
| 58 | "" => { |
| 59 | response_status = "200 OK".into(); |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 60 | response_body = BodyTypes::String("".into()); |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 61 | }, |
| 62 | "echo" => { |
| 63 | response_status = "200 OK".into(); |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 64 | response_body = BodyTypes::String(path.join("/")); |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 65 | response_headers.push("Content-Type: text/plain".into()); |
| 66 | }, |
| 67 | "user-agent" => { |
| 68 | response_status = "200 OK".into(); |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 69 | response_body = BodyTypes::String(request.get("User-Agent").unwrap().into()); |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 70 | response_headers.push("Content-Type: text/plain".into()); |
| 71 | }, |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 72 | "files" => { |
| 73 | let file_name = path.join("/"); |
| 74 | let args: Vec<String> = args().collect(); |
Samuel Shuert | b809653 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 75 | let dir_arg = args.iter().find_position(|item| **item == "--directory"); |
| 76 | let file_path = if dir_arg.is_some() { |
| 77 | args[dir_arg.unwrap().0+1].to_owned() + "/" + file_name.as_str() |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 78 | } else { |
| 79 | return Err("No directory Given".into()) |
| 80 | }; |
| 81 | let maybe_file = File::open(file_path).await; |
| 82 | |
| 83 | match maybe_file { |
| 84 | Ok(file) => { |
| 85 | response_status = "200 OK".into(); |
| 86 | response_body = BodyTypes::File(file); |
Samuel Shuert | 9c09d87 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 87 | response_headers.push("Content-Type: application/octet-stream".into()); |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 88 | }, |
| 89 | Err(_) => { |
| 90 | response_status = "404 Not Found".into(); |
| 91 | response_body = BodyTypes::String("".into()); |
| 92 | } |
| 93 | } |
| 94 | }, |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 95 | _ => { |
| 96 | response_status = "404 Not Found".into(); |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 97 | response_body = BodyTypes::String("".into()); |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 98 | } |
| 99 | } |
| 100 | |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 101 | match response_body { |
| 102 | BodyTypes::String(ref content) => { |
| 103 | if content.len() > 0 { |
| 104 | response_headers.push(format!("Content-Length: {}", content.len())); |
| 105 | }; |
| 106 | }, |
| 107 | BodyTypes::File(ref file) => { |
| 108 | response_headers.push(format!("Content-Length: {}", file.metadata().await.unwrap().size())); |
| 109 | } |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 110 | } |
| 111 | |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 112 | stream.write(format!("HTTP/1.1 {response_status}\r\n{}\r\n\r\n", response_headers.join("\r\n")).as_bytes()).await.unwrap(); |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 113 | |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 114 | match response_body { |
| 115 | BodyTypes::String(ref content) => { |
| 116 | stream.write(content.as_bytes()).await.unwrap(); |
| 117 | }, |
| 118 | BodyTypes::File(mut file) => { |
| 119 | loop { |
| 120 | let mut buf = BytesMut::with_capacity(1024); |
| 121 | |
| 122 | let read = file.read_buf(&mut buf).await.unwrap(); |
| 123 | |
| 124 | if read == 0 { |
| 125 | break; |
| 126 | } |
| 127 | |
| 128 | stream.write(&mut buf).await.unwrap(); |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | Ok(()) |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 134 | } |
| 135 | |
| 136 | #[tokio::main] |
Samuel Shuert | 71aa136 | 2024-01-10 17:27:57 -0500 | [diff] [blame] | 137 | async fn main() -> io::Result<()> { |
Samuel Shuert | 2ecef06 | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 138 | let listener = TcpListener::bind("127.0.0.1:4221").await?; |
| 139 | let mut futures = vec![]; |
| 140 | |
| 141 | loop { |
| 142 | let (socket, _) = listener.accept().await?; |
Samuel Shuert | b933bdd | 2024-01-10 16:31:55 -0500 | [diff] [blame] | 143 | futures.push(tokio::spawn(process_socket(socket))); |
Samuel Shuert | f10dce9 | 2024-01-10 14:31:46 -0500 | [diff] [blame] | 144 | } |
codecrafters-bot | 6706158 | 2024-01-10 17:35:39 +0000 | [diff] [blame] | 145 | } |