From 4f9d09b58ae81e4e6144b85ce36ba424e319b779 Mon Sep 17 00:00:00 2001 From: omagdy7 Date: Mon, 3 Jun 2024 20:40:54 +0300 Subject: codecrafters submit [skip ci] --- src/http_types.rs | 77 ++++++++++++++++++++++ src/lib.rs | 3 + src/main.rs | 187 ++++-------------------------------------------------- src/request.rs | 43 +++++++++++++ src/response.rs | 40 ++++++++++++ 5 files changed, 176 insertions(+), 174 deletions(-) create mode 100644 src/http_types.rs create mode 100644 src/lib.rs create mode 100644 src/request.rs create mode 100644 src/response.rs diff --git a/src/http_types.rs b/src/http_types.rs new file mode 100644 index 0000000..a652e6c --- /dev/null +++ b/src/http_types.rs @@ -0,0 +1,77 @@ +use itertools::Itertools; +use std::collections::HashMap; + +pub enum StatusCode { + // 2xx Success + Ok, + + // 4xx Client Error + BadRequest, + NotFound, +} + +type Endpoint = String; +type Target = String; + +#[derive(Debug)] +pub enum HTTPMethod { + Get((Endpoint, Target)), + Post((Endpoint, Target)), + Put((Endpoint, Target)), +} + +impl From for String { + fn from(val: StatusCode) -> Self { + use StatusCode::*; + match val { + Ok => "200 OK".to_string(), + BadRequest => "400 Bad Request".to_string(), + NotFound => "404 Not Found".to_string(), + } + } +} + +impl From for String { + fn from(val: HTTPMethod) -> Self { + use HTTPMethod::*; + match val { + Get((endpoint, target)) => "GET".to_string() + &endpoint + &target, + Post((endpoint, target)) => "POST".to_string() + &endpoint + &target, + Put((endpoint, target)) => "PUT".to_string() + &endpoint + &target, + } + } +} + +#[derive(Debug)] +pub struct Headers(pub HashMap); + +impl From<&[&str]> for Headers { + fn from(value: &[&str]) -> Self { + let mut header_map = HashMap::new(); + for header in value.iter().filter(|val| !val.is_empty()) { + let (key, val) = header + .split_once(": ") + .expect("Should be splitable by :"); + header_map.insert(key.to_string(), val.to_string()); + } + Headers(header_map) + } +} + +impl From<&str> for HTTPMethod { + fn from(val: &str) -> Self { + use HTTPMethod::*; + let request_line = val.split(' ').collect_vec(); + let (method, info) = (request_line[0], request_line[1]); + let info = info.chars().skip(1).collect::() + &"/"; + let (endpoint, target) = info.split_once("/").expect("Should be splitable by /"); + match method { + "GET" => Get((endpoint.to_string(), target.to_string())), + "POST" => Post((endpoint.to_string(), target.to_string())), + _ => { + eprintln!("{method} Not Supported Yet"); + unreachable!() + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4667e1d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod http_types; +pub mod request; +pub mod response; diff --git a/src/main.rs b/src/main.rs index 2f61490..1ad6f26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,171 +1,13 @@ +#![allow(unused)] +use itertools::Itertools; use std::collections::HashMap; -use std::fmt::format; -use std::io::{Read, Write}; +use std::io::{self, Read, Write}; use std::net::{TcpListener, TcpStream}; -use std::str; +use std::{str, thread}; -use itertools::{join, Itertools}; - -enum StatusCode { - // 2xx Success - Ok, - // Created = 201, - // Accepted = 202, - // NonAuthoritative = 203, - // NoContent = 204, - // ResetContent = 205, - // PartialContent = 206, - - // 4xx Client Error - BadRequest, - NotFound, -} - -type Endpoint = String; -type Target = String; - -#[derive(Debug)] -enum HTTPMethod { - Get((Endpoint, Target)), - Post((Endpoint, Target)), - Put((Endpoint, Target)), -} - -impl From for String { - fn from(val: StatusCode) -> Self { - use StatusCode::*; - match val { - Ok => "200 OK".to_string(), - BadRequest => "400 Bad Request".to_string(), - NotFound => "404 Not Found".to_string(), - } - } -} - -impl From for String { - fn from(val: HTTPMethod) -> Self { - use HTTPMethod::*; - match val { - Get((endpoint, target)) => "GET".to_string() + &endpoint + &target, - Post((endpoint, target)) => "POST".to_string() + &endpoint + &target, - Put((endpoint, target)) => "PUT".to_string() + &endpoint + &target, - } - } -} - -#[derive(Debug)] -struct Headers(HashMap); - -impl From<&[&str]> for Headers { - fn from(value: &[&str]) -> Self { - let mut header_map = HashMap::new(); - for header in value.iter().filter(|val| !val.is_empty()) { - let (key, val) = header - .split_once(": ") - .expect("Should be splitable by :"); - header_map.insert(key.to_string(), val.to_string()); - } - Headers(header_map) - } -} - -impl From<&str> for HTTPMethod { - fn from(val: &str) -> Self { - use HTTPMethod::*; - let request_line = val.split(' ').collect_vec(); - let (method, info) = (request_line[0], request_line[1]); - let info = info.chars().skip(1).collect::() + &"/"; - let (endpoint, target) = info.split_once("/").expect("Should be splitable by /"); - match method { - "GET" => Get((endpoint.to_string(), target.to_string())), - "POST" => Post((endpoint.to_string(), target.to_string())), - _ => { - eprintln!("{method} Not Supported Yet"); - unreachable!() - } - } - } -} - -#[derive(Debug)] -struct Request { - method: HTTPMethod, - headers: Option, - body: Option, -} - -impl Request { - fn new(method: HTTPMethod, headers: Headers, body: String) -> Self { - let headers = if headers.0.len() == 0 { - None - } else { - Some(headers) - }; - let body = if body.is_empty() { None } else { Some(body) }; - Request { - method, - headers, - body, - } - } -} - -impl From<&str> for Request { - fn from(val: &str) -> Self { - let request: Vec<&str> = val.split("\r\n").collect(); - match &request[..] { - [request_line, headers @ .., body] => { - let (method, headers, body) = ( - HTTPMethod::from(*request_line), - Headers::from(headers), - body.to_string(), - ); - Request::new(method, headers, body) - } - _ => { - unreachable!(); - } - } - } -} - -struct Response { - version: String, - status: StatusCode, - headers: Option, - body: Option, -} - -impl Response { - fn new( - version: String, - status: StatusCode, - headers: Option, - body: Option, - ) -> Self { - Response { - version, - headers, - status, - body, - } - } -} - -impl Into for Response { - fn into(self) -> String { - let status_line = format!("HTTP/{} {}", self.version, String::from(self.status)); - let headers = self - .headers - .unwrap_or(Headers(HashMap::new())) - .0 - .iter() - .map(|(key, value)| format!("{key}: {value}\r\n")) - .collect::(); - let body = self.body.unwrap_or("".to_string()); - format!("{status_line}\r\n{headers}\r\n{body}") - } -} +use http_server_starter_rust::http_types::*; +use http_server_starter_rust::request::*; +use http_server_starter_rust::response::*; fn handle_client(mut stream: TcpStream) { // Buffer to store the data received from the client @@ -258,14 +100,10 @@ fn handle_client(mut stream: TcpStream) { }, } } - HTTPMethod::Post(target) => todo!(), - HTTPMethod::Put(target) => todo!(), + HTTPMethod::Post(_target) => todo!(), + HTTPMethod::Put(_target) => todo!(), } } - - // Prepare a response - // let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, world!"; - // } Err(e) => { eprintln!("Failed to read from stream: {}", e); @@ -273,18 +111,19 @@ fn handle_client(mut stream: TcpStream) { } } -fn main() { +fn main() -> io::Result<()> { let listener = TcpListener::bind("127.0.0.1:4221").unwrap(); for stream in listener.incoming() { match stream { Ok(stream) => { - // println!("accepted new connection"); - handle_client(stream) + thread::spawn(move || handle_client(stream)); } Err(e) => { eprintln!("error: {}", e); } } } + + Ok(()) } diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..8728e79 --- /dev/null +++ b/src/request.rs @@ -0,0 +1,43 @@ +use crate::http_types::*; + +#[derive(Debug)] +pub struct Request { + pub method: HTTPMethod, + pub headers: Option, + body: Option, +} + +impl Request { + fn new(method: HTTPMethod, headers: Headers, body: String) -> Self { + let headers = if headers.0.len() == 0 { + None + } else { + Some(headers) + }; + let body = if body.is_empty() { None } else { Some(body) }; + Request { + method, + headers, + body, + } + } +} + +impl From<&str> for Request { + fn from(val: &str) -> Self { + let request: Vec<&str> = val.split("\r\n").collect(); + match &request[..] { + [request_line, headers @ .., body] => { + let (method, headers, body) = ( + HTTPMethod::from(*request_line), + Headers::from(headers), + body.to_string(), + ); + Request::new(method, headers, body) + } + _ => { + unreachable!(); + } + } + } +} diff --git a/src/response.rs b/src/response.rs new file mode 100644 index 0000000..4219afd --- /dev/null +++ b/src/response.rs @@ -0,0 +1,40 @@ +use crate::http_types::*; +use std::collections::HashMap; + +pub struct Response { + version: String, + status: StatusCode, + headers: Option, + body: Option, +} + +impl Response { + pub fn new( + version: String, + status: StatusCode, + headers: Option, + body: Option, + ) -> Self { + Response { + version, + headers, + status, + body, + } + } +} + +impl Into for Response { + fn into(self) -> String { + let status_line = format!("HTTP/{} {}", self.version, String::from(self.status)); + let headers = self + .headers + .unwrap_or(Headers(HashMap::new())) + .0 + .iter() + .map(|(key, value)| format!("{key}: {value}\r\n")) + .collect::(); + let body = self.body.unwrap_or("".to_string()); + format!("{status_line}\r\n{headers}\r\n{body}") + } +} -- cgit v1.2.3