Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • codecraft/webserver
  • sorcaMriete/webserver
  • opnonmicgo/webserver
  • loposuezo/webserver
  • diufeYmike/webserver
  • contbuspecmi/webserver
  • mogamuboun/webserver
  • glabalwelre/webserver
8 results
Show changes
Showing
with 3805 additions and 11 deletions
pub mod file_handlers;
pub mod methods;
pub mod request;
pub mod response;
pub mod routes;
use std::collections::HashMap;
use super::Request;
impl Request {
/// Extracts the cookies from a Vector and gives back an optional HashMap of Strings
///
/// Returns none if there are no cookies or there is a problem with the cookies, for example
/// missing a value.
///
/// Removes the `Cookie: ` Header from the Vector of headers
///
/// # Examples
/// ```
/// use http::handling::request::Request;
///
///
/// let mut request_vec = vec![
/// "GET / HTTP/1.1".to_string(),
/// "Accept: sdf".to_string(),
/// "Cookie: io=23; f=as".to_string(),
/// "Format: gzip".to_string(),
/// ];
/// let right_finished_vec = vec![
/// "GET / HTTP/1.1".to_string(),
/// "Accept: sdf".to_string(),
/// "Format: gzip".to_string(),
/// ];
///
/// let mut wrong = vec!["GET / HTTP/1.1".to_string()];
///
/// assert_eq!(None, Request::extract_cookies_from_vec(&mut wrong));
/// let cookies = Request::extract_cookies_from_vec(&mut request_vec);
/// assert_eq!(right_finished_vec, request_vec);
/// assert_eq!("23", cookies.clone().unwrap().get("io").unwrap());
/// assert_eq!("as", cookies.unwrap().get("f").unwrap());
/// ```
///
/// # Panics
/// No Panics
pub fn extract_cookies_from_vec(headers: &mut Vec<String>) -> Option<HashMap<String, String>> {
let mut cookies: HashMap<String, String> = HashMap::new();
let mut cookies_string = if let Some(index) = headers
.iter()
.position(|header| header.starts_with("Cookie: "))
{
headers.remove(index)
} else {
return None;
};
cookies_string = cookies_string
.strip_prefix("Cookie: ")
.unwrap()
.trim()
.trim_matches('"')
.to_string();
for cookie in cookies_string.split(';') {
let Some((name, cookie)) = cookie.split_once('=') else {
return None;
};
cookies
.entry(name.trim().to_string())
.or_insert(cookie.trim().to_string());
}
Some(cookies)
}
}
#[cfg(test)]
mod test {
use crate::handling::request::Request;
#[test]
fn test_cookies() {
let mut request_vec = vec![
"GET / HTTP/1.1".to_string(),
"Accept: sdf".to_string(),
"Cookie: io=23; f=as".to_string(),
"Format: gzip".to_string(),
];
let right_finished_vec = vec![
"GET / HTTP/1.1".to_string(),
"Accept: sdf".to_string(),
"Format: gzip".to_string(),
];
let mut wrong = vec!["GET / HTTP/1.1".to_string()];
assert_eq!(None, Request::extract_cookies_from_vec(&mut wrong));
let cookies = Request::extract_cookies_from_vec(&mut request_vec);
assert_eq!(right_finished_vec, request_vec);
assert_eq!("23", cookies.clone().unwrap().get("io").unwrap());
assert_eq!("as", cookies.unwrap().get("f").unwrap());
}
}
use std::{collections::HashMap, error::Error, fmt::Display};
use crate::{
handling::methods::Method,
utils::{mime::Mime, url_utils::Uri},
};
type HeaderMap = Vec<String>;
/// A struct to handle Requests
///
#[derive(Clone)]
pub struct Request {
/// The requested Uri
pub uri: Uri,
/// All headers of the request that haven't been parsed
pub headers: HeaderMap,
/// The methods Request represented with the [Method]
pub method: Method,
/// An optional HashMap representation of all Cookies of the request
pub cookies: Option<HashMap<String, String>>,
/// If the has a body it represents the [Mime]-type of the body
pub mime_type: Option<(Mime, String)>,
// pub connection: ConnectionMeta,
}
// struct ConnectionMeta {
// remote: Option<SocketAddr>,
// // certificates
// }
#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)]
/// Media Types in which a Route can be requested to ansewr, optional for routes
pub enum MediaType {
/// Json Data
Json,
/// Plain Text
Plain,
/// HTML Text
Html,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ParseErrors {
NoData,
BadData,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
/// Errors that Occur when a Form can't be parsed
pub struct ParseFormError {
pub error: ParseErrors,
}
impl Display for ParseFormError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.error)
}
}
impl Display for ParseErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseErrors::NoData => write!(f, "No Data at key"),
ParseErrors::BadData => write!(f, "Bad Data at key"),
}
}
}
impl Error for ParseFormError {}
use std::collections::HashMap;
use crate::{
handling::routes::Data,
utils::{mime::Mime, urlencoded::DeCodable},
};
use super::{datatypes::ParseErrors, ParseFormError, Request};
static TWO_NEWLINES: u8 = 3;
impl Request {
/// # Gets data from a get_form as a HashMap
///
/// # Errors
/// Gives back a [ParseFormError], top level, if there is lacking data
///
/// If everything is fine on the top level it gives back a HashMap of keys and Results, that
/// indicate wether the key exists with the [ParseFormError] with an error of
/// [ParseErrors::NoData] or wether the key is corrupt with the [ParseErrors::BadData]-Variant
///
/// # Examples
/// ```
/// use http::handling::{request::{Request, ParseFormError, ParseErrors}, methods::Method};
/// use http::utils::urlencoded::UrlEncodeData;
///
///
/// let request = Request {
/// uri: UrlEncodeData::from_encoded("/form?name=Name&age=Age").unwrap(),
/// headers: vec![],
/// method: Method::Get,
/// cookies: None,
/// mime_type: None,
/// };
/// let right = request.get_get_form_keys(&["name", "age"]).unwrap();
/// assert_eq!(&"Name", right.get("name").unwrap().as_ref().unwrap());
/// assert_eq!(&"Age", right.get("age").unwrap().as_ref().unwrap());
///
/// let wrong_request = Request {
/// uri: UrlEncodeData::from_encoded("/form").unwrap(),
/// ..request.clone()
/// };
/// assert_eq!(
/// Err(ParseFormError {
/// error: ParseErrors::NoData
/// }),
/// wrong_request.get_get_form_keys(&["name", "age"])
/// );
///
/// let bad_data = Request {
/// uri: UrlEncodeData::from_encoded("/form?age=").unwrap(),
/// ..request.clone()
/// };
/// let wrong = bad_data.get_get_form_keys(&["name", "age"]).unwrap();
/// assert_eq!(
/// &Err(ParseFormError {
/// error: ParseErrors::NoData
/// }),
/// wrong.get("name").unwrap()
/// );
/// assert_eq!(&Ok(""), wrong.get("age").unwrap());
/// ```
///
/// # Panics
/// No Panics
pub fn get_get_form_keys<'a>(
&'a self,
keys: &'a [&str],
) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> {
let data = if let Some(val) = self
.uri
.parts()
.last()
.unwrap()
.raw_string()
.unwrap()
.split_once('?')
{
val
} else {
return Err(ParseFormError {
error: ParseErrors::NoData,
});
};
let data = data
.1
.split('&')
.map(|kvp| kvp.split_once('='))
.collect::<Vec<Option<(&str, &str)>>>();
let mut values: HashMap<&str, &str> = HashMap::new();
for kvp in data {
let kvp = if let Some(kvp) = kvp {
kvp
} else {
continue;
};
values.insert(kvp.0, kvp.1);
}
let mut response = HashMap::new();
for key in keys {
let entry = if let Some(val) = values.get(key) {
Ok(*val)
} else {
Err(ParseFormError {
error: ParseErrors::NoData,
})
};
response.insert(*key, entry);
}
Ok(response)
}
pub fn get_post_data<'a>(
&'a self,
keys: &[&'a str],
data: &Data,
) -> Result<HashMap<String, Result<Vec<u8>, ParseFormError>>, ParseFormError> {
let data = data.buffer.as_slice();
let mut keymap: HashMap<String, Result<Vec<u8>, ParseFormError>> =
HashMap::with_capacity(keys.len());
for key in keys {
keymap.entry(key.to_string()).or_insert(Err(ParseFormError {
error: ParseErrors::NoData,
}));
}
let Some(ref mime_type) = self.mime_type else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
match mime_type.0 {
Mime::ApplicationXWwwFormUrlencoded => {
let Ok(data) = String::from_utf8(data.to_vec()) else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
for kvp in data.split('&') {
let Some(kvp) = kvp.split_once('=') else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let Ok(key) = kvp.0.decode() else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let Ok(key) = String::from_utf8(key) else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let Ok(value) = kvp.1.trim_end_matches('\0').decode() else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let Some(thing) = keymap.get_mut(&key) else {
continue;
};
*thing = Ok(value);
}
}
Mime::MultipartFormData => {
let Some(mut boundary) = mime_type.1.split(';').find(|element| element.trim().starts_with("boundary=")) else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
boundary = boundary
.trim()
.strip_prefix("boundary=")
.unwrap()
.trim_matches('"');
let mut temp_bound = "--".to_string();
temp_bound.push_str(boundary);
let end_boundary: Vec<u8> = format!("{temp_bound}--\r").into();
temp_bound.push('\r');
let boundary = temp_bound.as_bytes();
Self::get_multipart_data(data, boundary, &end_boundary, &mut keymap);
}
_ => {
return Err(ParseFormError {
error: ParseErrors::BadData,
})
}
};
Ok(keymap)
}
fn get_multipart_data(
data: &[u8],
boundary: &[u8],
end_boundary: &[u8],
map: &mut HashMap<String, Result<Vec<u8>, ParseFormError>>,
) {
let mut current_part: Vec<&[u8]> = vec![];
let mut current_key: Option<String> = None;
let mut ignore_line = 0;
for part in data.split(|byte| byte == &b'\n') {
if part == [b'\r'] {
if current_key.is_some() {
if ignore_line >= TWO_NEWLINES {
current_part.push(&[b'\n']);
continue;
}
ignore_line += 1;
}
continue;
}
if part == end_boundary {
if let Some(key) = &current_key {
let mut part = current_part.join(&b'\n');
if part.ends_with(&[b'\r']) {
part.pop();
}
map.insert(key.to_string(), Ok(part));
}
break;
}
if part == boundary {
if let Some(key) = &current_key {
let mut part = current_part.join(&b'\n');
if part.ends_with(&[b'\r']) {
part.pop();
}
map.insert(key.to_string(), Ok(part));
}
current_part = vec![];
current_key = None;
ignore_line = 0;
continue;
}
if part.starts_with(b"Content-Disposition: form-data; name=") {
let headers = part
.split(|byte| byte == &b';')
.filter(|header| !header.is_empty())
.collect::<Vec<_>>();
if headers.len() < 2 {
continue;
}
let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>();
if name.len() != 2 {
continue;
}
let mkey = String::from_utf8_lossy(name[1])
.as_ref()
.trim_end()
.trim_matches('"')
.to_owned();
if map.contains_key::<str>(&mkey) {
current_key = Some(mkey.to_owned());
}
continue;
} else if current_key.is_some() {
current_part.push(part);
}
}
}
}
#[cfg(test)]
mod test {
use crate::{
handling::{
methods::Method,
request::{datatypes::ParseErrors, ParseFormError},
routes::Data,
},
utils::mime::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData},
};
use super::Request;
#[test]
fn try_get_test() {
let request = Request {
uri: "/form?name=Name&age=Age".try_into().unwrap(),
headers: vec![],
method: Method::Get,
cookies: None,
mime_type: None,
};
let right = request.get_get_form_keys(&["name", "age"]).unwrap();
assert_eq!(&"Name", right.get("name").unwrap().as_ref().unwrap());
assert_eq!(&"Age", right.get("age").unwrap().as_ref().unwrap());
let wrong_request = Request {
uri: "/form".try_into().unwrap(),
..request.clone()
};
assert_eq!(
Err(ParseFormError {
error: ParseErrors::NoData
}),
wrong_request.get_get_form_keys(&["name", "age"])
);
let bad_data = Request {
uri: "/form?age=".try_into().unwrap(),
..request.clone()
};
let wrong = bad_data.get_get_form_keys(&["name", "age"]).unwrap();
assert_eq!(
&Err(ParseFormError {
error: ParseErrors::NoData
}),
wrong.get("name").unwrap()
);
assert_eq!(&Ok(""), wrong.get("age").unwrap());
}
#[test]
fn try_post_text() {
let req = Request {
uri: "".try_into().unwrap(),
headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()],
method: Method::Post,
cookies: None,
mime_type: Some((ApplicationXWwwFormUrlencoded, "".into())),
};
let data = Data {
buffer: b"message=23&message1=24".to_vec(),
is_complete: true,
};
let map = req.get_post_data(&["message", "message1"], &data).unwrap();
assert_eq!(
&b"23".to_vec(),
map.get("message").unwrap().as_ref().unwrap()
);
assert_eq!(
&b"24".to_vec(),
map.get("message1").unwrap().as_ref().unwrap()
);
let req = Request {
uri: "".try_into().unwrap(),
headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()],
method: Method::Post,
cookies: None,
mime_type: Some((
MultipartFormData,
"charset=UTF-8; boundary=\"boundary\"".into(),
)),
};
let data = Data {
buffer: b"--boundary\r
Content-Disposition: form-data; name=\"field1\"\r
\r
value1\r
--boundary\r
Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r
\r
va\nlue2\r
--boundary--\r
"
.to_vec(),
is_complete: true,
};
let map = req.get_post_data(&["field1", "field2"], &data).unwrap();
assert_eq!(
&b"value1".to_vec(),
map.get("field1").unwrap().as_ref().unwrap()
);
assert_eq!(
&b"va\nlue2".to_vec(),
map.get("field2").unwrap().as_ref().unwrap()
);
}
}
mod cookies;
mod datatypes;
mod form_utils;
mod request_impl;
mod request_mime;
pub use datatypes::{MediaType, ParseErrors, ParseFormError, Request};
use crate::handling::methods::Method;
use super::Request;
impl Request {
/// Checks if the request can have a body
pub fn can_have_body(&self) -> bool {
matches!(
self.method,
Method::Post | Method::Put | Method::Patch | Method::Delete
)
}
/// Checks if a body is mandatory for the Request
pub fn mandatory_body(&self) -> bool {
matches!(self.method, Method::Post | Method::Put | Method::Patch)
}
}
use super::datatypes::Request;
impl Request {
/// Sets the `mime_type` of this [`Request`] from the headers.
///
/// The mime_type can remain none if there isn't a `Content-Type: ` header
///
/// # Example
/// ```
/// use http::{
/// handling::{methods::Method, request::Request},
/// utils::{mime::Mime, urlencoded::UrlEncodeData},
/// };
///
/// let mut request = Request {
/// uri: UrlEncodeData::from_encoded("thing").unwrap(),
/// headers: vec![
/// "GET / 23".to_string(),
/// "SDF:LKJSD:F".to_string(),
/// "Content-Type: text/plain; charset=UTF-8".to_string(),
/// "SDF".to_string(),
/// ],
/// method: Method::Get,
/// cookies: None,
/// mime_type: None,
/// };
/// let mut wrong = Request {
/// uri: UrlEncodeData::from_encoded("thing").unwrap(),
/// headers: vec![
/// "GET / 23".to_string(),
/// "SDF:LKJSD:F".to_string(),
/// "SDF".to_string(),
/// ],
/// method: Method::Get,
/// cookies: None,
/// mime_type: None,
/// };
/// request.mime_from_headers();
/// wrong.mime_from_headers();
/// assert_eq!(None, wrong.mime_type);
/// assert_eq!((Mime::TextPlain, " charset=UTF-8".to_string()), request.mime_type.unwrap());
/// ```
/// # Panics
/// No Panics
pub fn mime_from_headers(&mut self) {
let Some(content_type_header) = self.headers
.iter()
.find(|header| header.starts_with("Content-Type: ")) else {
return;
};
let content_type_string: &str = content_type_header
.strip_prefix("Content-Type: ")
.unwrap()
.trim();
self.mime_type = if let Some(content_type) = content_type_string.split_once(';') {
let Ok(mime) = content_type.0.trim().parse() else {
return;
};
Some((mime, content_type.1.to_owned()))
} else {
let Ok(mime) = content_type_string.parse() else {
return;
};
Some((mime, "".to_owned()))
};
}
}
#[cfg(test)]
mod test {
use crate::{
handling::{methods::Method, request::Request},
utils::mime::Mime,
};
#[test]
pub fn test_mime_parse_from_header_vec() {
let mut request = Request {
uri: "thing".try_into().unwrap(),
headers: vec![
"GET / 23".to_string(),
"SDF:LKJSD:F".to_string(),
"Content-Type: text/plain".to_string(),
"SDF".to_string(),
],
method: Method::Get,
cookies: None,
mime_type: None,
};
let mut wrong = Request {
uri: "thing".try_into().unwrap(),
headers: vec![
"GET / 23".to_string(),
"SDF:LKJSD:F".to_string(),
"SDF".to_string(),
],
method: Method::Get,
cookies: None,
mime_type: None,
};
request.mime_from_headers();
wrong.mime_from_headers();
assert_eq!(None, wrong.mime_type);
assert_eq!((Mime::TextPlain, "".to_owned()), request.mime_type.unwrap());
}
}
use std::io::Result;
use tokio::io::{AsyncWriteExt, AsyncRead, AsyncWrite};
use crate::handling::{methods::Method, request::Request, response::Status};
use super::Response;
impl Response {
/// Builds a [`Vec<u8>`] valid http response from a [Response] and consumes it. Optionally
/// takes in a request for things like [Method::Head]
pub fn build(self, request: Option<Request>) -> Vec<u8> {
let 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 mut compiled_out: Vec<u8> = compiled_headers.into();
compiled_out.extend_from_slice(&compiled_body);
compiled_out
}
/// Builds and writes The http-Response, consumes the [tokio::net::TcpStream] [Request] and [Response]
pub async fn write<T: AsyncRead + AsyncWrite + std::marker::Unpin>(self, mut stream: T, request: Option<Request>) -> Result<()> {
let resp = self.build(request);
stream.write_all(&resp).await?;
Ok(())
}
}
use std::{collections::HashMap, str::FromStr, time::Duration};
use crate::{
handling::response::{cookie_management::error_types::CookieError, CookieBuilder},
utils::{url_utils::Uri, urlencoded::DeCodable},
};
macro_rules! update_map {
($map:expr, $key:expr) => {
*$map.get_mut($key).unwrap() = true;
};
}
use super::error_types::{ParseCookieError, ParseSameSiteError, SameSiteError};
/// Structure representing a Cookie
/// # Creating a Cookie:
/// ```
/// use http::handling::response::Cookie;
/// use http::handling::response::CookieBuilder;
///
/// let cookie = CookieBuilder::build("name", "value").finish();
/// ```
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Cookie {
/// Storage for the cookie string. Only used if this structure was derived
/// from a string that was subsequently parsed.
pub(crate) cookie_string: Option<String>,
pub(crate) name: String,
pub(crate) value: String,
/// Whether this cookie was marked Secure.
pub(crate) secure: bool,
/// Whether this cookie was marked HttpOnly.
pub(crate) http_only: bool,
pub(crate) partitioned: bool,
// expires: Option<Tm>,
pub(crate) max_age: Option<Duration>,
/// The draft `SameSite` attribute.
pub(crate) same_site: Option<SameSite>,
/// The cookie's domain, if any.
pub(crate) domain: Option<String>,
/// The cookie's path domain, if any.
pub(crate) path: Option<Uri>,
pub(crate) expires: Option<String>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
/// SameSite Paremeters
pub enum SameSite {
/// Requires Secure
None,
Lax,
Strict,
}
impl std::fmt::Display for SameSite {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => write!(f, "SameSite=None"),
Self::Lax => write!(f, "SameSite=Lax"),
Self::Strict => write!(f, "SameSite=Strict"),
}
}
}
impl FromStr for SameSite {
type Err = ParseSameSiteError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"None" => Ok(SameSite::None),
"Lax" => Ok(SameSite::Lax),
"Strict" => Ok(SameSite::Strict),
_ => Err(Self::Err {
inner: SameSiteError::NotAValidVariant,
}),
}
}
}
impl std::fmt::Display for Cookie {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut appendix = String::from("");
if self.secure {
appendix += "; Secure";
}
if self.http_only {
appendix += "; HttpOnly";
}
if self.partitioned {
appendix += "; Partitioned";
}
if let Some(max_age) = &self.max_age {
appendix += &format!("; Max-Age={}", max_age.as_secs());
}
if let Some(domain) = &self.domain {
appendix += &format!("; Domain={}", domain);
}
if let Some(path) = &self.path {
appendix += &format!("; Path={}", path);
}
if let Some(same_site) = &self.same_site {
appendix += &format!("; {}", same_site);
if !self.secure && *same_site == SameSite::None {
appendix += "; Secure";
}
}
if let Some(expires) = &self.expires {
appendix += &format!("; Expires={}", expires)
}
write!(f, "Set-Cookie: {}={}{}", self.name, self.value, appendix)
}
}
impl FromStr for Cookie {
type Err = ParseCookieError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut map = HashMap::with_capacity(8);
map.insert("Partitioned", false);
map.insert("HttpOnly", false);
map.insert("Secure", false);
map.insert("SameSite", false);
map.insert("Max-Age", false);
map.insert("Domain", false);
map.insert("Path", false);
map.insert("Expires", false);
let mut final_result = CookieBuilder::build("", "");
let mut first = true;
let stripped = s.strip_prefix("Set-Cookie: ").ok_or(Self::Err {
inner: CookieError::NoSetCookieHeader,
})?;
for part in stripped.split(';') {
let trimmed_part = part.trim();
if first {
let Some(name_val) = part.split_once('=') else {
return Err(Self::Err { inner: CookieError::MissingEqual });
};
unsafe {
final_result = CookieBuilder::build(
&String::from_utf8_unchecked(if let Ok(name) = name_val.0.decode() {
name
} else {
name_val.0.into()
}),
&String::from_utf8_unchecked(if let Ok(value) = name_val.1.decode() {
value
} else {
name_val.1.into()
}),
);
}
first = false;
continue;
}
if !map.get("Max-Age").unwrap() && trimmed_part.starts_with("Max-Age=") {
final_result = final_result.max_age(Duration::from_secs(
trimmed_part
.strip_prefix("Max-Age=")
.unwrap()
.parse()
.map_err(|_| Self::Err {
inner: CookieError::InvalidMaxAge,
})?,
));
update_map!(map, "Max-Age");
continue;
}
if !map.get("Expires").unwrap() && trimmed_part.starts_with("Expires=") {
final_result = final_result.expires(trimmed_part.strip_prefix("Expires=").unwrap());
update_map!(map, "Expires");
continue;
}
if !map.get("HttpOnly").unwrap() && trimmed_part == "HttpOnly" {
final_result = final_result.http_only(true);
update_map!(map, "HttpOnly");
continue;
}
if !map.get("SameSite").unwrap() && trimmed_part.starts_with("SameSite=") {
final_result = final_result.same_site(
trimmed_part
.strip_prefix("SameSite=")
.unwrap()
.parse::<SameSite>()
.map_err(|err| Self::Err {
inner: CookieError::InvilidSameSite(err.inner),
})?,
);
update_map!(map, "SameSite");
continue;
}
if !map.get("Path").unwrap() && trimmed_part.starts_with("Path=") {
final_result = final_result.path(
trimmed_part
.strip_prefix("Path=")
.unwrap()
.parse::<Uri>()
.map_err(|err| Self::Err {
inner: CookieError::PathError(err.error),
})?,
);
update_map!(map, "Path");
continue;
}
if !map.get("Domain").unwrap() && trimmed_part.starts_with("Domain=") {
final_result = final_result.domain(trimmed_part.strip_prefix("Domain=").unwrap());
update_map!(map, "Domain");
continue;
}
if !map.get("Secure").unwrap() && trimmed_part == "Secure" {
final_result = final_result.secure(true);
update_map!(map, "Secure");
continue;
}
if !map.get("Partitioned").unwrap() && trimmed_part == "Partitioned" {
final_result = final_result.partitioned(true);
update_map!(map, "Partitioned");
continue;
}
}
Ok(final_result.finish())
}
}
#[cfg(test)]
mod test {
use std::time::Duration;
use crate::handling::response::{Cookie, CookieBuilder};
use super::SameSite;
#[test]
fn test_cookie_to_string() {
let test_cookie1 = CookieBuilder::build("a", "cookie").finish().to_string();
let test_cookie1_res = "Set-Cookie: a=cookie";
let test_cookie2 = CookieBuilder::build("a", "secure_cookie")
.secure(true)
.finish()
.to_string();
let test_cookie2_res = "Set-Cookie: a=secure_cookie; Secure";
let test_cookie3 = CookieBuilder::build("ab", "ss")
.max_age(Duration::from_secs(24))
.domain("codecraft.com")
.path("/".parse().unwrap())
.same_site(SameSite::None)
.http_only(true)
.partitioned(true)
.expires("Monday")
.finish();
let test_cookie3_res = "Set-Cookie: ab=ss; Secure; HttpOnly; Partitioned; \
Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Expires=Monday";
assert_eq!(test_cookie1_res, test_cookie1);
assert_eq!(test_cookie2_res, test_cookie2);
assert_eq!(test_cookie3_res, test_cookie3.to_string());
}
#[test]
fn cookie_from_string() {
let test_cookie3_res = "Set-Cookie: ab=ss; HttpOnly; Partitioned; \
Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Secure; Expires=Monday";
let test_cookie3 = CookieBuilder::build("ab", "ss")
.max_age(Duration::from_secs(24))
.domain("codecraft.com")
.path("/".parse().unwrap())
.same_site(SameSite::None)
.http_only(true)
.partitioned(true)
.expires("Monday")
.finish();
assert_eq!(test_cookie3, test_cookie3_res.parse::<Cookie>().unwrap());
}
}
use std::time::Duration;
use crate::utils::{url_utils::Uri, urlencoded::EnCodable};
use super::{Cookie, SameSite};
/// Builder wrapper for a Cookie
///
/// # Example
/// ```
/// use http::handling::response::Cookie;
/// use http::handling::response::CookieBuilder;
///
/// let cookie = CookieBuilder::build("name", "value").path("/".parse().unwrap()).finish();
/// ```
#[derive(Debug)]
pub struct CookieBuilder {
/// Cookie under the hood
inner: Cookie,
}
impl CookieBuilder {
/// Builds a basic CookieBuilder from a name and a value
pub fn build(name: &str, value: &str) -> Self {
CookieBuilder {
inner: Cookie {
cookie_string: None,
name: name.encode(),
value: value.encode(),
max_age: None,
domain: None,
path: None,
secure: false,
http_only: false,
same_site: None,
expires: None,
partitioned: false,
},
}
}
pub fn finish(self) -> Cookie {
self.inner
}
pub fn max_age(mut self, duration: Duration) -> Self {
self.inner.max_age = Some(duration);
self
}
pub fn domain(mut self, domain: &str) -> Self {
self.inner.domain = Some(domain.encode());
self
}
pub fn path(mut self, path: Uri) -> Self {
self.inner.path = Some(path);
self
}
pub fn secure(mut self, secure: bool) -> Self {
self.inner.secure = secure;
self
}
pub fn http_only(mut self, http_only: bool) -> Self {
self.inner.http_only = http_only;
self
}
pub fn same_site(mut self, same_site: SameSite) -> Self {
if same_site == SameSite::None {
self.inner.secure = true;
}
self.inner.same_site = Some(same_site);
self
}
pub fn expires(mut self, expire: &str) -> Self {
self.inner.expires = Some(expire.encode());
self
}
pub fn partitioned(mut self, partitioned: bool) -> Self {
self.inner.partitioned = partitioned;
self
}
pub fn name(mut self, name: &str) -> Self {
self.inner.name = name.encode();
self
}
pub fn value(mut self, value: &str) -> Self {
self.inner.value = value.encode();
self
}
}
use std::error::Error;
use crate::utils::url_utils::UriError;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum SameSiteError {
NotAValidVariant,
}
#[derive(Debug)]
pub struct ParseSameSiteError {
pub inner: SameSiteError,
}
impl Error for ParseSameSiteError {}
impl std::fmt::Display for ParseSameSiteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error for ParseCookieError {}
#[derive(Debug)]
pub struct ParseCookieError {
pub inner: CookieError,
}
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
pub enum CookieError {
MissingEqual,
InvalidAttribute,
InvalidMaxAge,
NoSetCookieHeader,
InvilidSameSite(SameSiteError),
PathError(UriError),
}
impl std::fmt::Display for ParseCookieError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ParseCookieError {{ error: {:?} }}", self.inner)
}
}
mod cookie;
mod cookie_builder;
mod error_types;
pub use cookie::Cookie;
pub use cookie::SameSite;
pub use cookie_builder::CookieBuilder;
pub use error_types::CookieError;
pub use error_types::ParseCookieError;
pub use error_types::ParseSameSiteError;
pub use error_types::SameSiteError;
use super::{Cookie, ResponseBody, Status};
type HeaderMap = Vec<String>;
#[derive(Debug)]
/// Enum for the result of a Handling Function, where...
///
/// [Outcome::Success] represents that the route
/// was successful and the Answer is contained in \[S\].
/// [Outcome::Failure] represents that it was unsuccessful and nobody else is going to be
/// successful. \[E\] represnts the Error Code.
/// [Outcome::Forward] represents that some requirements weren't met for a route to be working with
/// the request so the next one that matches should cover that \[F\] represents the maybe processed
/// data of the request.
///
/// # Example
/// ```
/// use http::handling::{response::{Outcome, Response, Status}, routes::Data, request::Request};
/// fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> {
/// todo!()
/// }
/// ```
pub enum Outcome<S, E, F> {
Success(S),
Failure(E),
Forward(F),
}
/// Response is a wrapper for http responses.
#[derive(Debug)]
pub struct Response {
/// the [`Vec<String>`] of headers unrelated to `Content-Type` and `Content-Length`
pub headers: HeaderMap,
/// Optional Cookie in the response
pub cookies: Option<Cookie>,
/// Status code of the response
pub status: Option<Status>,
/// Response body and `Content-Type` and `Content-Length` headers.
pub body: Box<dyn ResponseBody>,
}
mod build_and_write;
mod cookie_management;
mod datatypes;
mod status;
mod traits;
pub use cookie_management::Cookie;
pub use cookie_management::CookieBuilder;
pub use cookie_management::SameSite;
pub use datatypes::Outcome;
pub use datatypes::Response;
pub use status::Status;
pub use traits::ResponseBody;
use std::fmt::Display;
#[derive(Debug)]
/// Enum With every http status for complete documentation [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
pub enum Status {
Continue,
SwitchingProtocols,
/// WebDAV
Processing,
/// Experimental
EarlyHints,
Ok,
Created,
Accepted,
NonAuthorativeIfnormation,
NoContent,
ResetContent,
PartialContent,
/// WebDAV
MultiStatus,
/// WebDAV
AlreadyReported,
HttpDataEncodingImUsed,
MultipleChoices,
MovedPermanently,
Found,
SeeOther,
NotModfiied,
/// Deprecated
UseProxy,
/// Deprecated
Unused,
TemporaryRedirect,
PermanentRedirect,
BadRequest,
Unauthorized,
/// Experimental
PaymentRequired,
Forbidden,
NotFound,
MethodNotAllowed,
NotAcceptable,
ProxyAuthenticationRequired,
RequestTimeout,
Conflict,
Gone,
LengthRequired,
PreconditionFailed,
PayloadTooLarge,
UriTooLong,
UnsupportedMediaType,
RangeNotSatisfiable,
ExpectationFailed,
ImATeapot,
MisdirectedRequest,
/// WebDAV
UnprocessableContent,
/// WebDAV
Locked,
/// WebDAV
FailedDependency,
/// Experimental
TooEarly,
UpgradeRequred,
PreconditionRequired,
TooManyRequests,
RequestHeaderFieldsTooLarge,
UnavailableForLegalReasons,
InternalServerError,
NotImplemented,
BadGetaway,
ServiceUnavailable,
GetawayTimeout,
HttpVersionNotSupported,
VariantAlsoNegotiates,
/// WebDAV
InsufficientStorage,
/// WebDAV
LoopDetected,
NotExtended,
NetworkAuthenticationRequired,
}
impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Status::Continue => write!(f, "100 Continue"),
Status::SwitchingProtocols => write!(f, "101 Switching Protocols"),
Status::Processing => write!(f, "102 Processing"),
Status::EarlyHints => write!(f, "103 Early Hints"),
Status::Ok => write!(f, "200 OK"),
Status::Created => write!(f, "201 Created"),
Status::Accepted => write!(f, "202 Accepted"),
Status::NonAuthorativeIfnormation => write!(f, "203 Non-Authorative Information"),
Status::NoContent => write!(f, "204 No Content"),
Status::ResetContent => write!(f, "205 Reset Content"),
Status::PartialContent => write!(f, "206 Partial Content"),
Status::MultiStatus => write!(f, "207 Mutli-Status"),
Status::AlreadyReported => write!(f, "208 Already Reported"),
Status::HttpDataEncodingImUsed => write!(f, "226 IM Used"),
Status::MultipleChoices => write!(f, "300 Multiple Choices"),
Status::MovedPermanently => write!(f, "301 Moved Permanently"),
Status::Found => write!(f, "302 Found"),
Status::SeeOther => write!(f, "303 See Other"),
Status::NotModfiied => write!(f, "304 Not Modified"),
Status::TemporaryRedirect => write!(f, "307 Temporary Redirect"),
Status::PermanentRedirect => write!(f, "308 Permanent Redirect"),
Status::UseProxy => write!(f, "305 Use Proxy"),
Status::Unused => write!(f, "306 unused"),
Status::BadRequest => write!(f, "400 Bad Request"),
Status::Unauthorized => write!(f, "401 Unauthorized"),
Status::PaymentRequired => write!(f, "402 Payment Required"),
Status::Forbidden => write!(f, "403 Forbidden"),
Status::NotFound => write!(f, "404 Not Found"),
Status::MethodNotAllowed => write!(f, "405 Method Not Allowed"),
Status::NotAcceptable => write!(f, "406 Not Acceptable"),
Status::ProxyAuthenticationRequired => {
write!(f, "407 Proxy Athentication Required")
}
Status::RequestTimeout => write!(f, "408 Request Timout"),
Status::Conflict => write!(f, "409 Conflict"),
Status::Gone => write!(f, "410 Gone"),
Status::LengthRequired => write!(f, "411 Length Required"),
Status::PreconditionFailed => write!(f, "412 Precondition Failed"),
Status::PayloadTooLarge => write!(f, "413 Payload Too Large"),
Status::UriTooLong => write!(f, "414 URI Too Long"),
Status::UnsupportedMediaType => write!(f, "415 Unsupported Media Type"),
Status::RangeNotSatisfiable => write!(f, "416 Range Not Satisfiable"),
Status::ExpectationFailed => write!(f, "417 Expectation Failed"),
Status::ImATeapot => write!(f, "418 I'm a Teapot"),
Status::MisdirectedRequest => write!(f, "421 Misdirected Request"),
Status::UnprocessableContent => write!(f, "422 Unprocessable Content"),
Status::Locked => write!(f, "423 Locked"),
Status::FailedDependency => write!(f, "424 Failed Dependency"),
Status::TooEarly => write!(f, "425 Too Early"),
Status::UpgradeRequred => write!(f, "426 Upgrade Required"),
Status::PreconditionRequired => write!(f, "428 Precondition Required"),
Status::TooManyRequests => write!(f, "429 Too Many Requests"),
Status::RequestHeaderFieldsTooLarge => {
write!(f, "431 Request Header Fields Too Large")
}
Status::UnavailableForLegalReasons => {
write!(f, "451 Unavailable For Legal Reasons")
}
Status::InternalServerError => write!(f, "500 Internal Server Error"),
Status::NotImplemented => write!(f, "501 Not Implmenented"),
Status::BadGetaway => write!(f, "502 Bad Getaway"),
Status::ServiceUnavailable => write!(f, "503 Service Unavailable"),
Status::GetawayTimeout => write!(f, "504 Getaway Timeout"),
Status::HttpVersionNotSupported => write!(f, "505 HTTP Version Not Supported"),
Status::VariantAlsoNegotiates => write!(f, "506 Variant Also Negotiates"),
Status::InsufficientStorage => write!(f, "507 Insufficient Storage"),
Status::LoopDetected => write!(f, "508 Loop Detected"),
Status::NotExtended => write!(f, "510 Not Extendend"),
Status::NetworkAuthenticationRequired => {
write!(f, "511 Network Authentication Required")
}
}
}
}
use std::fmt::Debug;
use crate::{handling::routes::Body, utils::mime::Mime};
/// Trait for using datatypes as response bodies
pub trait ResponseBody: Send + Debug {
/// Get a cloned version of the data as a [`Vec<u8>`]
/// # Ecamples
/// ```
/// use http::handling::response::ResponseBody;
/// let data = "DATA";
/// assert_eq!(b"DATA".to_vec(), data.get_data());
/// ```
fn get_data(&self) -> Vec<u8>;
/// get the miem type of the data as a [Mime]
/// # Examples
/// ```
/// use http::handling::response::ResponseBody;
/// use http::utils::mime::Mime;
/// let data = "DATA";
/// assert_eq!(Mime::TextPlain, data.get_mime());
/// ```
fn get_mime(&self) -> Mime;
/// get the length in bytes of the data as a [usize]
/// # Examples
/// ```
/// use http::handling::response::ResponseBody;
/// let data = "DATA";
/// assert_eq!(4, data.get_len());
/// ```
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()
}
}
use crate::{
handling::{
methods::Method,
request::{MediaType, Request},
response::{Outcome, Response, Status},
},
utils::{mime::Mime, url_utils::RawUri},
};
/// A RouteBuilder struct
pub struct RouteBuilder {
/// An optional name of the route
name: Option<&'static str>,
/// The [Method] via which the route is accesable
method: Method,
/// The path of the route, allows special cases:
/// # Examples
/// ```
/// "/home"; // Only /home
/// "/<home>/something";
/// // Variable content the users provides this acts for /<anything>/something
/// "/<home..>";
/// // All Information after this sequence is irrelvent
/// // Matches: /a, /a/b/c ...
/// ```
path: &'static str,
/// The Handler function for this route, which gets called when the request need the route.
/// Inputs to the function are an [Request] and the [Data] which represents the body of the
/// [Request]. The Outcome is expected to be an [Outcome], which is a [Response], A [Status] if
/// something went wrong and a [Status] page is need or a [Outcome::Forward] of the requests
/// [Data] for the next [Route] to take care of.
handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
/// The Specific answer format of the [Route] as a [MediaType]. Optional
format: Option<MediaType>,
/// The Optional Rank of the Route, dependent on its specificness. so the rank of a uri `"/home"` would be
/// ranked high, whereas a uri of `"/<anything..>"` would be ranked the lowest
/// If not given generated based on parematers.
rank: Option<isize>,
}
/// A struct to define Routes on the Server
#[derive(Clone)]
pub struct Route {
/// An optional name of the route
pub name: Option<&'static str>,
/// The [Method] via which the route is accesable
pub method: Method,
/// The Uri of the route, allows special cases:
/// # Examples
/// ```
/// "/home"; // Only /home
/// "/<home>/something";
/// // Variable content the users provides this acts for /<anything>/something
/// "/<home..>";
/// // All Information after this sequence is irrelvent
/// // Matches: /a, /a/b/c ...
/// ```
pub uri: RawUri,
/// The Handler function for this route, which gets called when the request need the route.
/// Inputs to the function are an [Request] and the [Data] which represents the body of the
/// [Request]. The Outcome is expected to be an [Outcome], which is a [Response], A [Status] if
/// something went wrong and a [Status] page is need or a [Outcome::Forward] of the requests
/// [Data] for the next [Route] to take care of.
pub handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
/// The Rank of the Route, dependent on its specificness. so the rank of a uri `"/home"` would be
/// ranked high, whereas a uri of `"/<anything..>"` would be ranked the lowest
pub rank: isize,
/// The Specific answer format of the [Route] as a [MediaType]. Optional
pub format: Option<MediaType>,
}
impl Route {
/// generates a Route from a Routebuilder
//TODO: ranking
pub fn from(routeinfo: RouteBuilder) -> Self {
let rank = routeinfo.rank.unwrap_or(0);
Route {
name: routeinfo.name,
method: routeinfo.method,
uri: routeinfo
.path
.try_into()
.unwrap_or_else(|_| panic!("Incorrect RawUri for path {}", routeinfo.path)),
handler: routeinfo.handler,
rank,
format: routeinfo.format,
}
}
}
/// Alias for using a &'a str for Uri
#[derive(Debug, Clone)]
/// A basic Body type for respones
pub struct Body {
/// The Response body
body: Vec<u8>,
/// The Mime Type
mime_type: Mime,
}
impl Body {
/// New body of a Response
pub fn new(body: Vec<u8>, mime_type: Mime) -> Self {
Self { body, mime_type }
}
/// Sets the `mime_type` of the Body
pub fn set_mime_type(&mut self, mime_type: Mime) {
self.mime_type = mime_type;
}
/// Reassigns the body
pub fn set_body(&mut self, body: Vec<u8>) {
self.body = body;
}
/// mime_type of the body
pub fn mime_type(&self) -> Mime {
self.mime_type
}
/// cloned body as [`Vec<u8>`]
pub fn body(&self) -> Vec<u8> {
self.body.clone()
}
}
#[derive(Debug, Clone)]
/// Data of the Body of a [Request]
pub struct Data {
/// The Data
pub buffer: Vec<u8>,
/// For Split Data if it is complete
pub is_complete: bool,
}
impl Data {
/// Checks if the buffer.oen() is -0
pub fn is_empty(&self) -> bool {
self.buffer.len() == 0
}
}
pub fn add(left: usize, right: usize) -> usize { pub mod handlers;
left + right pub mod handling;
} mod setup;
pub mod utils;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {}
use super::*;
#[test] pub use setup::build;
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
use std::thread::available_parallelism;
use tokio::{
net::TcpListener,
select,
signal::unix::{signal, SignalKind, Signal},
};
#[cfg(feature = "secure")]
use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor};
use crate::{
handlers::handler::handle_connection,
handling::routes::Route, utils::url_utils::Uri,
};
#[cfg(feature = "secure")]
use crate::handling::response::{Response, Status};
#[derive(Clone)]
/// Represnts a [MountPoint] that can be mounted in the config
pub struct MountPoint {
/// The prefix of the [MountPoint]
pub mountpoint: Uri,
/// All Routes mounted on the [MountPoint]. The Routes are all prefixed by the mountpoints
/// mountpoint
pub routes: Vec<Route>,
}
/// A server configuration that is run
pub struct Config {
/// Contains an Optional [`Vec<MountPoint>`]. which contains all [MountPoint]s on the Server.
/// Which contain all the [Route]s
mountpoints: Option<Vec<MountPoint>>,
/// Contains a [tokio::net::TcpListener] that is bound for the server
address: TcpListener,
#[cfg(feature = "secure")]
to_secure_redirect: TcpListener,
#[cfg(feature = "secure")]
tls_acceptor: TlsAcceptor,
}
impl<'a> Config {
/// Utility that checks if the given mointpoint is already taken. takes in the uri of the to be
/// mounted mountpoint
fn check_mountpoint_taken(&self, to_insert: &Uri) -> bool {
if let Some(to_check) = &self.mountpoints {
for i in to_check.iter() {
if i.mountpoint == *to_insert {
return true; // Found a duplicate &str
}
}
};
false
}
/// mounts a [MountPoint] on the [Config] takes in a blank [MountPoint] and the [Route]s to be
/// mounted, mounts them and inserts the new [MountPoint]. Also sorts by rank.
pub fn mount(mut self, mountpoint: Uri, mut routes: Vec<Route>) -> Self {
if self.check_mountpoint_taken(&mountpoint) {
eprintln!("\x1b[31mTrying to reassign a mountpoint, mountpoint `{}` already taken.\x1b[0m", mountpoint);
return self;
}
routes.sort_by(|a, b| a.rank.cmp(&b.rank));
let mut mount_message = format!(" >> \x1b[35m{}\x1b[0m\n", mountpoint.to_pretty_print_string());
for (index, route) in routes.iter().enumerate() {
let indent_sign = match index {
i if i == routes.len() - 1 => "└─",
_ => "├─",
};
mount_message += &format!(
" \x1b[35m{indent_sign}\x1b[0m \x1b[36m(\x1b[0m{}\x1b[36m)\x1b[0m \x1b[32m{}\x1b[0m {}\n",
route.name.unwrap_or(""),
route.method,
route.uri.to_pretty_print_string(&mountpoint)
)
}
println!("{mount_message}");
let mut temp_mountpoints = None;
std::mem::swap(&mut self.mountpoints, &mut temp_mountpoints);
if let Some(mut mountpoints) = temp_mountpoints {
mountpoints.push(MountPoint { mountpoint, routes });
self.mountpoints = Some(mountpoints);
} else {
self.mountpoints = Some(vec![MountPoint { mountpoint, routes }]);
}
self
}
/// # Launches/Starts the webserver
/// Launches a Webserver Configuration
///
/// Is Async
///
/// Is Blocking -> Can be interrupted with ^C
///
/// # Panics
/// Panics if there are no Mountpoints in the Confiuration to lauch, as well as when the there
/// is no interrupt signal
pub async fn launch(self) {
{
#[cfg(feature = "secure")] {
println!(
"Server launched from https://{} and http://{}",
self.address.local_addr().unwrap(), self.to_secure_redirect.local_addr().unwrap()
);
}
#[cfg(not(feature = "secure"))] {
println!("Server launched from http://{}", self.address.local_addr().unwrap())
}
}
let mut sigint = signal(SignalKind::interrupt()).unwrap();
let location_string = format!("Location: https://{}", self.address.local_addr().unwrap());
loop {
if !self.selector(&mut sigint, &location_string).await {
break;
}
}
}
#[cfg(feature = "secure")]
async fn selector(&self, sigint: &mut Signal, location_string: &str) -> bool{
select! {
_ = sigint.recv() => {
println!("Shutting down...");
return false;
}
Ok((socket, _)) = self.address.accept() => {
let mountpoints = self.mountpoints.clone().unwrap();
let Ok(socket) = self.tls_acceptor.accept(socket).await else {
eprintln!("\x1b[31mClient used http, not https\x1b[0m");
return true;
};
tokio::spawn(async move { handle_connection(socket, mountpoints).await; });
}
Ok((socket, _)) = self.to_secure_redirect.accept() => {
let redirect_response = Response {
headers: vec![location_string.to_string()],
cookies: None,
status: Some(Status::MovedPermanently),
body: Box::new(""),
};
tokio::spawn(async move { let _ = redirect_response.write(socket, None).await; });
}
}
true
}
#[cfg(not(feature = "secure"))]
async fn selector(&self, sigint: &mut Signal, _location_string: &str) -> bool {
select! {
_ = sigint.recv() => {
println!("Shutting down...");
return false;
}
Ok((socket, _)) = self.address.accept() => {
let mountpoints = self.mountpoints.clone().unwrap();
tokio::spawn(async move { handle_connection(socket, mountpoints).await; });
}
}
true
}
}
/// # Creates a Webserver Config which can be launched with the launch function
/// Takes the IP and Port as an argument
///
/// Prints out the configuration test
///
/// Is async
///
/// # Example
/// ```
/// async fn example() {
/// let _ = http::build("127.0.0.1:8443", "127.0.0.1:8080");
/// }
/// ```
/// # Panics
/// Panics if the IP is not bindable, or other forms of system errors or it's not a valid
/// IP-Address
#[cfg(feature = "secure")]
pub async fn build(ip: &str, ip_http: &str) -> Config {
let Ok(listener) = TcpListener::bind(ip).await else {
panic!("\x1b[31mCould't bind Listener to address\x1b[0m");
};
let ip_vec = ip.splitn(2, ':').collect::<Vec<&str>>();
if ip_vec.len() != 2 {
panic!("Invalid IP Address");
}
let Ok(listener_http) = TcpListener::bind(ip_http).await else {
panic!("\x1b[31mCould't bind Listener to address\x1b[0m");
};
let identity = Identity::from_pkcs12(include_bytes!("certificates/identity.pfx"), "1234").unwrap();
let port = ip_vec[1];
let ip = ip_vec[0];
let workers = available_parallelism().unwrap().get();
println!(
"\x1b[34m⚙ Configuration\x1b[0m
>> \x1b[34mIp\x1b[0m: {ip}
>> \x1b[34mPort\x1b[0m: {port}
>> \x1b[34mWorkers\x1b[0m: {workers}
\x1b[32m Security\x1b[0m
>> \x1b[32mHttp to Https Redirect: http://{ip_http} -> https://{ip}:{port}\x1b[0m
\x1b[35m🛪 Mountpoints\x1b[0m"
);
Config {
mountpoints: None,
address: listener,
to_secure_redirect: listener_http,
tls_acceptor: native_tls::TlsAcceptor::builder(identity).build().unwrap().into()
}
}
#[cfg(not(feature = "secure"))]
pub async fn build(ip: &str) -> Config {
let Ok(listener) = TcpListener::bind(ip).await else {
panic!("\x1b[31mCould't bind Listener to address\x1b[0m");
};
let ip_vec = ip.splitn(2, ':').collect::<Vec<&str>>();
if ip_vec.len() != 2 {
panic!("Invalid IP Address");
}
let port = ip_vec[1];
let ip = ip_vec[0];
let workers = available_parallelism().unwrap().get();
println!(
"\x1b[34m⚙ Configuration\x1b[0m
>> \x1b[34mIp\x1b[0m: {ip}
>> \x1b[34mPort\x1b[0m: {port}
>> \x1b[34mWorkers\x1b[0m: {workers}
\x1b[35m🛪 Mountpoints\x1b[0m"
);
Config {
mountpoints: None,
address: listener,
}
}
This diff is collapsed.