From a6ffcabd22044eb87ce6f2c28ce7405967ab696a Mon Sep 17 00:00:00 2001
From: Darius Auding <Darius.auding@gmx.de>
Date: Sun, 14 May 2023 13:25:15 +0200
Subject: [PATCH] Add addable routes

---
 core/http/src/handlers/handlers.rs | 112 ++++++++++++++---------------
 core/http/src/lib.rs               |   4 +-
 core/http/src/routing/methods.rs   |   2 +-
 core/http/src/routing/routes.rs    |  42 ++++++++---
 core/http/src/setup.rs             |  42 ++++++-----
 site/404.html                      |   6 +-
 site/src/main.rs                   |  43 ++++++++++-
 7 files changed, 163 insertions(+), 88 deletions(-)
 mode change 100644 => 100755 core/http/src/handlers/handlers.rs

diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs
old mode 100644
new mode 100755
index 4f9073e..c3e06cf
--- a/core/http/src/handlers/handlers.rs
+++ b/core/http/src/handlers/handlers.rs
@@ -1,16 +1,15 @@
 use std::{
-    fs,
     io::{BufRead, BufReader, Write},
     net::TcpStream,
     path::PathBuf,
 };
 
-use crate::routing::methods::Method;
 use crate::routing::routes::{Data, Outcome, Request, Response, Status};
+use crate::setup::MountPoint;
 
 use super::file_handlers::NamedFile;
 
-pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) {
+pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) {
     let buf_reader = BufReader::new(&mut stream);
     let http_request: Vec<String> = buf_reader
         .lines()
@@ -25,7 +24,12 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) {
     let request = Request {
         uri: &request_status_line.split(" ").nth(1).unwrap(),
         headers: http_request.clone(),
-        method: Method::Get,
+        method: request_status_line
+            .split(" ")
+            .next()
+            .unwrap()
+            .parse()
+            .unwrap(),
     };
 
     let data = Data {
@@ -33,65 +37,57 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) {
         is_complete: true,
     };
 
-    let handled_response = match handler(request, data) {
-        Outcome::Success(success) => (
-            format!("HTTP/1.1 {} OK\r\n", success.status.unwrap().code)
-                + &success.headers.join("\r\n")
-                + "\r\n\r\n",
-            success.body.get_data(),
-        ),
-        Outcome::Failure(error) => {
-            let content = fs::read("./404.html").unwrap();
-            (
-                format!(
-                    "HTTP/1.1 {} NOT FOUND\r\nContent-Length: {}\r\nContent-Type: text/html\r\n\r\n",
-                    error.code,
-                    content.len()
-                ),
-                content,
-            )
+    let mut handled_response: Option<Outcome<Response, Status, Data>> = None;
+    for mountpoint in mountpoints {
+        if !request.uri.starts_with(mountpoint.mountpoint) {
+            println!("MOUNTPOINT COMPARISON {}", request.uri);
+            continue;
+        }
+        let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap();
+        for route in mountpoint.routes {
+            if route.method != request.method {
+                println!("METHOD COMPARE {}", request.method);
+                continue;
+            }
+            if !route.compare_uri(mounted_request_uri) {
+                println!("URI COMPARISON {} {}", mounted_request_uri, route.uri);
+                continue;
+            }
+            handled_response = Some((route.handler)(
+                Request {
+                    uri: mounted_request_uri,
+                    ..request.clone()
+                },
+                data.clone(),
+            ));
         }
-        Outcome::Forward(_) => (
-            format!("HTTP/1.1 {} NOT FOUND", 404),
-            fs::read("./404.html").unwrap(),
-        ),
-    };
-
-    stream.write_all(handled_response.0.as_bytes()).unwrap();
-    stream.write(&handled_response.1).unwrap();
-}
-
-fn parse_request<'a>(request: &'a str, mountpoint: &'a str) -> Result<PathBuf, &'a str> {
-    let uri = request.split(" ").nth(1).unwrap();
-    if !uri.starts_with(mountpoint) {
-        return Err("Request isn't on mountpoint");
     }
-    let path_elements = uri
-        .split_once(mountpoint)
-        .map(|(_, rest)| rest)
-        .unwrap()
-        .split("/")
-        .filter(|&val| val != ".." && val != "")
-        .collect::<Vec<&str>>();
-    Ok(PathBuf::from(format!("./{}", path_elements.join("/"))))
-}
 
-fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> {
-    let response = fileserver(request.uri);
-    let response = match response {
-        Ok(dat) => Response {
-            headers: vec![
-                format!("Content-Length: {}", dat.content_len),
-                format!("Content-Type: {}", dat.content_type),
-            ],
-            status: Some(Status { code: 200 }),
-            body: Box::new(dat),
+    let response = match handled_response {
+        Some(val) => match val {
+            Outcome::Success(success) => (
+                format!("HTTP/1.1 {} OK\r\n", success.status.unwrap().code)
+                    + &success.headers.join("\r\n")
+                    + "\r\n\r\n",
+                success.body.get_data(),
+            ),
+            Outcome::Failure(error) => handler_404(error),
+            Outcome::Forward(_) => handler_404(Status { code: 404 }),
         },
-        Err(_) => return Outcome::Failure(Status { code: 404 }),
+        None => handler_404(Status { code: 404 }),
     };
-    Outcome::Success(response)
+
+    stream.write_all(response.0.as_bytes()).unwrap();
+    stream.write(&response.1).unwrap();
 }
 
-fn fileserver(path: &str) -> Result<NamedFile, Status> {
-    NamedFile::open(PathBuf::from("static/".to_string() + path))
+fn handler_404(status: Status) -> (String, Vec<u8>) {
+    let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap();
+    (
+        format!(
+            "HTTP/1.1 {} NOT FOUND\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n",
+            status.code, page_404.content_len, page_404.content_type
+        ),
+        page_404.content,
+    )
 }
diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs
index 0b86db5..bf072c9 100644
--- a/core/http/src/lib.rs
+++ b/core/http/src/lib.rs
@@ -1,5 +1,5 @@
-mod handlers;
-mod routing;
+pub mod handlers;
+pub mod routing;
 mod setup;
 mod threading;
 
diff --git a/core/http/src/routing/methods.rs b/core/http/src/routing/methods.rs
index 328e697..998cfa5 100644
--- a/core/http/src/routing/methods.rs
+++ b/core/http/src/routing/methods.rs
@@ -1,6 +1,6 @@
 use std::{fmt::Display, str::FromStr};
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Debug, Copy)]
 pub enum Method {
     Get,
     Head,
diff --git a/core/http/src/routing/routes.rs b/core/http/src/routing/routes.rs
index 05ecf74..6da1218 100644
--- a/core/http/src/routing/routes.rs
+++ b/core/http/src/routing/routes.rs
@@ -15,13 +15,14 @@ pub struct RoutInfo {
     rank: Option<isize>,
 }
 
+#[derive(Clone, Copy)]
 pub struct Route<'a> {
-    name: Option<&'static str>,
-    method: Method,
-    uri: Uri<'a>,
-    handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
-    rank: isize,
-    format: Option<MediaType>,
+    pub name: Option<&'static str>,
+    pub method: Method,
+    pub uri: Uri<'a>,
+    pub handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
+    pub rank: isize,
+    pub format: Option<MediaType>,
 }
 
 impl Route<'_> {
@@ -36,8 +37,29 @@ impl Route<'_> {
             format: routeinfo.format,
         }
     }
+    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("..>") {
+                return true;
+            }
+            if true_str.starts_with("<") && true_str.ends_with(">") {
+                continue;
+            }
+            if true_str != comp_str {
+                return false;
+            }
+        }
+        return true;
+    }
 }
 
+#[derive(Clone)]
 pub struct Request<'a> {
     pub uri: Uri<'a>,
     pub headers: HeaderMap,
@@ -51,15 +73,17 @@ struct ConnectionMeta {
 }
 
 type HeaderMap = Vec<String>;
-type Uri<'a> = &'a str;
+pub type Uri<'a> = &'a str;
 
+#[derive(Debug)]
 pub enum Outcome<S, E, F> {
     Success(S),
     Failure(E),
     Forward(F),
 }
 
-enum MediaType {
+#[derive(Clone, Debug, Copy)]
+pub enum MediaType {
     Json,
     Plain,
     Html,
@@ -70,6 +94,7 @@ pub struct Status {
     pub code: u16,
 }
 
+#[derive(Debug)]
 pub struct Body {
     pub size: Option<usize>,
     pub body: Vec<u8>,
@@ -87,6 +112,7 @@ pub struct Response {
     pub body: Box<dyn ResponseBody>,
 }
 
+#[derive(Debug, Clone)]
 pub struct Data {
     pub buffer: Vec<u8>,
     pub is_complete: bool,
diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs
index d3b106c..35c8a06 100644
--- a/core/http/src/setup.rs
+++ b/core/http/src/setup.rs
@@ -7,26 +7,36 @@ use std::{
     thread::available_parallelism,
 };
 
-use crate::{handlers::handlers::handle_connection, routing::routes::Route, threading::ThreadPool};
+use crate::{
+    handlers::handlers::handle_connection,
+    routing::routes::{Route, Uri},
+    threading::ThreadPool,
+};
+
+#[derive(Clone)]
+pub struct MountPoint<'a> {
+    pub mountpoint: Uri<'a>,
+    pub routes: Vec<Route<'a>>,
+}
 
-pub struct Config<'a> {
-    mountpoints: Option<Vec<&'static str>>,
+pub struct Config {
+    mountpoints: Option<Vec<MountPoint<'static>>>,
     address: TcpListener,
     threadpool: ThreadPool,
-    routes: Option<Vec<Route<'a>>>,
 }
 
-impl<'a> Config<'a> {
-    pub fn mount(self, mountpoint: &'static str, routes: Vec<Route<'static>>) -> Config<'a> {
-        if self.mountpoints == None {
-            return Config {
-                mountpoints: Some(vec![mountpoint]),
-                routes: Some(routes),
-                ..self
-            };
+impl<'a> Config {
+    pub fn mount(mut self, mountpoint: Uri<'static>, routes: Vec<Route<'static>>) -> Config {
+        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 {
-            return self;
+            self.mountpoints = Some(vec![MountPoint { mountpoint, routes }]);
         }
+        self
     }
     pub fn launch(self) {
         let running = Arc::new(AtomicBool::new(true));
@@ -42,15 +52,14 @@ impl<'a> Config<'a> {
             running_for_handler.store(false, atomic::Ordering::SeqCst);
         })
         .expect("Error setting Ctrl-C handler");
-
         for stream in self.address.incoming() {
             if !running.load(atomic::Ordering::SeqCst) {
                 break;
             }
             let stream = stream.unwrap();
-            let mountpoint = self.mountpoints.clone().unwrap();
+            let mountpoints = self.mountpoints.clone().unwrap();
             self.threadpool
-                .execute(move || handle_connection(stream, &mountpoint[0].to_string()))
+                .execute(move || handle_connection(stream, mountpoints));
         }
     }
 }
@@ -75,7 +84,6 @@ Server has launched from {ip}:{port}.
     );
     Config {
         mountpoints: None,
-        routes: None,
         address: listener,
         threadpool,
     }
diff --git a/site/404.html b/site/404.html
index 3a6ceb5..550831f 100644
--- a/site/404.html
+++ b/site/404.html
@@ -3,10 +3,14 @@
   <head>
     <meta charset="utf-8" />
     <title>404</title>
-    <link rel="stylesheet" href="/hello.css" />
   </head>
   <body>
     <h1>404</h1>
     <p>Hi from Rust</p>
+    <form action="/" method="POST">
+      <label for="message">Enter your message:</label>
+      <input type="text" id="message" name="message" />
+      <button type="submit">Send</button>
+    </form>
   </body>
 </html>
diff --git a/site/src/main.rs b/site/src/main.rs
index 55ea439..8974db9 100644
--- a/site/src/main.rs
+++ b/site/src/main.rs
@@ -1,3 +1,44 @@
+use std::path::PathBuf;
+
+use http::{
+    handlers::file_handlers::NamedFile,
+    routing::routes::{Data, Outcome, Request, Response, Route, Status},
+};
+
+fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> {
+    println!("called");
+    let response = fileserver(request.uri.strip_prefix("static/").unwrap());
+    let response = match response {
+        Ok(dat) => Response {
+            headers: vec![
+                format!("Content-Length: {}", dat.content_len),
+                format!("Content-Type: {}", dat.content_type),
+            ],
+            status: Some(Status { code: 200 }),
+            body: Box::new(dat),
+        },
+        Err(_) => return Outcome::Failure(Status { code: 404 }),
+    };
+    println!("{:?}", response.headers);
+    Outcome::Success(response)
+}
+
+fn fileserver(path: &str) -> Result<NamedFile, Status> {
+    let file = NamedFile::open(PathBuf::from("static/".to_string() + path));
+    return file;
+}
+
 fn main() {
-    http::build("127.0.0.1:8000").mount("/", vec![]).launch();
+    let fileserver = Route {
+        format: None,
+        handler,
+        name: Some("file_server"),
+        uri: "static/<path..>",
+        method: http::routing::methods::Method::Get,
+        rank: 0,
+    };
+
+    http::build("127.0.0.1:8000")
+        .mount("/", vec![fileserver])
+        .launch();
 }
-- 
GitLab