rust_web_framework/lib.rs
1//! Quickly set up a backend web framework using rust.
2//! Very fast and easy to use.
3
4/*- Global allowings -*/
5#![allow(
6 unused_imports,
7 unused_mut,
8 dead_code,
9 unused_variables
10)]
11
12/*- Module imports -*/
13pub mod utils;
14pub mod request_info;
15pub mod response;
16pub mod request;
17
18/*- Imports -*/
19pub use request_info::{ RequestInfo, Method };
20use terminal_link::Link;
21use response::{
22 respond,
23 Respond,
24 ResponseType,
25 not_found,
26 with_file
27};
28use ansi_term;
29use std::{
30 net::{
31 TcpStream,
32 TcpListener
33 },
34 io::{
35 Read, Error,
36 },
37 thread::spawn,
38 path::Path,
39 collections::HashMap,
40 fs,
41};
42
43/*- Constants -*/
44const DATA_BUF_INIT:usize = 1024usize;
45
46/*- Structs, enums & unions -*/
47/// The ServerConfig struct contains changeable fields
48/// which configures the server during both startup and
49/// whilst it's running.
50#[derive(Clone, Copy)]
51pub struct ServerConfig {
52 pub addr: &'static str,
53 pub port: u16,
54 pub serve: Option<&'static str>,
55 pub not_found: Option<&'static str>,
56 pub routes: Route<'static>
57}
58
59/*- Send diffrent type of function -*/
60#[derive(Clone, Copy)]
61/// Rust does not provide a way of doing function overloads, which
62/// would be helpful in some cases. However this enum is like a way
63/// of solving that issue. When creating an API function which will
64/// be executed at some endpoint, the function will automatically
65/// contain three parameters: stream, headers, and body. However most
66/// of the time, you won't need to use ex the body (in ex. GET requests).
67/// So, this enum will let you choose which params you want your function
68/// to use, however all functions need to have a stream as a parameter
69/// because otherwise the server will not be able to respond correctly.
70///
71/// ## Example
72/// ```
73/// /* some_function only takes stream as param */
74/// Route::Tail(Method::GET, "enpoint1", Function::S(some_function)),
75///
76/// /* some_function only takes stream, headers and body as its parameters */
77/// Route::Tail(Method::GET, "enpoint2", Function::SHB(some_function)),
78/// ```
79///
80/// S stands for stream
81/// H stands for headers
82/// B stands for body
83pub enum Function {
84 /// Function that takes only TcpStream as input,
85 S(fn( &mut TcpStream )),
86
87 /// Function that takes TcpStream and headers as input,
88 SB(fn( &mut TcpStream, &String )),
89
90 /// Function that takes TcpStream and headers as input,
91 SH(fn( &mut TcpStream, &HashMap<
92 &str,
93 &str
94 > )),
95
96 /// Function that takes TcpStream,
97 /// headers and body as params
98 SHB(fn(
99 &mut TcpStream,
100 &HashMap<
101 &str,
102 &str
103 >,
104 &String
105 )),
106}
107
108#[derive(Clone, Copy)]
109/// A quick way of nesting routes inside of eachother
110/// stacks can contain either yet another stack, or a
111/// tail, which will act as an API-endpoint. This enum
112/// is used for the server config when initializing the
113/// server.
114///
115/// ## Examples
116/// ```
117/// /*- Initiaize routes -*/
118/// let routes = Route::Stack("", &[
119/// Route::Stack("nest1", &[
120/// Route::Tail(Method::POST, "value", Function::S(|_| {})),
121/// Route::Stack("nest2", &[
122/// Route::Tail(Method::GET, "value1", Function::S(|_| {})),
123/// Route::Tail(Method::GET, "value2", Function::S(|_| {})),
124/// ]),
125/// ]),
126/// ]);
127/// ```
128pub enum Route<'lf> {
129 Stack(
130 &'lf str,
131 &'lf [Route<'lf>]
132 ),
133 Tail(
134 Method,
135 &'lf str,
136 Function
137 )
138}
139
140/*- Starting server might fail so return Err(()) if so -*/
141/// Start the server using this function. It takes a 'ServerConfig'
142/// struct as input and returns a result, because setting up the
143/// server might fail.
144///
145/// ## Example:
146/// ```
147/// start(ServerConfig {
148/// addr: "127.0.0.1",
149/// port: 8080u16,
150/// serve: Some("./static"),
151/// not_found: Some("./static/404.html"),
152/// routes,
153/// }).unwrap();
154/// ```
155pub fn start(__sconfig:ServerConfig) -> Result<(), Error> {
156 let bind_to = &format!(
157 "{}:{}",
158 __sconfig.addr, __sconfig.port
159 );
160
161 /*- Start the listener -*/
162 let stream = match TcpListener::bind(bind_to) {
163 Ok(listener) => listener,
164
165 /*- If failed to open server on port -*/
166 Err(e) => return Err(e)
167 };
168
169 /*- Log status -*/
170 println!("{}",
171 &format!(
172 "{} {}",
173 ansi_term::Color::RGB(123, 149, 250).paint(
174 "Server opened on"
175 ),
176 ansi_term::Color::RGB(255, 255, 0).underline().paint(
177 format!("{}", Link::new(
178 &format!("http://{}", &bind_to),
179 bind_to,
180 ))
181 )
182 )
183 );
184
185 /*- Stream.incoming() is a blocking iterator. Will unblock on requests -*/
186 for request in stream.incoming() {
187
188 /*- Spawn a new thread -*/
189 spawn(move || {
190 /*- Ignore failing requests -*/
191 handle_req(match request {
192 Ok(req) => req,
193 Err(_) => return,
194 }, &__sconfig);
195 });
196 };
197
198 /*- Return, even though it will never happen -*/
199 Ok(())
200}
201
202/*- Functions -*/
203fn handle_req(mut stream:TcpStream, config:&ServerConfig) -> () {
204 /*- Data buffer -*/
205 let buffer:&mut [u8] = &mut [0u8; DATA_BUF_INIT];
206
207 /*- Read data into buffer -*/
208 match stream.read(buffer) {
209 Ok(data) => data,
210 Err(_) => return
211 };
212
213 /*- Parse headers (via utils) -*/
214 let request:String = String::from_utf8_lossy(&buffer[..]).to_string();
215 let headers:HashMap<&str, &str> = utils::headers::parse_headers(&request);
216
217 /*- Get request info -*/
218 let info:RequestInfo = match RequestInfo::parse_req(&request) {
219 Ok(e) => e,
220 Err(_) => return
221 };
222
223 /*- If getting body is neccesary or not -*/
224 let mut body:String = String::new();
225 if info.method == Method::POST {
226 body = request.split("\r\n\r\n").last().unwrap().to_string();
227 }
228
229 /*- Get the function or file which is coupled to the request path -*/
230 match call_endpoint(&config.routes, info, String::new(), (&headers, &mut stream, &body)) {
231 Ok(_) => (),
232 Err(_) => {
233 /*- If no path was found, we'll check if the
234 user want's to serve any static dirs -*/
235 if let Some(static_path) = config.serve {
236 match serve_static_dir(&static_path, info.path, &stream) {
237 Ok(_) => (),
238 Err(_) => {
239 /*- Now that we didn't find a function, nor
240 a static file, we'll send a 404 page -*/
241 return not_found(&mut stream, *config);
242 }
243 };
244 }else {
245 return not_found(&mut stream, *config);
246 };
247 },
248 };
249}
250
251/*- Execute an api function depending on path -*/
252fn call_endpoint(
253 route:&Route,
254 info:RequestInfo,
255 mut final_path:String,
256
257 // Function params
258 (
259 headers,
260 stream,
261 body
262 ):(
263 &HashMap<&str, &str>,
264 &mut TcpStream,
265 &String
266 )
267) -> Result<(), ()> {
268
269 /*- Check what type of route it is -*/
270 match route {
271 Route::Stack(pathname, routes) => {
272
273 /*- If a tail was found -*/
274 let mut tail_found:bool = false;
275
276 /*- Iterate over all stacks and tails -*/
277 'tail_search: for route in routes.iter() {
278 let mut possible_final_path = final_path.clone();
279
280 /*- Push the path -*/
281 possible_final_path.push_str(pathname);
282 possible_final_path.push_str("/");
283
284 /*- Recurse -*/
285 match call_endpoint(route, info, possible_final_path.clone(), (headers, stream, body)) {
286 Ok(_) => {
287 tail_found = true;
288
289 /*- Push the path to the actual final path -*/
290 final_path.push_str(pathname);
291 final_path.push_str("/");
292 break 'tail_search;
293 },
294 Err(_) => continue
295 };
296 };
297
298 /*- Return -*/
299 if tail_found { return Ok(()); }
300 else { return Err(()); }
301 },
302 Route::Tail(method, pathname, function) => {
303 /*- If it's the requested path -*/
304 if [final_path, pathname.to_string()].concat() == info.path {
305
306 /*- If it's the requested method -*/
307 if method == &info.method {
308 /*- Call the associated function -*/
309 Function::call_fn(*function, stream, headers, body);
310
311 /*- Return success -*/
312 return Ok(());
313 }else {
314 return Err(());
315 }
316 }else {
317 return Err(());
318 }
319 }
320 };
321}
322
323/*- Serve static files from a specified dir -*/
324fn serve_static_dir(dir:&str, request_path:&str, mut stream:&TcpStream) -> Result<(), ()> {
325
326 /*- Get the requested file path -*/
327 let path = &[dir, request_path].concat();
328 let file_path:&Path = Path::new(path);
329
330 /*- Check path availability -*/
331 match file_path.is_file() {
332 true => (),
333 false => return Err(())
334 };
335
336 /*- Open file -*/
337 match fs::File::open(file_path) {
338 Ok(mut e) => {
339 /*- Get file content -*/
340 let mut content:String = String::new();
341 match e.read_to_string(&mut content) {
342 Ok(_) => (),
343 Err(_) => (),
344 };
345
346 /*- Respond -*/
347 respond(
348 &mut stream,
349 200u16,
350 Some(Respond {
351 response_type: ResponseType::guess(file_path),
352 content
353 })
354 )
355 },
356 Err(_) => return Err(())
357 }
358
359 Ok(())
360}
361
362/*- Method implementation -*/
363impl Function {
364 pub fn call_fn(self, stream:&mut TcpStream, headers:&HashMap<&str, &str>, body:&String) -> () {
365 match self {
366 Self::S(e) => e(stream),
367 Self::SB(e) => e(stream, body),
368 Self::SH(e) => e(stream, headers),
369 Self::SHB(e) => e(stream, headers, body),
370 }
371 }
372}