feat: dynmaic request path handling
This commit is contained in:
		
							parent
							
								
									e8a30fb919
								
							
						
					
					
						commit
						8d8cfe33fa
					
				
					 2 changed files with 85 additions and 13 deletions
				
			
		| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
use crate::http::{
 | 
			
		||||
    requests::{Request, RequestStatus},
 | 
			
		||||
    requests::{Request, RequestStatus, URL},
 | 
			
		||||
    responses::{Response, UnitOrBoxedError},
 | 
			
		||||
};
 | 
			
		||||
use std::{
 | 
			
		||||
| 
						 | 
				
			
			@ -7,17 +7,19 @@ use std::{
 | 
			
		|||
    net::{Shutdown, TcpListener},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type HandlerFn = fn(Request) -> Response<'static>;
 | 
			
		||||
 | 
			
		||||
// Collection of handlers for requests
 | 
			
		||||
pub struct Handlers<FnT> {
 | 
			
		||||
    matchers: HashMap<String, FnT>,
 | 
			
		||||
pub struct Handlers {
 | 
			
		||||
    matchers: HashMap<String, HandlerFn>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<FnT> Handlers<FnT>
 | 
			
		||||
where
 | 
			
		||||
    FnT: Fn(Request) -> Response<'static>,
 | 
			
		||||
{
 | 
			
		||||
impl Handlers {
 | 
			
		||||
    /// Create a new Handlers struct to handle requests
 | 
			
		||||
    ///
 | 
			
		||||
    /// !! This struct does nothing until you add handlers to it (add_handler) and then bind it to a TcpListener !!
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Handlers::<FnT> {
 | 
			
		||||
        Handlers {
 | 
			
		||||
            matchers: HashMap::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +30,7 @@ where
 | 
			
		|||
    ///
 | 
			
		||||
    /// path: Path to match (no trailing /)
 | 
			
		||||
    /// handler: Function to handle the request. Must return a Response
 | 
			
		||||
    pub fn add_handler(&mut self, path: &str, handler: FnT) {
 | 
			
		||||
    pub fn add_handler(&mut self, path: &str, handler: HandlerFn) {
 | 
			
		||||
        self.matchers.insert(path.to_string(), handler);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -63,10 +65,64 @@ where
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_req(&self, req: Request) -> UnitOrBoxedError {
 | 
			
		||||
        match self.matchers.get(&req.url.path) {
 | 
			
		||||
            Some(handler) => (*handler)(req).send(),
 | 
			
		||||
            None => Response::new(req, 404, "Not Found").send(),
 | 
			
		||||
    fn match_handler(&self, url: &URL) -> Option<HandlerFn> {
 | 
			
		||||
        'matching_loop: for (path, handler) in self.matchers.iter() {
 | 
			
		||||
            // Exact match
 | 
			
		||||
            if path == &url.path {
 | 
			
		||||
                return Some(*handler);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // Segment matching
 | 
			
		||||
            let url_segements = url.path.split('/').collect::<Vec<&str>>();
 | 
			
		||||
            let path_segments = path.split('/').collect::<Vec<&str>>();
 | 
			
		||||
 | 
			
		||||
            // If the URL has more segments than the path, it can't match
 | 
			
		||||
            // or if the path has no segments, it can't match
 | 
			
		||||
            if (url_segements.len() != path_segments.len()) || (path_segments.len() == 0) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check each segment of the url
 | 
			
		||||
            for (url_segment, path_segment) in url_segements.iter().zip(path_segments.iter()) {
 | 
			
		||||
                if path_segment.starts_with('[') {
 | 
			
		||||
                    // e.g. /path/[id]
 | 
			
		||||
                    if path_segment.ends_with(']') { 
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // e.g. /path/prefix[id].suffix
 | 
			
		||||
                    let prefix = path_segment.split('[').collect::<Vec<_>>()[0];
 | 
			
		||||
                    let suffix = path_segment.split(']').collect::<Vec<_>>()[1];
 | 
			
		||||
 | 
			
		||||
                    // begins_with and starts_with always return true on empty strings
 | 
			
		||||
                    if url_segment.starts_with(prefix) && url_segment.ends_with(suffix) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    break 'matching_loop;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if url_segment == path_segment {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break 'matching_loop;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Some(*handler);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_req(&self, req: Request) -> UnitOrBoxedError {
 | 
			
		||||
        match self.match_handler(&req.url) {
 | 
			
		||||
            Some(handler) => handler(req).send()?,
 | 
			
		||||
            None => {
 | 
			
		||||
                return Response::new(req, 404, "Not Found").send();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,22 @@ impl URL {
 | 
			
		|||
 | 
			
		||||
        Some(URL { path, query, raw })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Utility function to get a path segment by index
 | 
			
		||||
    /// 
 | 
			
		||||
    /// This is a very simple function, and its implementation is fairly naïve
 | 
			
		||||
    /// 
 | 
			
		||||
    /// index: Index of the path segment to get
 | 
			
		||||
    /// 
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    /// 
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// let url = URL::new("/path/to/resource").unwrap();
 | 
			
		||||
    /// assert_eq!(url.get_path_segment(1), Some("to"));
 | 
			
		||||
    /// ```
 | 
			
		||||
    pub fn get_path_segment(&self, index: usize) -> Option<&str> {
 | 
			
		||||
        self.path.split('/').nth(index + 1)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct Request {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue