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}