use std::thread::available_parallelism; use tokio::{ net::TcpListener, select, signal::unix::{signal, SignalKind}, }; use crate::{ handlers::handler::handle_connection, handling::routes::{Route, Uri}, }; #[derive(Clone)] pub struct MountPoint<'a> { pub mountpoint: Uri<'a>, pub routes: Vec<Route<'a>>, } pub struct Config { mountpoints: Option<Vec<MountPoint<'static>>>, address: TcpListener, } impl<'a> Config { 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 } 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"); return self; } routes.sort_by(|a, b| a.rank.cmp(&b.rank)); let mut mount_message = format!(" >> \x1b[35m{}\x1b[0m\n", mountpoint); 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 \x1b[34;4m{}\x1b[24m{}\x1b[0m\n", route.name.unwrap_or(""), route.method, mountpoint, route.uri ) } 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) { println!( "Server launched from http://{}", self.address.local_addr().unwrap() ); let mut sigint = signal(SignalKind::interrupt()).unwrap(); loop { select! { _ = sigint.recv() => { println!("Shutting down..."); break; } Ok((socket, _)) = self.address.accept() => { let mountpoints = self.mountpoints.clone().unwrap(); tokio::spawn(async move { handle_connection(socket, mountpoints).await; }); } } } } } /// # 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:8000"); /// } /// ``` /// # Panics /// Panics if the IP is not bindable, or other forms of system errors or it's not a valid /// IP-Address pub async fn build(ip: &str) -> Config { let listener = if let Ok(listener) = TcpListener::bind(ip).await { listener } else { panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); }; let ip = ip.splitn(2, ':').collect::<Vec<&str>>(); if ip.len() != 2 { panic!("Invalid IP Address"); } let port = ip[1]; let ip = ip[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, } }