rambl_rs 0.1.10

An HTTP server framework
Documentation
#![allow(dead_code)]
use std::{borrow::Cow, cell::OnceCell, collections::HashMap, pin::Pin, sync::Arc};
use tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, net::{TcpListener, TcpStream, ToSocketAddrs}, sync::Mutex, time::{timeout, Duration}};

use crate::{Context, Handler::{self, *}, HandlerFunc, Request, RequestType, Responder, Response};

async fn res_request(mut stream: TcpStream,msg: &str, res: &str) {
    let _ = stream.write_all(format!("HTTP/1.1 {}\r\nContent-Length: {}\r\n\r\n{}", msg, res.len(), res).as_bytes()).await;
    let _ = stream.flush().await;
}

/// The main struct in the crate. 
/// Allows you to create HTTP web servers.
pub struct HttpServer<State = ()> {
    routes: std::sync::Mutex<HashMap<(RequestType, Cow<'static, str>), Handler<State>>>,
    state: Mutex<Arc<Option<State>>>
}

impl<State: Send + Sync + 'static> HttpServer<State> {

    /// Returns an instance of HttpServer
    pub fn new() -> HttpServer<State> {
        HttpServer {  
            routes: std::sync::Mutex::new(HashMap::new()),
            state: Mutex::new(Arc::new(None)),
        }
    }

    /// Sets the server state, State is kept inside a Arc<Option<()>>
    /// 
    /// # Examples
    /// 
    /// ```
    /// async fn index(_res: Responder, ctx: Context<i32>) {
    ///     assert_eq!(ctx.state.unwrap(), 0);
    /// }
    /// 
    /// #[tokio::main]
    /// async fn main() {
    ///     let server = HttpServer::new();
    ///     
    ///     server.manage(0);
    ///     server.on(Get, "/", index);
    ///     
    ///     server.listen("127.0.0.1:8000").await.unwrap();
    /// }
    /// ```
    pub fn manage(&self, state: State) 
    {
        (*self.state.try_lock().unwrap()) = Arc::new(Some(state));
    }

    /// Maps a route handler to the specified route signature (eg. ("GET", "/"))
    /// 
    /// # Examples
    /// 
    /// ```
    /// async fn index(res: Responder, ctx: Context) {
    ///     res.respond(ctx.response.body).await.unwwrap();
    /// }
    /// 
    /// #[tokio::main]
    /// async fn main() {
    ///     let server = HttpServer::new();
    ///     
    ///     server.on(Get, "/", index);
    ///     
    ///     server.listen("127.0.0.1:8000").await.unwrap();
    /// }
    /// ```
    pub fn on<T, U>(&self, req_type: RequestType, route: U, handler: T) 
    where 
        U: Into<Cow<'static, str>>,
        T: HandlerFunc<State> + 'static
    {
        self.routes.lock().unwrap().insert((req_type, route.into()), Handler::Active(Box::new(handler)));
    }

    /// Serves static information, under the hood, it calls a Responder.
    /// 
    /// # Examples
    /// 
    /// ```
    /// #[tokio::main]
    /// async fn main() {
    ///     let server = HttpServer::new();
    ///     let res = HttpResponse::new();
    ///     
    ///     res.body("Hello World");
    ///     server.on_static(Get, "/", res);
    ///     
    ///     server.listen("127.0.0.1:8000").await.unwrap();
    /// }
    /// ```
    pub fn on_static<T, U>(&self, req_type: RequestType, route: U, res: T) 
    where
        U: Into<Cow<'static, str>>,
        T: Response + 'static 
    {
        self.routes.lock().unwrap().insert((req_type, route.into()), Handler::Static(Box::new(res)));
    }

    /// Activates the HttpServer
    pub async fn listen<A: ToSocketAddrs>(self, addr: A) -> io::Result<()> {
        let listener = TcpListener::bind(addr).await?;
        let routes = Arc::new(self.routes.into_inner().unwrap());
        let state = self.state.into_inner(); 

        loop {
            let stream = if let Ok(e) = listener.accept().await {e.0} else {continue;};
            let routes = routes.clone();
            let state = Arc::clone(&state);
            tokio::spawn(async move {
                let mut reader = BufReader::new(stream);
                let mut first_line = String::new();

                if let Err(_) = timeout(Duration::from_millis(100), reader.read_line(&mut first_line)).await {
                    let stream = reader.into_inner();
                    res_request(stream, "400 BAD REQUEST", "").await;
                    return;
                };

                let first_line: Vec<&str> = first_line.split(" ").collect();
                if first_line.len() != 3 {
                    let stream = reader.into_inner();
                    res_request(stream, "400 BAD REQUEST", "").await;
                    return;
                }

                let request_type = first_line[0];
                let (request_route, query_str) = match first_line[1].split_once("?") {
                    Some(e) => e,
                    _ => (first_line[1], "")
                };
                
                let request_type = match RequestType::try_from(request_type) {
                    Ok(e) => {e},
                    _ => {
                        let stream = reader.into_inner();
                        res_request(stream, "400 BAD REQUEST", "").await;
                        return;
                    }
                };
                

                let handler;

                if let Some(route_handler) = routes.get(&(request_type, Cow::Borrowed(request_route))) {
                    handler = route_handler;
                } else {
                    let stream = reader.into_inner();
                    res_request(stream, "404 NOT FOUND", "<h1>404 NOT FOUND</h1>").await;
                    return;
                }

                match handler {
                    Static(res) => {
                        let stream = reader.into_inner();
                        let responder = Responder::new(stream);
                        let _ = responder.respond(res).await;
                    },
                    Active(handler) => {
                        let mut line = String::new();
                        let mut headers = Vec::new();
                        let mut content_length = None;
                        let mut body = String::new();
                        let mut query = HashMap::new();

                        for q in query_str.split("&") {
                            if let Some((name, value)) = q.split_once("=") {
                                query.insert(name.to_string(), value.to_string());
                            }
                        }

                        let c_l_str = "Content-Length".to_string();
                        loop {
                            reader.read_line(&mut line).await.unwrap();
                            if &line[..] == "\r\n" {
                                break;
                            }
                            
                            let split = match line.split_once(": ") {
                                Some(s) => (s.0.to_string(), s.1.to_string()),
                                _ => {continue;}
                            };
                            if split.0 == c_l_str {
                                content_length = Some(split.1.clone())
                            }
                            headers.push(split);
                            

                            line.clear();
                        }

                        if let Some(length) = content_length {
                            let length = match length.trim().parse::<usize>() {
                                Ok(l) => l, 
                                _ => {0}
                            };

                            let mut data_buf = vec![0u8; length];
                            if timeout(Duration::from_millis(100), reader.read_exact(&mut data_buf)).await.is_ok() {
                                body = String::from_utf8_lossy(&data_buf[..]).to_string();
                            }
                        }

                        let stream = reader.into_inner();
                        let responder = Responder::new(stream);
                        let ctx = Context {
                            request: Request {body, headers, query},
                            state,
                        };
                        handler.call(responder, ctx).await;
                    },
                }
            });
        }
    }
}