blob: 6e98efab7dc9d0a2d219cc84160b078e02e5ab2f [file] [log] [blame]
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());
}
}
println!("{:?}", response_body);
match response_body {
BodyTypes::String(ref content) => {
if content.len() > 0 {
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)));
}
}