2024-11-02 10:05:03 +00:00
|
|
|
use crate::{
|
|
|
|
http::{
|
|
|
|
requests::{Request, RequestStatus, URL},
|
|
|
|
responses::{Body, Response, UnitOrBoxedError},
|
|
|
|
},
|
|
|
|
threads::ThreadPool,
|
2024-11-01 08:35:25 +00:00
|
|
|
};
|
|
|
|
use std::{
|
2024-11-03 10:34:07 +00:00
|
|
|
collections::HashMap,
|
|
|
|
net::{Shutdown, TcpListener},
|
|
|
|
sync::Arc,
|
2024-11-01 08:35:25 +00:00
|
|
|
};
|
|
|
|
|
2024-11-02 10:05:03 +00:00
|
|
|
type DynHandlerFn = dyn Fn(Request) -> Response + Send + Sync;
|
2024-11-01 10:33:19 +00:00
|
|
|
type BoxedHandlerFn = Box<DynHandlerFn>;
|
2024-11-02 10:05:03 +00:00
|
|
|
type HandlersMap = HashMap<String, Arc<BoxedHandlerFn>>;
|
2024-11-01 10:14:47 +00:00
|
|
|
|
2024-11-01 08:35:25 +00:00
|
|
|
// Collection of handlers for requests
|
2024-11-01 10:14:47 +00:00
|
|
|
pub struct Handlers {
|
2024-11-02 10:05:03 +00:00
|
|
|
matchers: HandlersMap,
|
2024-11-01 08:35:25 +00:00
|
|
|
}
|
|
|
|
|
2024-11-01 10:14:47 +00:00
|
|
|
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 !!
|
2024-11-01 08:35:25 +00:00
|
|
|
pub fn new() -> Self {
|
2024-11-01 10:14:47 +00:00
|
|
|
Handlers {
|
2024-11-01 08:35:25 +00:00
|
|
|
matchers: HashMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add a request handler
|
|
|
|
///
|
|
|
|
/// !! Be sure to bind this Handlers struct to a TcpListener to actually handle requests !!
|
|
|
|
///
|
|
|
|
/// path: Path to match (no trailing /)
|
|
|
|
/// handler: Function to handle the request. Must return a Response
|
2024-11-02 10:05:03 +00:00
|
|
|
pub fn add_handler(
|
|
|
|
&mut self,
|
|
|
|
path: &str,
|
|
|
|
handler: impl Fn(Request) -> Response + Send + Sync + 'static,
|
|
|
|
) {
|
|
|
|
self.matchers
|
|
|
|
.insert(path.to_string(), Arc::new(Box::from(handler)));
|
2024-11-01 08:35:25 +00:00
|
|
|
}
|
|
|
|
|
2024-11-02 10:05:03 +00:00
|
|
|
/// Bind these handlers to a listener in order to handle incoming requests.
|
|
|
|
/// You will need to pass in a TcpListener.
|
|
|
|
/// This method creates a ThreadPool to handle incoming requests so no multithreading is needed on the part of the caller.
|
2024-11-01 08:35:25 +00:00
|
|
|
///
|
|
|
|
/// !! Call this *after* adding all your handlers with add_handler !!
|
|
|
|
///
|
|
|
|
/// listener: TcpListener to bind to
|
2024-11-02 10:05:03 +00:00
|
|
|
pub fn bind(&self, listener: TcpListener) -> UnitOrBoxedError {
|
|
|
|
let pool = match ThreadPool::build(4) {
|
|
|
|
Some(pool) => pool,
|
|
|
|
None => return Err(Box::from("Failed to create ThreadPool")),
|
|
|
|
};
|
|
|
|
|
2024-11-01 08:35:25 +00:00
|
|
|
for stream in listener.incoming() {
|
|
|
|
match stream {
|
|
|
|
Ok(stream) => {
|
|
|
|
let request = match Request::parse_stream(stream) {
|
|
|
|
RequestStatus::Ok(req) => req,
|
|
|
|
RequestStatus::MalformedHTTP(stream) => {
|
|
|
|
stream.shutdown(Shutdown::Both).unwrap_or_else(|_| {
|
|
|
|
eprintln!("Failed to close malformed HTTP stream")
|
|
|
|
});
|
2024-11-02 10:05:03 +00:00
|
|
|
continue;
|
2024-11-01 08:35:25 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-11-02 10:05:03 +00:00
|
|
|
let handler = match Handlers::match_handler(&self.matchers, &request.url) {
|
|
|
|
Some(handler) => handler.clone(),
|
|
|
|
None => {
|
|
|
|
Response::new(request, 404, Body::Static("Not Found")).send()?;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
pool.execute(move || {
|
|
|
|
handler.as_ref()(request)
|
|
|
|
.send()
|
|
|
|
.unwrap_or_else(|_| eprintln!("Failed to send response"));
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|_| {
|
|
|
|
eprintln!("Failed to send job to ThreadPool");
|
|
|
|
});
|
2024-11-01 08:35:25 +00:00
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("Failed to establish connection: {}", e);
|
|
|
|
}
|
2024-11-02 10:05:03 +00:00
|
|
|
};
|
2024-11-01 08:35:25 +00:00
|
|
|
}
|
2024-11-02 10:05:03 +00:00
|
|
|
|
|
|
|
return Ok(());
|
2024-11-01 08:35:25 +00:00
|
|
|
}
|
|
|
|
|
2024-11-02 10:05:03 +00:00
|
|
|
fn match_handler<'a>(matchers: &'a HandlersMap, url: &URL) -> Option<&'a Arc<BoxedHandlerFn>> {
|
|
|
|
'matching_loop: for (path, handler) in matchers.iter() {
|
2024-11-01 10:14:47 +00:00
|
|
|
// Exact match
|
|
|
|
if path == &url.path {
|
2024-11-01 10:33:19 +00:00
|
|
|
return Some(handler);
|
2024-11-01 10:14:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Segment matching
|
2024-11-02 10:05:03 +00:00
|
|
|
let url_segments = url.path.split('/').collect::<Vec<&str>>();
|
2024-11-01 10:14:47 +00:00
|
|
|
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
|
2024-11-02 10:05:03 +00:00
|
|
|
if (url_segments.len() != path_segments.len()) || (path_segments.len() == 0) {
|
2024-11-01 10:14:47 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check each segment of the url
|
2024-11-02 10:05:03 +00:00
|
|
|
for (url_segment, path_segment) in url_segments.iter().zip(path_segments.iter()) {
|
2024-11-01 10:14:47 +00:00
|
|
|
if path_segment.starts_with('[') {
|
|
|
|
// e.g. /path/[id]
|
2024-11-01 10:59:38 +00:00
|
|
|
if path_segment.ends_with(']') {
|
2024-11-01 10:14:47 +00:00
|
|
|
continue;
|
|
|
|
}
|
2024-11-01 10:59:38 +00:00
|
|
|
|
2024-11-01 10:14:47 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2024-11-01 23:48:59 +00:00
|
|
|
continue 'matching_loop;
|
2024-11-01 10:14:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if url_segment == path_segment {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-11-01 23:48:59 +00:00
|
|
|
continue 'matching_loop;
|
2024-11-01 10:14:47 +00:00
|
|
|
}
|
|
|
|
|
2024-11-01 10:33:19 +00:00
|
|
|
return Some(handler);
|
2024-11-01 08:35:25 +00:00
|
|
|
}
|
2024-11-01 10:14:47 +00:00
|
|
|
|
|
|
|
None
|
|
|
|
}
|
2024-11-01 08:35:25 +00:00
|
|
|
}
|