rohanasan/
lib.rs

1// MIT License
2
3// Copyright (c) 2024 Rohan Vashisht
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#![doc = include_str!("../README.md")]
24
25mod constants;
26mod priv_parse;
27mod readers;
28mod senders;
29mod static_folder;
30pub use constants::*;
31use priv_parse::parse_headers;
32use readers::read_the_request;
33use senders::{send_invalid_utf8_error, send_static_folder_and_programmers_response};
34use std::{net::SocketAddr, time::Duration};
35pub use tokio::runtime::Builder;
36use tokio::{
37    net::{TcpListener, TcpStream},
38    time::timeout,
39};
40
41/// # Rohanasan macro
42/// **This is the macro inside which you need to declare the serve function.**
43/// ## Usage:
44/// ```no_run
45/// use rohanasan::{
46///     rohanasan, send_http_response, serve, Request, DEFAULT_HTML_HEADER,
47/// };
48/// fn handle(req: Request) -> String {
49///     send_http_response(DEFAULT_HTML_HEADER, "<h1>Hello!</h1>", req)
50/// }
51///
52/// fn main() {
53///     rohanasan! {
54///         serve(8080, handle)
55///     }
56/// }
57/// ```
58/// > Place 'only' the serve function inside the rohanasan macro without a semicolon.
59#[macro_export]
60macro_rules! rohanasan {
61    // Define the macro pattern.
62    ($($body:tt)*) => {
63        use $crate::Builder as why_will_someone_use_this_as_a_name_to_import_task_32194ilqrjf8da;
64        why_will_someone_use_this_as_a_name_to_import_task_32194ilqrjf8da::new_multi_thread().enable_all()
65        .build()
66        .unwrap()
67        .block_on(
68            $($body)*
69        )
70    };
71}
72
73/// # Request Struct
74/// **This is the structure that you have to import in your handle function.**
75pub struct Request {
76    /// **Tells the method used to connect to your server, either GET or POST.**
77    pub method: &'static str,
78    /// **Tells the path at which the request was made.**
79    /// For example: /path
80    pub path: &'static str,
81    /// **Tells the parameters that were passed to the url in form of get request.**
82    /// For example: q=Hello%20World
83    pub get_request: &'static str,
84    /// **This tells whether the request was a close or keep alive.**
85    /// For example: q=Hello%20World
86    pub keep_alive: bool,
87    /// **This tells which protocol was used to make the request.**
88    /// For example: http/1.1
89    pub protocol: &'static str,
90    /// This has been used multiple times throughout the code to check id therequest was correct or not.
91    request_was_correct: bool,
92}
93
94/// private functions to handle connections
95async fn handle_connection<F>(mut stream: TcpStream, func: F)
96where
97    F: Fn(Request) -> String + Send + Copy,
98{
99    let (buffer, n) = read_the_request(&mut stream).await;
100    if n == 0 {
101        return;
102    }
103    let request: Request = parse_headers(buffer, n);
104    if request.request_was_correct {
105        if request.keep_alive {
106            send_static_folder_and_programmers_response(request, &mut stream, func).await;
107            let mut counter = 0;
108            while counter < 20 {
109                counter += 1;
110                let request_result =
111                    timeout(Duration::from_secs(10), read_the_request(&mut stream)).await;
112                if let Ok((buffer, n)) = request_result {
113                    if n == 0 {
114                        return; // breaking and returning (closing the connection)
115                    }
116
117                    let request_inside_loop: Request = parse_headers(buffer, n);
118                    let keep_alive = request_inside_loop.keep_alive;
119                    if request_inside_loop.request_was_correct {
120                        send_static_folder_and_programmers_response(
121                            request_inside_loop,
122                            &mut stream,
123                            func,
124                        )
125                        .await;
126                        if !keep_alive {
127                            return;
128                        }
129                    } else {
130                        send_invalid_utf8_error(&mut stream).await;
131                    }
132                } else {
133                    // lets not do anything here. I suppose maybe a continue statement? This will work as a continue anyways.
134                }
135            }
136        } else {
137            send_static_folder_and_programmers_response(request, &mut stream, func).await;
138        }
139    } else {
140        send_invalid_utf8_error(&mut stream).await;
141    }
142}
143/// # The serve function
144/// **Use this function to start the server at a specific port and also provide it with a handler function.**
145/// ## Usage:
146/// ```no_run
147/// use rohanasan::{
148///     rohanasan, send_http_response, serve, Request, DEFAULT_HTML_HEADER,
149/// };
150/// fn handle(req: Request) -> String {
151///     send_http_response(DEFAULT_HTML_HEADER, "<h1>Hello!</h1>", req)
152/// }
153///
154/// fn main() {
155///     rohanasan! {
156///         serve(8080, handle)
157///     }
158/// }
159/// ```
160pub async fn serve<F>(port: u16, func: F)
161where
162    F: Fn(Request) -> String + Send + 'static + Copy,
163{
164    let addr = SocketAddr::from(([0, 0, 0, 0], port));
165    let listener = TcpListener::bind(addr).await.expect("");
166
167    loop {
168        let (stream, _) = listener.accept().await.expect("");
169        tokio::spawn(handle_connection(stream, func));
170    }
171}
172/// # Send HTTP response function:
173/// **Use this function to send a http response**
174/// **Provide it with a header, a response string and req.**
175/// ## Example usage:
176/// ```no_run
177/// use rohanasan::{
178///     rohanasan, send_http_response, serve, Request, DEFAULT_HTML_HEADER,
179/// };
180/// fn handle(req: Request) -> String {
181///
182///     send_http_response(DEFAULT_HTML_HEADER, "<h1>Hello!</h1>", req)
183/// }
184///
185/// fn main() {
186///     rohanasan! {
187///         serve(8080, handle)
188///     }
189/// }
190/// ```
191pub fn send_http_response(header: &str, body: &str, req: Request) -> String {
192    if req.keep_alive {
193        format!(
194            "{}\r\nContent-Length:{}\r\nConnection:Keep-Alive\r\n\r\n{}",
195            header,
196            body.len(),
197            body
198        )
199    } else {
200        format!(
201            "{}\r\nContent-Length:{}\r\nConnection:Close\r\n\r\n{}",
202            header,
203            body.len(),
204            body
205        )
206    }
207}
208
209/// # Send file function:
210/// **Use this function to send a file as a response.**
211/// **Provide it with a header, the file's path and req.**
212/// ## Example usage:
213/// ```no_run
214/// use rohanasan::{
215///     rohanasan, send_file, serve, Request, DEFAULT_HTML_HEADER,
216/// };
217/// fn handle(req: Request) -> String {
218///
219///     send_file(DEFAULT_HTML_HEADER, "./html/index.html", req)
220/// }
221///
222/// fn main() {
223///     rohanasan! {
224///         serve(8080, handle)
225///     }
226/// }
227/// ```
228pub fn send_file(header: &str, file_path: &str, req: Request) -> String {
229    let contents = std::fs::read_to_string(file_path).expect("msg");
230    send_http_response(header, &contents, req)
231}
232
233/// # Send file top bottom function:
234/// **Use this function to send a file as a response along with data to insert at the file's top and bottom.**
235/// **Provide it with a header, the file's path, top content, bottom content and req.**
236/// ## Example usage:
237/// ```no_run
238/// use rohanasan::{
239///     rohanasan, send_file, serve, Request, DEFAULT_HTML_HEADER,
240/// };
241/// fn handle(req: Request) -> String {
242///
243///     send_file_top_bottom(DEFAULT_HTML_HEADER, "./html/index.html", "<body>", "</body>" req)
244/// }
245///
246/// fn main() {
247///     rohanasan! {
248///         serve(8080, handle)
249///     }
250/// }
251/// ```
252pub fn send_file_top_bottom(
253    header: &str,
254    file_path: &str,
255    top: &str,
256    bottom: &str,
257    req: Request,
258) -> String {
259    let mut contents: String = std::fs::read_to_string(file_path).expect("msg");
260    contents = contents.replace("{%INJECT_DATA TOP%}", top);
261    contents = contents.replace("{%INJECT_DATA BOTTOM%}", bottom);
262    send_http_response(header, &contents, req)
263}
264
265/// # Url Decode function:
266/// **Use this function to convert an encoded url to a decoded one.**
267/// **Provide it with a String**
268/// **This will convert q=Hello%20World to q=Hello World**
269/// ## Example usage:
270/// ```no_run
271/// use rohanasan::{
272///     rohanasan, send_file, serve, Request, DEFAULT_HTML_HEADER,url_decode
273/// };
274///
275/// fn handle(req: Request) -> String {
276///     if req.path == "/request"{
277///         println!("{}" , url_decode(req.get_request()));
278///     }
279/// }
280///
281/// fn main() {
282///     rohanasan! {
283///         serve(8080, handle)
284///     }
285/// }
286/// ```
287pub fn url_decode(encoded_string: &str) -> String {
288    urldecode::decode(encoded_string.to_string())
289}