From 94752493d1a03d7c7c3172a53279855e9c99203d Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 30 Jun 2023 19:23:38 +0200 Subject: [PATCH] Add write and build function to result, add basis for cookie setting possibility, divide the response module; --- core/http/src/handlers/handler.rs | 69 +++++++++---------- core/http/src/handling/request/form_utils.rs | 6 ++ core/http/src/handling/request/mod.rs | 2 +- core/http/src/handling/response/cookie.rs | 0 core/http/src/handling/response/datatypes.rs | 45 ++++++++++++ core/http/src/handling/response/mod.rs | 12 ++++ core/http/src/handling/response/response.rs | 32 +++++++++ .../{response.rs => response/status.rs} | 66 ------------------ core/http/src/handling/response/traits.rs | 48 +++++++++++++ site/src/main.rs | 20 ++---- 10 files changed, 185 insertions(+), 115 deletions(-) create mode 100644 core/http/src/handling/response/cookie.rs create mode 100644 core/http/src/handling/response/datatypes.rs create mode 100644 core/http/src/handling/response/mod.rs create mode 100644 core/http/src/handling/response/response.rs rename core/http/src/handling/{response.rs => response/status.rs} (84%) create mode 100644 core/http/src/handling/response/traits.rs diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index 9596f52..3c46159 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -1,12 +1,12 @@ -use std::path::PathBuf; +use std::{path::PathBuf, io}; -use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt, AsyncWriteExt}, net::TcpStream}; +use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt}, net::TcpStream}; use crate::handling::{ file_handlers::NamedFile, request::Request, response::{Outcome, Response, ResponseBody, Status}, - routes::Data, methods::Method, + routes::{Data, Body}, methods::Method, }; use crate::setup::MountPoint; @@ -78,13 +78,17 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin size } else { eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method); - len_not_defined(stream, Status::LengthRequired).await; + if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { + error_occured_when_writing(e) + }; return; } } else { if request.mandatory_body() { eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method); - len_not_defined(stream, Status::LengthRequired).await; + if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { + error_occured_when_writing(e) + }; return; } 0 @@ -94,7 +98,9 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()]; let read = buf_reader.read(&mut buffer).await.unwrap(); if read != length { - len_not_defined(stream, Status::LengthRequired).await; + if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { + error_occured_when_writing(e) + }; return; } data.is_complete = true; @@ -133,47 +139,40 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let response = match handled_response { Some(val) => match val { - Outcome::Success(success) => ( - format!("HTTP/1.1 {}\r\n", success.status.unwrap_or(Status::Ok)) - + &success.headers.join("\r\n") - + "\r\n\r\n", - if request.method == Method::Head { - vec![] - } else { - success.body.get_data() - } - ), + Outcome::Success(success) => success + , Outcome::Failure(error) => failure_handler(error), Outcome::Forward(_) => failure_handler(Status::NotFound), }, None => failure_handler(Status::NotFound), }; - let mut resp = response.0.as_bytes().to_vec(); - resp.extend_from_slice(&response.1); - if let Err(e) = stream.write_all(&resp).await { + if let Err(e) = response.write(stream, Some(request)).await { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); } } -fn failure_handler(status: Status) -> (String, Vec<u8>) { +fn failure_handler<'a>(status: Status) -> Response<'a> { let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); - ( - format!( - "HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", - status, - page_404.get_len(), - page_404.get_mime() - ), - page_404.get_data(), - ) + Response { + cookies: None, + headers: vec![], + status: Some(status), + body: Box::new(Body::new(page_404.get_data(), page_404.get_mime())), + } } -async fn len_not_defined(mut stream: TcpStream, status: Status) { +async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()>{ let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); - let mut response = format!("HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", status, page_411.get_len(), page_411.get_mime()).as_bytes().to_vec(); - response.extend_from_slice(&page_411.get_data()); - if let Err(e) = stream.write_all(&response).await { - eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); - } + Response { + cookies: None, + headers: vec![], + status: Some(status), + body: Box::new(Body::new(page_411.get_data(), page_411.get_mime())), + }.write(stream, None).await?; + Ok(()) +} + +fn error_occured_when_writing(e: io::Error) { + eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); } diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index d70b2bc..cab16eb 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -17,6 +17,12 @@ impl Request<'_> { /// /// # Examples /// ``` + /// use http::handling::request::Request; + /// use http::handling::request::ParseFormError; + /// use http::handling::request::ParseErrors; + /// use http::handling::methods::Method; + /// + /// /// let request = Request { /// uri: "/form?name=Name&age=Age", /// headers: vec![], diff --git a/core/http/src/handling/request/mod.rs b/core/http/src/handling/request/mod.rs index 54da4ad..f04c57a 100644 --- a/core/http/src/handling/request/mod.rs +++ b/core/http/src/handling/request/mod.rs @@ -3,4 +3,4 @@ mod datatypes; mod form_utils; mod request_impl; mod request_mime; -pub use datatypes::{MediaType, ParseFormError, Request}; +pub use datatypes::{MediaType, ParseErrors, ParseFormError, Request}; diff --git a/core/http/src/handling/response/cookie.rs b/core/http/src/handling/response/cookie.rs new file mode 100644 index 0000000..e69de29 diff --git a/core/http/src/handling/response/datatypes.rs b/core/http/src/handling/response/datatypes.rs new file mode 100644 index 0000000..503929a --- /dev/null +++ b/core/http/src/handling/response/datatypes.rs @@ -0,0 +1,45 @@ +use std::time::Duration; + +use super::{ResponseBody, Status}; + +type HeaderMap = Vec<String>; + +#[derive(Debug)] +pub enum Outcome<S, E, F> { + Success(S), + Failure(E), + Forward(F), +} + +pub enum SameSite { + None, + Lax, + Strict, +} + +pub struct Cookie<'a> { + /// Storage for the cookie string. Only used if this structure was derived + /// from a string that was subsequently parsed. + cookie_string: &'a str, + name: &'a str, + value: &'a str, + // expires: Option<Tm>, + max_age: Option<Duration>, + /// The cookie's domain, if any. + domain: Option<&'a str>, + /// The cookie's path domain, if any. + path: Option<&'a str>, + /// Whether this cookie was marked Secure. + secure: Option<bool>, + /// Whether this cookie was marked HttpOnly. + http_only: Option<bool>, + /// The draft `SameSite` attribute. + same_site: Option<SameSite>, +} + +pub struct Response<'a> { + pub headers: HeaderMap, + pub cookies: Option<Cookie<'a>>, + pub status: Option<Status>, + pub body: Box<dyn ResponseBody>, +} diff --git a/core/http/src/handling/response/mod.rs b/core/http/src/handling/response/mod.rs new file mode 100644 index 0000000..017bc15 --- /dev/null +++ b/core/http/src/handling/response/mod.rs @@ -0,0 +1,12 @@ +mod cookie; +mod datatypes; +mod response; +mod status; +mod traits; + +pub use datatypes::Cookie; +pub use datatypes::Outcome; +pub use datatypes::Response; +pub use datatypes::SameSite; +pub use status::Status; +pub use traits::ResponseBody; diff --git a/core/http/src/handling/response/response.rs b/core/http/src/handling/response/response.rs new file mode 100644 index 0000000..dfdb909 --- /dev/null +++ b/core/http/src/handling/response/response.rs @@ -0,0 +1,32 @@ +use std::io::Result; + +use tokio::{net::TcpStream, io::AsyncWriteExt}; + +use crate::handling::{methods::Method, request::Request, response::Status}; + +use super::Response; + +impl Response<'_> { + pub fn build(self, request: Option<Request>) -> Vec<u8> { + let mut compiled_headers = format!("HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n", self.status.unwrap_or(Status::Ok), self.body.get_len(), self.body.get_mime()) + + &self.headers.join("\r\n") + + "\r\n"; + let is_head = if let Some(req) = request { + req.method == Method::Head + } else { + false + }; + let compiled_body = if is_head { + vec![] + } else { + self.body.get_data() + }; + let compiled_out = unsafe { compiled_headers.as_mut_vec() }; + compiled_out.extend_from_slice(&compiled_body); + compiled_out.to_vec() + } + pub async fn write(self, mut stream: TcpStream, request: Option<Request<'_>>) -> Result<()> { + let resp = self.build(request); + stream.write_all(&resp).await?; + Ok(()) +}} diff --git a/core/http/src/handling/response.rs b/core/http/src/handling/response/status.rs similarity index 84% rename from core/http/src/handling/response.rs rename to core/http/src/handling/response/status.rs index 81a54af..9f825af 100644 --- a/core/http/src/handling/response.rs +++ b/core/http/src/handling/response/status.rs @@ -1,71 +1,5 @@ use std::fmt::Display; -use crate::utils::mime::mime_enum::Mime; - -use super::routes::Body; - -type HeaderMap = Vec<String>; - -#[derive(Debug)] -pub enum Outcome<S, E, F> { - Success(S), - Failure(E), - Forward(F), -} - -pub trait ResponseBody: Send { - fn get_data(&self) -> Vec<u8>; - fn get_mime(&self) -> Mime; - fn get_len(&self) -> usize; -} - -impl ResponseBody for Body { - fn get_data(&self) -> Vec<u8> { - self.body() - } - fn get_mime(&self) -> Mime { - self.mime_type() - } - - fn get_len(&self) -> usize { - self.get_data().len() - } -} - -impl ResponseBody for &str { - fn get_data(&self) -> Vec<u8> { - self.as_bytes().to_vec() - } - - fn get_mime(&self) -> Mime { - Mime::TextPlain - } - - fn get_len(&self) -> usize { - self.len() - } -} - -impl ResponseBody for String { - fn get_data(&self) -> Vec<u8> { - self.as_bytes().to_vec() - } - - fn get_mime(&self) -> Mime { - Mime::TextPlain - } - - fn get_len(&self) -> usize { - self.len() - } -} - -pub struct Response { - pub headers: HeaderMap, - pub status: Option<Status>, - pub body: Box<dyn ResponseBody>, -} - #[derive(Debug)] pub enum Status { Continue, diff --git a/core/http/src/handling/response/traits.rs b/core/http/src/handling/response/traits.rs new file mode 100644 index 0000000..96ac87c --- /dev/null +++ b/core/http/src/handling/response/traits.rs @@ -0,0 +1,48 @@ +use crate::{handling::routes::Body, utils::mime::mime_enum::Mime}; + +pub trait ResponseBody: Send { + fn get_data(&self) -> Vec<u8>; + fn get_mime(&self) -> Mime; + fn get_len(&self) -> usize; +} + +impl ResponseBody for Body { + fn get_data(&self) -> Vec<u8> { + self.body() + } + fn get_mime(&self) -> Mime { + self.mime_type() + } + + fn get_len(&self) -> usize { + self.get_data().len() + } +} + +impl ResponseBody for &str { + fn get_data(&self) -> Vec<u8> { + self.as_bytes().to_vec() + } + + fn get_mime(&self) -> Mime { + Mime::TextPlain + } + + fn get_len(&self) -> usize { + self.len() + } +} + +impl ResponseBody for String { + fn get_data(&self) -> Vec<u8> { + self.as_bytes().to_vec() + } + + fn get_mime(&self) -> Mime { + Mime::TextPlain + } + + fn get_len(&self) -> usize { + self.len() + } +} diff --git a/site/src/main.rs b/site/src/main.rs index f1fda07..f677a34 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -4,7 +4,7 @@ use http::handling::{ file_handlers::NamedFile, methods::Method, request::{Request, ParseFormError}, - response::{Outcome, Response, ResponseBody, Status}, + response::{Outcome, Response, Status}, routes::{Data, Route}, }; @@ -28,10 +28,8 @@ fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Statu }; let response = hashmap_to_string(&keys); Outcome::Success(Response { - headers: vec![ - format!("Content-Length: {}", response.len()), - format!("Content-Type: text/plain"), - ], + headers: vec![], + cookies: None, status: Some(Status::Ok), body: Box::new(response), }) @@ -42,10 +40,8 @@ fn handler(request: Request<'_>, _data: Data) -> Outcome<Response, Status, Data> let response = fileserver(request.uri.strip_prefix("static/").unwrap()); let response = match response { Ok(dat) => Response { - headers: vec![ - format!("Content-Length: {}", dat.get_len()), - format!("Content-Type: {}", dat.get_mime()), - ], + headers: vec![], + cookies: None, status: Some(Status::Ok), body: Box::new(dat), }, @@ -69,10 +65,8 @@ fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Da return Outcome::Failure(Status::BadRequest); }; Outcome::Success(Response { - headers: vec![ - format!("Content-Length: {}", dat.len()), - format!("Content-Type: text/plain"), - ], + headers: vec![], + cookies: None, status: Some(Status::Ok), body: Box::new(dat), }) -- GitLab