Skip to content
Snippets Groups Projects
Commit 4ee0cffd authored by codecraft's avatar codecraft :crocodile:
Browse files

Merge branch 'dev' into 'main'

Project Completed

See merge request !2
parents c4e5ed7a 7f78d269
No related branches found
No related tags found
1 merge request!2Project Completed
Showing
with 168 additions and 164 deletions
......@@ -20,7 +20,6 @@ git clone https://edugit.org/codecraft/webserver
```
cd webserver
git checkout new
cd site
cargo run
```
......
/target
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "codegen"
version = "0.1.0"
[package]
name = "codegen"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
/target
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "html"
version = "0.1.0"
[package]
name = "html"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
[package]
name = "http"
version = "0.1.0"
version = "1.0.0"
edition = "2021"
[features]
default = []
secure = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
......
use std::{io, path::PathBuf};
use std::{io, path::PathBuf, ops::Deref};
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader, AsyncWrite, AsyncRead};
......@@ -8,8 +8,7 @@ use crate::{handling::{
request::Request,
response::{Outcome, Response, ResponseBody, Status},
routes::{Body, Data},
}, utils::urlencoded::UrlEncodeData};
use crate::setup::MountPoint;
}, setup::MountPoint};
/// The Maximal size of the Body of an HTTP-Message in bytes
static MAX_HTTP_MESSAGE_SIZE: u16 = 4196;
......@@ -25,7 +24,7 @@ static MAX_HTTP_MESSAGE_SIZE: u16 = 4196;
///
/// # Panics
/// No Panics
pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(mut stream: T, mountpoints: Vec<MountPoint<'_>>) {
pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(mut stream: T, mountpoints: Vec<MountPoint>) {
let mut buf_reader = BufReader::new(&mut stream);
let mut http_request: Vec<String> = Vec::with_capacity(10);
loop {
......@@ -49,7 +48,7 @@ pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(m
let mut request = Request {
uri: if let Some(uri) = &request_status_line.split(' ').nth(1) {
if let Ok(uri) = UrlEncodeData::from_encoded(uri) {
if let Ok(uri) = uri.deref().try_into() {
uri
} else {
eprintln!("\x1b[31mAborting due to invalid uri\x1b[0m");
......@@ -132,29 +131,19 @@ pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(m
let mut handled_response: Option<Outcome<Response, Status, Data>> = None;
for mountpoint in mountpoints {
if request.uri.raw_string().is_none() {
return;
}
if !request.uri.raw_string().unwrap().starts_with(mountpoint.mountpoint) {
if !mountpoint.compare_with_uri(&mut request.uri) {
continue;
}
let mounted_request_uri = request.uri.raw_string().unwrap().strip_prefix(mountpoint.mountpoint).unwrap();
for route in mountpoint.routes {
if (route.method != request.method)
&& ((route.method != Method::Get) && (request.method == Method::Head))
{
continue;
}
if !route.compare_uri(mounted_request_uri) {
if !route.uri.compare_uri(&request.uri) {
continue;
}
handled_response = Some((route.handler)(
Request {
uri: UrlEncodeData::from_raw(mounted_request_uri),
..request.clone()
},
data.clone(),
));
handled_response = Some((route.handler)(request.clone(), data.clone()));
if let Some(Outcome::Forward(_)) = handled_response {
continue;
......
......@@ -2,7 +2,7 @@ use std::{collections::HashMap, error::Error, fmt::Display};
use crate::{
handling::methods::Method,
utils::{mime::Mime, urlencoded::UrlEncodeData},
utils::{mime::Mime, url_utils::Uri},
};
type HeaderMap = Vec<String>;
......@@ -12,7 +12,7 @@ type HeaderMap = Vec<String>;
#[derive(Clone)]
pub struct Request {
/// The requested Uri
pub uri: UrlEncodeData,
pub uri: Uri,
/// All headers of the request that haven't been parsed
pub headers: HeaderMap,
/// The methods Request represented with the [Method]
......
......@@ -66,10 +66,15 @@ impl Request {
&'a self,
keys: &'a [&str],
) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> {
let Some(uri) = self.uri.raw_string() else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let data = if let Some(val) = uri.split_once('?') {
let data = if let Some(val) = self
.uri
.parts()
.last()
.unwrap()
.raw_string()
.unwrap()
.split_once('?')
{
val
} else {
return Err(ParseFormError {
......@@ -249,17 +254,14 @@ mod test {
request::{datatypes::ParseErrors, ParseFormError},
routes::Data,
},
utils::{
mime::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData},
urlencoded::UrlEncodeData,
},
utils::mime::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData},
};
use super::Request;
#[test]
fn try_get_test() {
let request = Request {
uri: UrlEncodeData::from_encoded("/form?name=Name&age=Age").unwrap(),
uri: "/form?name=Name&age=Age".try_into().unwrap(),
headers: vec![],
method: Method::Get,
cookies: None,
......@@ -270,7 +272,7 @@ mod test {
assert_eq!(&"Age", right.get("age").unwrap().as_ref().unwrap());
let wrong_request = Request {
uri: UrlEncodeData::from_encoded("/form").unwrap(),
uri: "/form".try_into().unwrap(),
..request.clone()
};
assert_eq!(
......@@ -281,7 +283,7 @@ mod test {
);
let bad_data = Request {
uri: UrlEncodeData::from_encoded("/form?age=").unwrap(),
uri: "/form?age=".try_into().unwrap(),
..request.clone()
};
let wrong = bad_data.get_get_form_keys(&["name", "age"]).unwrap();
......@@ -297,7 +299,7 @@ mod test {
#[test]
fn try_post_text() {
let req = Request {
uri: UrlEncodeData::from_encoded("").unwrap(),
uri: "".try_into().unwrap(),
headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()],
method: Method::Post,
cookies: None,
......@@ -317,7 +319,7 @@ mod test {
map.get("message1").unwrap().as_ref().unwrap()
);
let req = Request {
uri: UrlEncodeData::from_encoded("").unwrap(),
uri: "".try_into().unwrap(),
headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()],
method: Method::Post,
cookies: None,
......
......@@ -69,15 +69,16 @@ impl Request {
#[cfg(test)]
mod test {
use crate::{
handling::{methods::Method, request::Request},
utils::{mime::Mime, urlencoded::UrlEncodeData},
utils::mime::Mime,
};
#[test]
pub fn test_mime_parse_from_header_vec() {
let mut request = Request {
uri: UrlEncodeData::from_raw("thing"),
uri: "thing".try_into().unwrap(),
headers: vec![
"GET / 23".to_string(),
"SDF:LKJSD:F".to_string(),
......@@ -90,7 +91,7 @@ mod test {
};
let mut wrong = Request {
uri: UrlEncodeData::from_raw("thing"),
uri: "thing".try_into().unwrap(),
headers: vec![
"GET / 23".to_string(),
"SDF:LKJSD:F".to_string(),
......
......@@ -4,7 +4,7 @@ use crate::{
request::{MediaType, Request},
response::{Outcome, Response, Status},
},
utils::mime::Mime,
utils::{mime::Mime, url_utils::RawUri},
};
/// A RouteBuilder struct
......@@ -39,8 +39,8 @@ pub struct RoutBuilder {
}
/// A struct to define Routes on the Server
#[derive(Clone, Copy)]
pub struct Route<'a> {
#[derive(Clone)]
pub struct Route {
/// An optional name of the route
pub name: Option<&'static str>,
/// The [Method] via which the route is accesable
......@@ -55,7 +55,7 @@ pub struct Route<'a> {
/// // All Information after this sequence is irrelvent
/// // Matches: /a, /a/b/c ...
/// ```
pub uri: Uri<'a>,
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
......@@ -69,7 +69,7 @@ pub struct Route<'a> {
pub format: Option<MediaType>,
}
impl Route<'_> {
impl Route {
/// generates a Route from a Routebuilder
//TODO: ranking
pub fn from(routeinfo: RoutBuilder) -> Self {
......@@ -77,39 +77,18 @@ impl Route<'_> {
Route {
name: routeinfo.name,
method: routeinfo.method,
uri: routeinfo.path,
uri: routeinfo
.path
.try_into()
.unwrap_or_else(|_| panic!("Incorrect RawUri for path {}", routeinfo.path)),
handler: routeinfo.handler,
rank,
format: routeinfo.format,
}
}
/// Matches a [Request] Uri with a [Route] Uri. Respecting special cases like `?` and `<a..>`
pub fn compare_uri(&self, uri: Uri) -> bool {
let mut iter_comp_str = uri.split('/');
for true_str in self.uri.split('/') {
let comp_str = if let Some(str) = iter_comp_str.next() {
str
} else {
return false;
};
if (true_str.starts_with('<') && true_str.ends_with("..>"))
|| (comp_str.starts_with(true_str) && comp_str.contains('?'))
{
return true;
}
if true_str.starts_with('<') && true_str.ends_with('>') {
continue;
}
if true_str != comp_str {
return false;
}
}
true
}
}
/// Alias for using a &'a str for Uri
pub type Uri<'a> = &'a str;
#[derive(Debug, Clone)]
/// A basic Body type for respones
......
......@@ -11,7 +11,7 @@ use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor};
use crate::{
handlers::handler::handle_connection,
handling::routes::{Route, Uri},
handling::routes::Route, utils::url_utils::Uri,
};
#[cfg(feature = "secure")]
use crate::handling::response::{Response, Status};
......@@ -19,19 +19,19 @@ use crate::handling::response::{Response, Status};
#[derive(Clone)]
/// Represnts a [MountPoint] that can be mounted in the config
pub struct MountPoint<'a> {
pub struct MountPoint {
/// The prefix of the [MountPoint]
pub mountpoint: Uri<'a>,
pub mountpoint: Uri,
/// All Routes mounted on the [MountPoint]. The Routes are all prefixed by the mountpoints
/// mountpoint
pub routes: Vec<Route<'a>>,
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<'static>>>,
mountpoints: Option<Vec<MountPoint>>,
/// Contains a [tokio::net::TcpListener] that is bound for the server
address: TcpListener,
#[cfg(feature = "secure")]
......@@ -40,13 +40,14 @@ pub struct Config {
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 {
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 {
if i.mountpoint == *to_insert {
return true; // Found a duplicate &str
}
}
......@@ -55,13 +56,13 @@ impl<'a> Config {
}
/// 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<'static>, mut routes: Vec<Route<'static>>) -> Self {
if self.check_mountpoint_taken(mountpoint) {
eprintln!("\x1b[31mTrying to reassign a mountpoint, mountpoint `{mountpoint}` already taken.\x1b[0m");
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);
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 => "└─",
......@@ -69,11 +70,10 @@ impl<'a> Config {
};
mount_message += &format!(
" \x1b[35m{indent_sign}\x1b[0m \x1b[36m(\x1b[0m{}\x1b[36m)\x1b[0m \x1b[32m{}\x1b[0m \x1b[34;4m{}\x1b[24m{}\x1b[0m\n",
" \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,
mountpoint,
route.uri
route.uri.to_pretty_print_string(&mountpoint)
)
}
......
pub mod mime;
pub mod url_utils;
pub mod urlencoded;
pub mod vec_utils;
......@@ -5,16 +5,17 @@ use crate::utils::urlencoded::UrlEncodeData;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct Uri {
pub(super) parts: Vec<UrlEncodeData>,
pub raw: String,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct RawUri {
pub(super) raw_string: String,
pub(super) infinte_end: bool,
pub(super) parts: Vec<RawUriElement>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum RawUriElement {
Variable,
Name(UrlEncodeData),
......
use std::str::FromStr;
use crate::utils::{url_utils::datatypes::RawUriElement, urlencoded::UrlEncodeData};
use crate::{
setup::MountPoint,
utils::{
url_utils::datatypes::RawUriElement,
urlencoded::{EnCodable, UrlEncodeData},
vec_utils::remove_n,
},
};
use super::datatypes::{ParseUriError, RawUri, Uri, UriError};
use super::datatypes::{ParseUriError, RawUri, Uri};
impl MountPoint {
pub fn compare_with_uri(&self, uri: &mut Uri) -> bool {
if !uri.raw.starts_with(&self.mountpoint.raw) {
return false;
}
let to_remove_after_finish = self.mountpoint.parts.len();
remove_n(uri.mut_parts(), to_remove_after_finish);
true
}
}
impl Uri {
pub fn new(parts: Vec<&str>) -> Self {
Self {
raw: "/".to_owned()
+ &parts
.iter()
.map(|part| part.encode())
.collect::<Vec<String>>()
.join("/"),
parts: parts.into_iter().map(UrlEncodeData::from_raw).collect(),
}
}
pub fn parts(&self) -> &[UrlEncodeData] {
self.parts.as_slice()
}
pub fn mut_parts(&mut self) -> &mut Vec<UrlEncodeData> {
self.parts.as_mut()
}
pub fn compare(&self, uri: &Uri) -> bool {
let mut uri_iter = uri.parts.iter();
for part in self.parts.iter() {
let Some(part_uri) = uri_iter.next() else {
return false;
};
if part != part_uri {
return false;
}
}
true
}
pub(crate) fn to_pretty_print_string(&self) -> String {
if self.parts.is_empty() {
"/".to_string()
} else {
let url = self
.parts
.iter()
.map(|part| part.encoded())
.collect::<Vec<_>>();
"/".to_string() + &url.join("/")
}
}
}
impl RawUri {
......@@ -17,7 +71,7 @@ impl RawUri {
let mut result = Self {
infinte_end: false,
parts: Vec::with_capacity(parts.len()),
raw_string: "/".to_owned() + &parts.join("/"),
raw_string: parts.join("/"),
};
for part in parts {
if part.starts_with('<') && part.ends_with("..>") {
......@@ -34,15 +88,15 @@ impl RawUri {
}
result
}
pub fn compare_uri(self, uri: Uri) -> bool {
pub fn compare_uri(&self, uri: &Uri) -> bool {
let mut iter_comp = uri.parts.iter();
let mut counter = 0;
if uri.parts().len() != self.parts.len() && !self.infinte_end {
return false;
}
for element in self.parts.iter() {
counter += 1;
let Some(compare_element) = iter_comp.next() else {
return false;
};
if *element == RawUriElement::Variable {
continue;
}
......@@ -54,11 +108,19 @@ impl RawUri {
return false;
}
}
if counter > self.parts.len() && !self.infinte_end {
return false;
if uri.parts.len() > self.parts.len() && self.infinte_end {
return true;
}
true
}
pub(crate) fn to_pretty_print_string(&self, is_after: &Uri) -> String {
let is_after = is_after.to_pretty_print_string();
if is_after == "/" {
format!("\x1b[34;4m/\x1b[24m{}\x1b[0m", self)
} else {
format!("\x1b[34;4m{is_after}/\x1b[24m{self}\x1b[0m")
}
}
}
impl std::fmt::Display for RawUri {
......@@ -69,12 +131,16 @@ impl std::fmt::Display for RawUri {
impl std::fmt::Display for Uri {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let url = self
.parts
.iter()
.map(|part| part.encoded())
.collect::<Vec<_>>();
write!(f, "/{}", url.join("/"))
if self.parts.is_empty() {
write!(f, "/")
} else {
let url = self
.parts
.iter()
.map(|part| part.encoded())
.collect::<Vec<_>>();
write!(f, "{}", url.join("/"))
}
}
}
......@@ -93,19 +159,21 @@ impl FromStr for Uri {
UrlEncodeData::from_raw(sub)
});
}
Ok(Self { parts: result })
Ok(Self {
parts: result,
raw: s.to_string(),
})
}
}
impl FromStr for RawUri {
type Err = UriError;
type Err = ParseUriError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split('/').collect::<Vec<&str>>();
let mut result = Self {
infinte_end: false,
parts: Vec::new(),
raw_string: parts.join("/"),
raw_string: s.to_string(),
};
for part in parts {
if part.is_empty() {
......@@ -127,6 +195,21 @@ impl FromStr for RawUri {
}
}
impl TryFrom<&str> for Uri {
type Error = ParseUriError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::from_str(value)
}
}
impl TryFrom<&str> for RawUri {
type Error = ParseUriError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::from_str(value)
}
}
#[cfg(test)]
mod test {
use crate::utils::{
......@@ -139,7 +222,7 @@ mod test {
#[test]
fn uri_to_string() {
assert_eq!("/a/%20", Uri::new(vec!["a", " "]).to_string());
assert_eq!("a/%20", Uri::new(vec!["a", " "]).to_string());
}
#[test]
......
pub(crate) fn remove_n<T>(vec: &mut Vec<T>, n: usize) {
if n > vec.len() {
return;
}
for i in 0..n {
vec.remove(i);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment