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::{
|
use crate::http::{
|
||||||
requests::{Request, RequestStatus},
|
requests::{Request, RequestStatus, URL},
|
||||||
responses::{Response, UnitOrBoxedError},
|
responses::{Response, UnitOrBoxedError},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -7,17 +7,19 @@ use std::{
|
||||||
net::{Shutdown, TcpListener},
|
net::{Shutdown, TcpListener},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type HandlerFn = fn(Request) -> Response<'static>;
|
||||||
|
|
||||||
// Collection of handlers for requests
|
// Collection of handlers for requests
|
||||||
pub struct Handlers<FnT> {
|
pub struct Handlers {
|
||||||
matchers: HashMap<String, FnT>,
|
matchers: HashMap<String, HandlerFn>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FnT> Handlers<FnT>
|
impl Handlers {
|
||||||
where
|
/// Create a new Handlers struct to handle requests
|
||||||
FnT: Fn(Request) -> Response<'static>,
|
///
|
||||||
{
|
/// !! This struct does nothing until you add handlers to it (add_handler) and then bind it to a TcpListener !!
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Handlers::<FnT> {
|
Handlers {
|
||||||
matchers: HashMap::new(),
|
matchers: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +30,7 @@ where
|
||||||
///
|
///
|
||||||
/// path: Path to match (no trailing /)
|
/// path: Path to match (no trailing /)
|
||||||
/// handler: Function to handle the request. Must return a Response
|
/// 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);
|
self.matchers.insert(path.to_string(), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,10 +65,64 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fn handle_req(&self, req: Request) -> UnitOrBoxedError {
|
||||||
match self.matchers.get(&req.url.path) {
|
match self.match_handler(&req.url) {
|
||||||
Some(handler) => (*handler)(req).send(),
|
Some(handler) => handler(req).send()?,
|
||||||
None => Response::new(req, 404, "Not Found").send(),
|
None => {
|
||||||
}
|
return Response::new(req, 404, "Not Found").send();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,22 @@ impl URL {
|
||||||
|
|
||||||
Some(URL { path, query, raw })
|
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 {
|
pub struct Request {
|
||||||
|
|
Loading…
Reference in a new issue