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}