use std::{
collections::HashMap,
sync::{Arc, Mutex, MutexGuard},
};
use threadpool::ThreadPool;
use crate::http::{request::{HttpRequest, HttpRequestMethod}, response::HttpResponse};
use regex::Regex;
struct RouteHandler {
regex: Regex,
handler: Box<dyn Fn(MutexGuard<'_, HttpRequest>) + Send + Sync>,
}
pub struct HttpRouter {
routes: Arc<HashMap<HttpRequestMethod, Vec<RouteHandler>>>,
pool: ThreadPool,
}
impl HttpRouter {
pub fn new(pool_size: usize) -> HttpRouter {
HttpRouter {
routes: Arc::new(HashMap::new()),
pool: ThreadPool::new(pool_size),
}
}
pub fn add_route<F>(&mut self, method: HttpRequestMethod, path: &str, handler: F)
where
F: Fn(MutexGuard<'_, HttpRequest>) + 'static + Send + Sync,
{
let regex_pattern = self.convert_path_to_regex(path);
let regex = Regex::new(®ex_pattern).unwrap();
let route_handler = RouteHandler {
regex,
handler: Box::new(handler),
};
let routes = match Arc::get_mut(&mut self.routes) {
Some(routes) => routes,
None => {
log::error!("Failed to create route! Unable to mutate routes object to add route.\n\t Please report this error at https://github.com/kyleyannelli/m_server");
std::process::exit(1);
}
};
routes.entry(method).or_insert(Vec::<RouteHandler>::new()).push(route_handler);
}
pub fn handle_request(&self, request: Arc<Mutex<HttpRequest>>) {
let routes = self.routes.clone();
self.pool.execute(move || {
let mut req = request.lock().unwrap();
if let Some(handlers) = routes.get(&req.route.method) {
for handler in handlers {
println!("HTTPREQ {} HANDLER {}", &req.route.path, handler.regex.to_string());
if handler.regex.is_match(&req.route.path) {
drop(req);
(handler.handler)(request.lock().unwrap());
return;
}
}
req.respond(HttpResponse::not_found());
}
else {
req.respond(HttpResponse::not_found());
}
drop(req);
});
}
fn convert_path_to_regex(&self, path: &str) -> String {
let mut regex_pattern = "^".to_string();
for segment in path.split('/') {
if !segment.is_empty() {
regex_pattern.push_str("/");
}
if segment.starts_with("{") && segment.ends_with("}") {
let param_name = &segment[1..segment.len()-1];
regex_pattern.push_str(&format!("(?P<{}>[^/]+)", param_name));
}
else {
regex_pattern.push_str(segment);
}
}
regex_pattern.push_str("$");
return regex_pattern.clone();
}
}
#[derive(Clone)]
pub struct HttpRoute {
pub method: HttpRequestMethod,
pub path: String,
}
impl std::fmt::Display for HttpRoute {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Method: {} Path: {}", self.method, self.path)
}
}