luciders/src/handlers.rs

130 lines
4.3 KiB
Rust
Raw Normal View History

use crate::http::{
2024-11-01 10:14:47 +00:00
requests::{Request, RequestStatus, URL},
responses::{Body, Response, UnitOrBoxedError},
};
use std::{
collections::HashMap,
net::{Shutdown, TcpListener},
};
type DynHandlerFn = dyn Fn(Request) -> Response;
type BoxedHandlerFn = Box<DynHandlerFn>;
2024-11-01 10:14:47 +00:00
// Collection of handlers for requests
2024-11-01 10:14:47 +00:00
pub struct Handlers {
matchers: HashMap<String, BoxedHandlerFn>,
}
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 !!
pub fn new() -> Self {
2024-11-01 10:14:47 +00:00
Handlers {
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
pub fn add_handler(&mut self, path: &str, handler: impl Fn(Request) -> Response + 'static) {
self.matchers.insert(path.to_string(), Box::from(handler));
}
/// Bind these handlers to a listener in order to handle incoming requests
/// You will need to pass in a TcpListener
///
/// !! Call this *after* adding all your handlers with add_handler !!
///
/// listener: TcpListener to bind to
pub fn bind(&self, listener: TcpListener) {
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")
});
return;
}
};
self.handle_req(request)
.unwrap_or_else(|_| eprintln!("Failed to send handle request"));
}
Err(e) => {
eprintln!("Failed to establish connection: {}", e);
return;
}
}
}
}
fn match_handler(&self, url: &URL) -> Option<&BoxedHandlerFn> {
2024-11-01 10:14:47 +00:00
'matching_loop: for (path, handler) in self.matchers.iter() {
// Exact match
if path == &url.path {
return Some(handler);
2024-11-01 10:14:47 +00:00
};
// 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(']') {
2024-11-01 10:14:47 +00:00
continue;
}
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;
}
break 'matching_loop;
}
if url_segment == path_segment {
continue;
}
break 'matching_loop;
}
return Some(handler);
}
2024-11-01 10:14:47 +00:00
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, Body::Static("Not Found")).send();
2024-11-01 10:14:47 +00:00
}
};
Ok(())
}
}