nutt_web/
lib.rs

1pub mod http;
2pub mod modules;
3
4use crate::http::cookie::{CookieJar, CookieReq};
5use crate::http::method::Method;
6use crate::http::request::{Request, RequestBuilder};
7use crate::http::response::ResponseBuilder;
8use crate::http::status::StatusCode;
9use crate::modules::displayable::DisplayableVec;
10use crate::modules::router::route::Route;
11use crate::modules::router::Router;
12use crate::modules::session::cookie_session::CookieSession;
13use crate::modules::session::{Session, SessionType};
14use crate::modules::state::State;
15use crate::modules::stream_reader::StreamReader;
16use serde::Deserialize;
17use std::any::Any;
18use std::collections::HashMap;
19use std::error::Error;
20use std::sync::{Arc, RwLock};
21use tracing_log::log::{log, Level};
22
23pub struct NuttServer {
24    address_dev: Option<(String, u16)>,
25    address_release: Option<(String, u16)>,
26    router: Router,
27    states: Arc<RwLock<HashMap<String, Box<dyn Any + Send + Sync>>>>,
28    session: Option<Session>,
29}
30
31impl Default for NuttServer {
32    fn default() -> Self {
33        Self::new()
34    }
35}
36
37impl NuttServer {
38    pub fn new() -> Self {
39        Self {
40            address_dev: None,
41            address_release: None,
42            router: Router::new(),
43            states: Arc::new(RwLock::new(HashMap::new())),
44            session: None,
45        }
46    }
47
48    pub fn routes(mut self, routes: Vec<Route>) -> Self {
49        for route in routes {
50            self.router.insert(route.get(), route)
51        }
52        self
53    }
54
55    pub fn bind_dev(mut self, address: (&str, u16)) -> Self {
56        self.address_dev = Some((address.0.to_string(), address.1));
57        self
58    }
59
60    pub fn bind_release(mut self, address: (&str, u16)) -> Self {
61        self.address_release = Some((address.0.to_string(), address.1));
62        self
63    }
64
65    pub fn state<T: Sync + Send + 'static + for<'a> Deserialize<'a>>(
66        self,
67        state: (String, State<T>),
68    ) -> Self {
69        self.states
70            .try_write()
71            .unwrap()
72            .insert(state.0, Box::new(state.1));
73        self
74    }
75
76    pub fn session(mut self, session_type: SessionType) -> Self {
77        match session_type {
78            SessionType::Cookie => self.session = Some(Session::Cookie(CookieSession::new())),
79        }
80        self
81    }
82
83    pub async fn run(self) {
84        tracing_subscriber::fmt::init();
85        let address = if cfg!(not(debug_assertions)) && self.address_release.is_some() {
86            self.address_release
87        } else {
88            self.address_dev
89        };
90        if let Some(address) = address {
91            let listener = tokio::net::TcpListener::bind(format!("{}:{}", address.0, address.1))
92                .await
93                .unwrap();
94            log!(Level::Info, "Server started on {}:{}", address.0, address.1);
95            let router = Arc::new(self.router);
96            let states = self.states.clone();
97            let session = Arc::new(self.session);
98            loop {
99                let router_arc = router.clone();
100                let states_arc = states.clone();
101                let session_arc = session.clone();
102                match listener.accept().await {
103                    Ok((stream, _)) => {
104                        tokio::task::spawn(async move {
105                            match Self::handle_stream(stream).await {
106                                Ok((method, path, stream, mut req)) => {
107                                    if let Some(route) = router_arc.get((method, path)) {
108                                        req.set_states(states_arc.clone());
109                                        req.set_session(session_arc.clone());
110                                        route.run_fabric(stream, req)
111                                    } else {
112                                        stream
113                                            .try_write(not_found!().to_string().as_bytes())
114                                            .unwrap();
115                                    }
116                                }
117                                Err(e) => log!(Level::Error, "Error handling stream: {}", e),
118                            }
119                        });
120                    }
121                    Err(e) => {
122                        log!(Level::Error, "Failed to accept connection: {}", e);
123                    }
124                }
125            }
126        } else {
127            panic!("Server don't have address")
128        }
129    }
130
131    async fn handle_stream(
132        mut stream: tokio::net::TcpStream,
133    ) -> Result<(Method, String, tokio::net::TcpStream, Request), Box<dyn Error>> {
134        let request = StreamReader::new(&mut stream).read_req().await;
135        let tokens: Vec<&str> = request.lines().nth(0).unwrap().split_whitespace().collect();
136        if tokens.len() != 3 {
137            return Err("Invalid HTTP request line".into());
138        }
139
140        let method = match tokens[0] {
141            "GET" => Method::GET,
142            "POST" => Method::POST,
143            "PUT" => Method::PUT,
144            "DELETE" => Method::DELETE,
145            _ => return Err("Unsupported HTTP method".into()),
146        };
147
148        let path = tokens[1].to_string();
149
150        let mut headers = DisplayableVec(vec![]);
151        let mut i = 1;
152        let mut is_header = true;
153        let mut body = String::new();
154        while let Some(line) = request.lines().nth(i) {
155            if line.is_empty() {
156                is_header = false
157            }
158            if is_header {
159                headers.0.push(line.to_string());
160            } else {
161                body.push_str(line.trim())
162            }
163            i += 1;
164        }
165
166        let mut cookies = CookieJar::new();
167
168        for header in &headers.0 {
169            if header.starts_with("Cookie: ") {
170                for cookie in header[8..].split(";") {
171                    let eq_pos = cookie.find("=").unwrap();
172                    cookies.push_cookie(
173                        &cookie[..eq_pos],
174                        CookieReq::new(cookie[eq_pos + 1..].to_string()),
175                    )
176                }
177            }
178        }
179
180        log!(
181            Level::Info,
182            "Request Method: {}, Path: {}, Headers: {}, Body: {}",
183            method,
184            path,
185            headers,
186            body
187        );
188
189        Ok((
190            method.clone(),
191            path,
192            stream,
193            RequestBuilder::new(method, serde_json::to_value(body).unwrap())
194                .set_cookie_jar(cookies)
195                .build(),
196        ))
197    }
198}