use std::{collections::HashMap, io::{BufRead, BufReader, Result, Write}, net::{SocketAddr, TcpListener, TcpStream}, thread, time::Duration}; use chrono::Local; use crate::ThreadPool; pub enum StatusCode { OK, NotFound, // InternalServerError, // ImATeapot, } pub struct Request<'a> { stream: &'a TcpStream, path: &'a str, method: &'a str, version: &'a str, headers: HashMap<&'a str, &'a str>, body: Option, } impl<'a> Request<'a> { pub fn new(stream: &'a TcpStream, lines: &'a Vec) -> Result> { let request_line = lines[0].as_str(); let request_line_split: Vec<&str> = request_line.split(" ").collect(); let method = request_line_split[0]; let path = request_line_split[1]; let version = request_line_split[2]; let mut headers: HashMap<&'a str, &'a str> = HashMap::new(); if lines.len() > 1 { let mut i: usize = 1; loop { if i >= lines.len() { break; } let line = &lines[i]; if line.len() == 0 || !line.contains(":") { break; } let (name, value) = line.split_once(":").unwrap(); headers.insert(name, value.trim()); i += 1; } } let mut body: Option = None; if lines.len() > headers.len() + 2 && (method == "POST" || method == "PUT") && lines[headers.len() + 1] == "\r\n" { body = Some(lines[headers.len() + 2..].join("\n")); } Ok(Request { stream, path, method, version, headers, body, }) } pub fn address(&self) -> Result { self.stream.peer_addr() } pub fn path(&self) -> &'a str { self.path } pub fn method(&self) -> &'a str { self.method } pub fn version(&self) -> &'a str { self.version } pub fn body(&self) -> &Option { &self.body } pub fn headers(&self) -> &HashMap<&'a str, &'a str> { &self.headers } } pub struct Response<'a> { stream: &'a TcpStream, status: StatusCode, headers: HashMap<&'a str, String>, body: Option, } impl<'a> Response<'a> { pub fn new(stream: &'a TcpStream) -> Response<'a> { Response { stream, status: StatusCode::OK, headers: HashMap::from([ ("Server", "mcstatusface".to_string()), ("Content-Type", "text/plain".to_string()), ]), body: None, } } pub fn status(&mut self, status: StatusCode) { self.status = status; } pub fn headers(&self) -> &HashMap<&'a str, String> { &self.headers } pub fn set_header(&mut self, name: &'a str, value: String) { self.headers.insert(name, value); } pub fn body(&mut self, body: String) { self.body = Some(body); } pub fn send(&mut self) -> Result { let mut len: usize = 0; let code = match self.status { StatusCode::OK => 200, StatusCode::NotFound => 404, // StatusCode::ImATeapot => 418, // StatusCode::InternalServerError => 500, }; let reason = match self.status { StatusCode::OK => "OK", StatusCode::NotFound => "Not Found", // StatusCode::ImATeapot => "I'm a teapot", // StatusCode::InternalServerError => "Internal Server Error", }; len += self.stream.write(format!("HTTP/1.1 {} {}\r\n", code, reason).as_bytes()).unwrap(); let mut content_length: usize = 0; if self.body.is_some() { content_length = self.body.as_ref().unwrap().len(); } self.set_header("Content-Length", content_length.to_string()); for (name, value) in &self.headers { len += self.stream.write(format!("{name}: {value}\r\n").as_bytes()).unwrap() } if self.body.is_some() { len += self.stream.write("\r\n".as_bytes()).unwrap(); len += self.stream.write(self.body.as_ref().unwrap().as_bytes()).unwrap(); } Ok(len) } } pub struct HttpServer { address: String, port: u16, max_connections: usize, } impl HttpServer { pub fn new(address: &str) -> HttpServer { HttpServer { address: address.to_string(), port: 8080, max_connections: 16, } } pub fn start(&self) -> Result<()> { let pool = ThreadPool::new(self.max_connections); let listener = TcpListener::bind(format!("{}:{}", self.address, self.port)).expect("Failed to bind to port"); println!("Now listening on {}:{}", self.address, self.port); for stream in listener.incoming() { match stream { Ok(stream) => { pool.execute(move || { HttpServer::handle_client(&stream); }); } Err(e) => { eprintln!("Failed to handle incoming connection: {e}"); } } } Ok(()) } fn handle_client(stream: &TcpStream) { let buf_reader = BufReader::new(stream); let http_request: Vec = buf_reader .lines() .map(|result| result.unwrap()) .take_while(|line| !line.is_empty()) .collect(); let request = Request::new(stream, &http_request); if request.is_err() { eprintln!("Failed to process request: {}", request.err().unwrap()); return; } let request = request.unwrap(); let response = Response::new(stream); let start_date = Local::now(); match HttpServer::handle_request(&request, response) { Ok(_) => { let end_date = Local::now(); println!( "[{}] {} {} {} - {}ms - {}", start_date.format("%Y-%m-%d %H:%M:%S"), request.method(), request.path(), request.version(), (end_date - start_date).num_milliseconds(), request.address().unwrap().ip(), ); } Err(e) => { eprintln!("Failed to handle request: {e}") } } } fn handle_request(request: &Request, mut response: Response) -> Result { response.status(StatusCode::OK); response.set_header("x-powered-by", "GIRL FUEL".to_string()); if request.method != "GET" { response.status(StatusCode::NotFound); return response.send() } response.status(StatusCode::OK); response.set_header("Content-Type", "text/html".to_string()); response.body(r#" hello world!~

it works!!

"#.to_string()); response.send() } }