use std::collections::{BTreeSet, HashMap};
use std::sync::Arc;
use crate::{
common::{Method, StatusCode},
headers::header_keys,
request::Request,
response::Response,
};
pub trait Handler: Send + Sync + 'static {
fn handle(&self, request: Request) -> Response;
}
impl<F> Handler for F
where
F: Fn(Request) -> Response + Send + Sync + 'static,
{
fn handle(&self, request: Request) -> Response {
(self)(request)
}
}
type SharedHandler = Arc<dyn Handler>;
pub struct RouterBuilder {
routes: HashMap<String, RouteEntry>,
auto_head: bool,
}
impl Default for RouterBuilder {
fn default() -> Self {
Self {
routes: HashMap::new(),
auto_head: true,
}
}
}
impl RouterBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn auto_head(mut self, enabled: bool) -> Self {
self.auto_head = enabled;
self
}
pub fn route<H>(mut self, method: Method, path: impl Into<String>, handler: H) -> Self
where
H: Handler,
{
let shared: SharedHandler = Arc::new(handler);
self.insert_route(method, path.into(), shared);
self
}
pub fn get<H>(self, path: impl Into<String>, handler: H) -> Self
where
H: Handler,
{
self.route(Method::Get, path, handler)
}
pub fn head<H>(self, path: impl Into<String>, handler: H) -> Self
where
H: Handler,
{
self.route(Method::Head, path, handler)
}
pub fn post<H>(self, path: impl Into<String>, handler: H) -> Self
where
H: Handler,
{
self.route(Method::Post, path, handler)
}
pub fn put<H>(self, path: impl Into<String>, handler: H) -> Self
where
H: Handler,
{
self.route(Method::Put, path, handler)
}
pub fn delete<H>(self, path: impl Into<String>, handler: H) -> Self
where
H: Handler,
{
self.route(Method::Delete, path, handler)
}
pub fn options<H>(self, path: impl Into<String>, handler: H) -> Self
where
H: Handler,
{
self.route(Method::Options, path, handler)
}
pub fn trace<H>(self, path: impl Into<String>, handler: H) -> Self
where
H: Handler,
{
self.route(Method::Trace, path, handler)
}
pub fn patch<H>(self, path: impl Into<String>, handler: H) -> Self
where
H: Handler,
{
self.route(Method::Patch, path, handler)
}
pub fn build(self) -> Router {
Router {
routes: self.routes,
auto_head: self.auto_head,
}
}
fn insert_route(&mut self, method: Method, path: String, handler: SharedHandler) {
let entry = self.routes.entry(path).or_insert_with(RouteEntry::default);
entry.insert(method, handler);
}
}
pub fn router() -> RouterBuilder {
RouterBuilder::default()
}
pub struct Router {
routes: HashMap<String, RouteEntry>,
auto_head: bool,
}
impl Handler for Router {
fn handle(&self, request: Request) -> Response {
let method_token = request.method().as_str().to_owned();
let target_key = target_key(&request);
if let Some(route) = self.routes.get(&target_key) {
match route.resolve(&method_token, self.auto_head) {
RouteMatch::Matched {
handler,
head_fallback,
} => {
let mut response = handler.handle(request);
if head_fallback {
response.strip_body_for_head();
}
response
}
RouteMatch::MethodNotAllowed { allow } => {
if matches!(request.method(), Method::Extension(_)) {
not_implemented()
} else {
method_not_allowed(allow)
}
}
}
} else if matches!(request.method(), Method::Extension(_)) {
not_implemented()
} else {
not_found()
}
}
}
pub struct DefaultRouter;
impl Handler for DefaultRouter {
fn handle(&self, _request: Request) -> Response {
Response::new(StatusCode::NOT_IMPLEMENTED)
}
}
#[derive(Default)]
struct RouteEntry {
handlers: HashMap<String, SharedHandler>,
methods: BTreeSet<String>,
}
impl RouteEntry {
fn insert(&mut self, method: Method, handler: SharedHandler) {
let token = method.as_str().to_owned();
self.handlers.insert(token.clone(), handler);
self.methods.insert(token);
}
fn resolve(&self, method: &str, auto_head: bool) -> RouteMatch {
if let Some(handler) = self.handlers.get(method) {
return RouteMatch::matched(handler, false);
}
if auto_head && method.eq_ignore_ascii_case("HEAD") {
if let Some(handler) = self.handlers.get("GET") {
return RouteMatch::matched(handler, true);
}
}
RouteMatch::method_not_allowed(self.allow_header(auto_head))
}
fn allow_header(&self, auto_head: bool) -> String {
let mut allowed = self.methods.clone();
if auto_head && allowed.contains("GET") {
allowed.insert(String::from("HEAD"));
}
allowed.into_iter().collect::<Vec<_>>().join(", ")
}
}
enum RouteMatch {
Matched {
handler: SharedHandler,
head_fallback: bool,
},
MethodNotAllowed {
allow: String,
},
}
impl RouteMatch {
fn matched(handler: &SharedHandler, head_fallback: bool) -> Self {
RouteMatch::Matched {
handler: Arc::clone(handler),
head_fallback,
}
}
fn method_not_allowed(allow: String) -> Self {
RouteMatch::MethodNotAllowed { allow }
}
}
fn target_key(request: &Request) -> String {
match request.target() {
crate::request::RequestTarget::Origin(path)
| crate::request::RequestTarget::Absolute(path)
| crate::request::RequestTarget::Authority(path) => path.clone(),
crate::request::RequestTarget::Asterisk => String::from("*"),
}
}
fn not_found() -> Response {
Response::new(StatusCode::NOT_FOUND)
}
fn method_not_allowed(allow: String) -> Response {
let mut response = Response::new(StatusCode::METHOD_NOT_ALLOWED);
response.headers_mut().insert(header_keys::ALLOW, allow);
response
}
fn not_implemented() -> Response {
Response::new(StatusCode::NOT_IMPLEMENTED)
}