responder/
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    dead_code,
7    unused_imports,
8    unused_variables,
9    unused_mut
10)]
11
12/*- Module imports -*/
13mod utils;
14mod thread_handler;
15pub mod response;
16pub mod request;
17pub mod errors;
18pub mod stream;
19pub mod prelude;
20
21/*- Imports -*/
22use crate::response::ResponseType;
23use errors::ConfigError;
24use request::info::{ RequestInfo, Method };
25pub use response::{ Respond, not_found };
26pub use stream::Stream;
27use lazy_static::lazy_static;
28use std::{
29    net::{
30        TcpStream,
31        TcpListener
32    },
33    io::{
34        Read, Write,
35    },
36    path::{Path, PathBuf},
37    collections::HashMap,
38    fs, sync::Mutex,
39};
40
41/*- Constants -*/
42const _DATA_BUF_INIT:usize = 1024usize;
43const DATA_BUF_POST_INIT:usize = 65536usize;
44
45/*- Loading files will check if they're already cached -*/
46lazy_static! {
47    pub static ref FILE_CACHE:Mutex<HashMap<String, Vec<u8>>> = Mutex::new(HashMap::new());
48}
49
50/*- What files we want to cache -*/
51#[derive(Clone, Copy)]
52enum FileCacheType {
53    All,
54    Selection(&'static [&'static str]),
55}
56
57#[derive(Clone, Copy)]
58/*- Structs, enums & unions -*/
59/// The Server struct contains changeable fields
60/// which configures the server during both startup and
61/// whilst it's running.
62pub struct Server {
63    /// The server address
64    addr:       Option<&'static str>,
65
66    /// The server port
67    port:       Option<u16>,
68
69    /// The maximum number of threads the current server will use.
70    num_threads:u16,
71
72    /// Serve static files from a directory (nested directories too)
73    serve:      Option<&'static str>,
74
75    /// Path to a 404 page, if not specified server will return "404 Not Found" if endpoint wasn't found
76    not_found:  Option<&'static str>,
77
78    /// All http-routes coupled to this server
79    routes:     &'static [Route],
80
81    /// The write buffer size when recieving requests in bytes
82    init_buf:   Option<usize>,
83
84    /// If file caching should be enabled or not
85    cache: Option<FileCacheType>,
86
87    /// If server logging should be enabled (Like when caching files / opening server)
88    logs: bool,
89
90    /// If CORS should be enabled or not
91    cors: bool,
92}
93
94/// A quick way of nesting routes inside of eachother
95/// stacks can contain either yet another stack, or an 
96/// endpoint like Get or Post. This enum is used for
97/// the server config when initializing the server.
98/// 
99/// ## Examples
100/// ```
101/// /*- Initiaize routes -*/
102/// let routes = &[
103///     Route::Stack("nest1", &[
104///         Route::Post("value", |stream| {}),
105///         Route::Stack("nest2", &[
106///             Route::Get("value1", |stream| {}),
107///             Route::Get("value2", |stream| {}),
108///         ]),
109///         Route::ControlledStack(origin_control, "admin", &[
110///             Route::Get("self-destruct", |stream| {})
111///         ])
112///     ]),
113/// ];
114/// ```
115pub enum Route {
116    /// A stack containing either an endpoint like Get or Post, or another Stack
117    Stack(
118        &'static str,
119        &'static [Route]
120    ),
121
122    /// A stack with all it's routes protected by an origin control function.
123    /// The origin control function returns a boolean indicating wether the
124    /// request is valid or not. (true = continue the request. false = cancel)
125    ControlledStack(
126        fn(&mut Stream) -> bool,
127        &'static str,
128        &'static [Route]
129    ),
130
131    /// Enpoint - Get request
132    Get(
133        &'static str,
134        fn(&mut Stream) -> ()
135    ),
136
137    /// Enpoint - Post request
138    Post(
139        &'static str,
140        fn(&mut Stream) -> ()
141    ),
142
143    /// Enpoint - File serving 
144    File(
145        &'static str,
146        &'static str
147    )
148}
149
150/*- Functions -*/
151fn handle_req(tcp_stream:TcpStream, config:&Server) {
152    /*- Data buffer -*/
153    let buffer:&mut Vec<u8> = &mut vec![0u8; config.init_buf.unwrap_or(DATA_BUF_POST_INIT)];
154    let mut stream = Stream::from(tcp_stream);
155
156    /*- Set CORS -*/
157    if config.cors {
158        stream.cors = true;
159    };
160
161    /*- Read data into buffer -*/
162    match stream.get_mut_inner_ref().read(buffer) {
163        Ok(data) => data,
164        Err(_) => return
165    };
166
167    /*- Parse headers (via utils) -*/
168    let request:String = String::from_utf8_lossy(
169        // Remove empty bytes
170        &buffer[..buffer.iter().position(|&r| r == 0).unwrap_or(buffer.len())]
171    ).to_string();
172    let headers:HashMap<&str, &str> = utils::headers::parse_headers(&request);
173
174    /*- Get request info -*/
175    let mut body:String = String::new();
176    let info:RequestInfo = match RequestInfo::parse_req(&request) {
177        Ok(e) => e,
178        Err(_) => return
179    };
180
181    /*- POST requests often contain huge bodies in terms of bytes, (ex when sending images). The
182        DATA_BUF_INIT constant is regularly set to a relativly small number like 2048 which images
183        won't fit into, therefore we'll update the buffer array to contain more bytes for POST requests -*/
184    if info.method == Method::POST {
185        body = request.split("\r\n\r\n").last().unwrap_or("").to_string();
186        // TODO
187    }
188    let mut full_path:String = String::new();
189    stream.set_body(body);
190    stream.set_headers(headers);
191
192    /*- Get the function or file which is coupled to the request path -*/
193    for route in config.routes {
194        match call_endpoint(&route, info, &mut full_path, &mut stream) {
195            Ok(_) => return,
196            Err(optional_status) => {
197                if let Some(status) = optional_status {
198                    return stream.respond_status(status);
199                }
200            },
201        };
202    };
203
204    /*- If no path was found, we'll check if the
205        user want's to serve any static dirs -*/
206    if let Some(static_path) = config.serve {
207        match serve_static_dir(static_path, info.path, &mut stream) {
208            Ok(_) => (),
209            Err(_) => {
210                /*- Now that we didn't find a function, nor
211                    a static file, we'll send a 404 page -*/
212                not_found(&mut stream, *config);
213            },
214        };
215    }else {
216        not_found(&mut stream, *config);
217    };
218}
219
220/*- Execute an api function depending on path -*/
221fn call_endpoint(
222    routes:&Route,
223    info:RequestInfo,
224    full_path:&mut String,
225
226    /*- Function parameters -*/
227    stream: &mut Stream
228) -> Result<(), Option<u16>> {
229
230    /*- ControlledStack and Stack have similar functionality,
231        the diffrence is that ControlledStack needs origin
232        control funciton to be called in the beginning -*/
233    if let Route::ControlledStack(_, pathname, next_routes) | Route::Stack(pathname, next_routes) = routes {
234        if let Route::ControlledStack(fnc, _, _) = routes {
235            /*- If request didn't pass origin control filters,
236                return with no error code because response
237                are handled in origin control function -*/
238            if fnc(stream) == false { return Err(None); };
239        }
240            
241        /*- If a tail was found -*/
242        let mut tail_found:bool = false;
243
244        /*- Iterate over all stacks and tails -*/
245        'tail_search: for route in next_routes.iter() {
246
247            /*- Push the path -*/
248            let mut possible_full_path = full_path.clone();
249            possible_full_path.push_str(pathname);
250            possible_full_path.push('/');
251
252            /*- Recurse -*/
253            match call_endpoint(route, info, &mut possible_full_path, stream) {
254                Ok(_) => {
255                    tail_found = true;
256
257                    /*- Push the path to the actual final path -*/
258                    full_path.push_str(pathname);
259                    full_path.push('/');
260                    break 'tail_search;
261                },
262                Err(_) => continue
263            };
264        };
265
266        /*- Return -*/
267        if tail_found { return Ok(()); }
268        else { return Err(None); }
269    }
270
271    /*- Check what type of route it is -*/
272    match routes {
273        Route::Post(pathname, function_ptr)
274       | Route::Get(pathname, function_ptr) => {
275
276            /*- Store url parameters. An url parameter is a "variable" which
277                will be set in the url. Example: localhost:8000/day/:day: -*/
278            let mut params:HashMap<String, String> = HashMap::new();
279
280            /*- Push the pathname -*/
281            let mut possible_full_path = full_path.clone();
282            possible_full_path.push_str(pathname);
283
284            /*- Check for url parameters -*/
285            let final_subpaths:Vec<&str> = get_subpaths(&possible_full_path);
286            let mut final_check_url:String = possible_full_path.clone();
287
288            /*- Iterate and find url params -*/
289            for (index, request_path) in get_subpaths(info.path).iter().enumerate() {
290
291                /*- Get the current searched subpath to check wether they are the same -*/
292                let subp:&str = match final_subpaths.get(index) {
293                    Some(e) => e,
294                    None => return Err(None)
295                };
296
297                match is_url_param(subp) {
298                    Some(param_name) => {
299                        params.insert(param_name.into(), request_path.to_string());
300
301                        /*- Change full_path -*/
302                        final_check_url = final_check_url.replace(subp, request_path);
303                        continue;
304                    },
305                    None => {
306                        if request_path != &subp {
307                            return Err(None);
308                        }else {
309                            continue;
310                        };
311                    },
312                }
313            }
314
315            /*- If it's the requested path -*/
316            if trim(final_check_url) == trim(info.path.to_string()) {
317
318                /*- If it's the requested method -*/
319                match &info.method {
320                    Method::GET => {
321                        /*- Call the associated function -*/
322                        stream.set_params(params);
323                        function_ptr(stream);
324    
325                        /*- Return success -*/
326                        Ok(())
327                    },
328                    Method::POST => {
329                        /*- Call the associated function -*/
330                        stream.set_params(params);
331                        function_ptr(stream);
332
333                        /*- Return success -*/
334                        Ok(())
335                    },
336                    Method::UNKNOWN => Err(Some(405u16)), // Method not allowed
337                    Method::OPTIONS => {
338                        stream.respond_status(200u16);
339                        Ok(())
340                    },
341                    _ => {
342                        /*- Call the associated function -*/
343                        stream.set_params(params);
344                        function_ptr(stream);
345
346                        /*- Return success -*/
347                        Ok(())
348                    }
349                }
350            }else {
351                Err(None)
352            }
353        },
354        Route::File(endpoint_path, file_path) => {
355            println!("{endpoint_path} {file_path}");
356            /*- Push the pathname -*/
357            let mut possible_full_path = full_path.clone();
358            possible_full_path.push_str(&endpoint_path);
359
360            dbg!(&possible_full_path);
361            dbg!(info.path);
362
363            if trim(possible_full_path) == trim(info.path.to_string()) {
364                stream.respond_file(200u16, file_path);
365                Ok(())
366            }else {
367                Err(None)
368            }
369        }
370        _ => Err(Some(405u16)) // Method not allowed
371    }
372}
373
374/*- Trim paths with trailing and leading slashes -*/
375pub fn trim(input:String) -> String {
376    let mut output = input.clone();
377    if output.ends_with('/') { output.pop(); };
378    if output.starts_with('/') { output.remove(0); };
379    output
380}
381
382/*- Get subpaths of a full path. Example: get_subpaths("/Path/to/value") -> vec!["Path", "to", "value"] -*/
383fn get_subpaths(path:&str) -> Vec<&str> {
384    let mut subpaths:Vec<&str> = Vec::new();
385
386    /*- Iterate over all subpaths -*/
387    for subpath in path.split('/') {
388        if !subpath.is_empty() { subpaths.push(subpath); };
389    };
390
391    /*- Return -*/
392    subpaths
393}
394
395/*- Check if a path is a url parameter -*/
396fn is_url_param(path:&str) -> Option<&str> {
397    if path.starts_with(':') && path.ends_with(':') {
398        Some(&path[1..path.len()-1])
399    }else {
400        None
401    }
402}
403
404/*- Serve static files from a specified dir -*/
405fn serve_static_dir(dir:&str, request_path:&str, stream:&mut Stream) -> Result<(), ()> {
406
407    /*- Get the requested file path -*/
408    let path = &[dir, request_path].concat();
409    let file_path:&Path = Path::new(path);
410
411    /*- Find if exists in file cache -*/
412    if let Ok(fc) = FILE_CACHE.lock() {
413        match fc.get(path) {
414            Some(buf) => {
415                stream.respond(
416                    200,
417                    Respond::new().content(
418                        &String::from_utf8_lossy(&buf),
419                        ResponseType::guess(file_path)
420                    )
421                );
422                return Ok(())
423            },
424            None => ()
425        }
426    };
427
428    /*- Check path availability -*/
429    match file_path.is_file() {
430        true => (),
431        false => return Err(())
432    };
433
434    /*- Open file -*/
435    match fs::File::open(file_path) {
436        Ok(_) => {
437            /*- Get file content -*/
438            let mut file_content:String = match fs::read_to_string(file_path) {
439                Ok(e) => e,
440                Err(_) => {
441                    /*- If we can't read the file, we'll send a 404 page -*/
442                    return Err(());
443                }
444            };
445            let res:Respond = Respond {
446                response_type: ResponseType::guess(file_path),
447                content: Some(file_content),
448                additional_headers: None
449            };
450
451            /*- Respond -*/
452            stream.respond(
453                200u16,
454                res
455            );
456        },
457        Err(_) => return Err(())
458    }
459
460    Ok(())
461}
462
463/*- Builder pattern for server config struct -*/
464impl<'f> Server {
465    pub fn new() -> Server {
466        Server {
467            addr: None,
468            port: None,
469            num_threads: 1,
470            serve: None,
471            not_found: None,
472            routes: &[],
473            init_buf: None,
474            cache: None,
475            logs: true,
476            cors: false
477        }
478    }
479    /// `[REQUIRED]` The server port
480    pub fn port(&mut self, port:u16) -> &mut Self                    { self.port = Some(port); self }
481    
482    /// Serve static files from a directory
483    pub fn serve(&mut self, serve:&'static str) -> &mut Self         { self.serve = Some(serve); self }
484    
485    /// All http-routes coupled to this server
486    pub fn routes(&mut self, routes:&'static [Route]) -> &mut Self   { self.routes = routes; self }
487    
488    /// `[REQUIRED]` The server address
489    pub fn address(&mut self, addr:&'static str) -> &mut Self        { self.addr = Some(addr); self }
490    
491    /// The number of threads the current server will use as a maximum
492    pub fn threads(&mut self, num_threads:u16) -> &mut Self          { self.num_threads = num_threads; self }
493    
494    /// Path to a 404 page, if not specified server will return "404 Not Found"
495    pub fn not_found(&mut self, not_found:&'static str) -> &mut Self { self.not_found = Some(not_found); self }
496    
497    /// The write buffer size when recieving requests in bytes
498    pub fn init_buf_size(&mut self, buf_size:usize) -> &mut Self     { self.init_buf = Some(buf_size); self }
499
500    /// If file caching should be enabled or not (for the directory specified in the serve function)
501    pub fn cache_serve_dir(&mut self) -> &mut Self                   { self.cache = Some(FileCacheType::All); self }
502
503    /// If file caching should be enabled or not (for specified file paths)
504    pub fn cache_selected(&mut self, selection:&'static [&'static str]) -> &mut Self { self.cache = Some(FileCacheType::Selection(selection)); self }
505
506    /// If file caching should be enabled or not (for specified file paths)
507    pub fn no_logs(&mut self) -> &mut Self                           { self.logs = false; self }
508
509    /// If CORS should be enabled or not
510    pub fn cors(&mut self) -> &mut Self                              { self.cors = true; self }
511    
512    /*- Starting server might fail so return Err(()) if so -*/
513    /// Start the server using this function. It takes a 'Server'
514    /// struct as input and returns a result, because setting up the
515    /// server might fail.
516    /// 
517    /// ## Example:
518    /// ```
519    /// Server::new()
520    ///     .routes(routes)
521    ///     .address("127.0.0.1")
522    ///     .port(8080)
523    ///     .threads(8)
524    ///     .serve("./static")
525    ///     .not_found("./static/404.html")
526    ///     .start()
527    ///     .unwrap();
528    /// ```
529    pub fn start(self) -> Result<(), ConfigError> {
530
531        /*- Get port and address -*/
532        let bind_to = &format!(
533            "{}:{}",
534            match self.addr {
535                Some(e) => e,
536                None => return Err(errors::ConfigError::MissingHost)
537            },
538            match self.port {
539                Some(e) => e,
540                None => return Err(errors::ConfigError::MissingPort)
541            }
542        );
543
544        /*- If cache is enabled -*/
545        if let Some(cache) = self.cache {
546            match cache {
547                FileCacheType::All => {
548                    load_files_cache(
549                        self.logs,
550                        get_list_dir(self.serve.expect("Calling .cache_serve_dir() requires .serve(dir) to be set"))
551                    )
552                },
553                FileCacheType::Selection(selection) => {
554                    load_files_cache(self.logs, selection.to_owned().iter().map(|e| e.to_string()).collect::<Vec<String>>())
555                }
556            }
557        };
558
559        /*- Start the listener -*/
560        let stream = match TcpListener::bind(bind_to) {
561            Ok(listener) => listener,
562
563            /*- If failed to open server on port -*/
564            Err(_) => return Err(ConfigError::HostPortBindingFail)
565        };
566
567        /*- Log status -*/
568        if self.logs { println!("http://{bind_to}") };
569
570        /*- Initialize thread_handler -*/
571        let thread_handler = thread_handler::MainThreadHandler::new(self.num_threads);
572
573        /*- Stream.incoming() is a blocking iterator. Will unblock on requests -*/
574        for request in stream.incoming() {
575            
576            /*- Spawn a new thread -*/
577            thread_handler.exec(move || {
578                /*- Ignore failing requests -*/
579                handle_req(match request {
580                    Ok(req) => req,
581                    Err(_) => return,
582                }, &self);
583            });
584        };
585
586        /*- Return, even though it will never happen -*/
587        Ok(())
588    }
589}
590
591/*- Gets all files in a dir using std::fs -*/
592fn get_list_dir<'a>(dir:&str) -> Vec<String> {
593    let mut files:Vec<String> = Vec::new();
594
595    /*- Get all files in dir -*/
596    for entry in match fs::read_dir(dir) { Ok(e) => e, Err(_) => return files } {
597        let entry = match entry {
598            Ok(e) => e,
599            Err(_) => continue
600        };
601        let path = entry.path();
602
603        /*- If path is a file -*/
604        if path.is_file() {
605            files.push(
606                match path.to_str() {
607                    Some(e) => e.to_string(),
608                    None => continue
609                }
610            );
611        };
612    };
613
614    files
615}
616
617/*- Loads all files in a dir into memory -*/
618fn load_files_cache(logs:bool, files:Vec<String>) {
619    let files_len = files.len();
620    let mut index = 0;
621    let mut stdout = std::io::stdout();
622    for file in files {
623        index += 1;
624        if logs { print!("\rLoading file: {} / {}", index, files_len) };
625        let mut file_ = match std::fs::File::open(file.clone()) {
626            Ok(e) => e,
627            Err(_) => continue
628        };
629        let mut buf = Vec::new();
630        match file_.read_to_end(&mut buf) {
631            Ok(e) => e,
632            Err(_) => continue
633        };
634        match FILE_CACHE.lock() {
635            Ok(e) => e,
636            Err(_) => continue
637        }.insert(Path::new(&file).canonicalize().unwrap_or(PathBuf::from("")).display().to_string(), buf);
638        stdout.flush().unwrap_or_default();
639    };
640    if logs { 
641        println!();
642        println!("Loaded {} files into memory", files_len);
643    };
644}