pub mod builder;
pub use builder::{build_router, build_simple_router};
pub mod response;
pub mod route;
pub mod tree;
mod non_match;
pub use self::non_match::RouteNonMatch;
use std::pin::Pin;
use std::sync::Arc;
use futures_util::future::{self, FutureExt, TryFutureExt};
use hyper::header::ALLOW;
use hyper::{Body, Response, StatusCode};
use log::{error, trace};
use crate::handler::{Handler, HandlerFuture, IntoResponse, NewHandler};
use crate::helpers::http::request::path::RequestPathSegments;
use crate::helpers::http::response::create_empty_response;
use crate::router::response::ResponseFinalizer;
use crate::router::route::{Delegation, Route};
use crate::router::tree::segment::SegmentMapping;
use crate::router::tree::Tree;
use crate::state::{request_id, State};
struct RouterData {
tree: Tree,
response_finalizer: ResponseFinalizer,
}
impl RouterData {
fn new(tree: Tree, response_finalizer: ResponseFinalizer) -> RouterData {
RouterData {
tree,
response_finalizer,
}
}
}
#[derive(Clone)]
pub struct Router {
data: Arc<RouterData>,
}
impl NewHandler for Router {
type Instance = Router;
fn new_handler(&self) -> anyhow::Result<Self::Instance> {
trace!(" cloning instance");
Ok(self.clone())
}
}
impl Handler for Router {
fn handle(self, mut state: State) -> Pin<Box<HandlerFuture>> {
trace!("[{}] starting", request_id(&state));
let future = match state.try_take::<RequestPathSegments>() {
Some(rps) => {
if let Some((node, params, processed)) = self.data.tree.traverse(rps.segments()) {
match node.select_route(&state) {
Ok(route) => match route.delegation() {
Delegation::External => {
trace!("[{}] delegating to secondary router", request_id(&state));
state.put(rps.subsegments(processed));
route.dispatch(state)
}
Delegation::Internal => {
trace!("[{}] dispatching to route", request_id(&state));
self.dispatch(state, params, route)
}
},
Err(non_match) => {
let (status, allow) = non_match.deconstruct();
trace!("[{}] responding with error status", request_id(&state));
let mut res = create_empty_response(&state, status);
if let StatusCode::METHOD_NOT_ALLOWED = status {
for allowed in allow {
res.headers_mut().append(
ALLOW,
allowed.as_str().to_string().parse().unwrap(),
);
}
}
future::ok((state, res)).boxed()
}
}
} else {
trace!("[{}] did not find routable node", request_id(&state));
let res = create_empty_response(&state, StatusCode::NOT_FOUND);
future::ok((state, res)).boxed()
}
}
None => {
trace!("[{}] invalid request path segments", request_id(&state));
let res = create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR);
future::ok((state, res)).boxed()
}
};
self.finalize_response(future)
}
}
impl Router {
fn new(tree: Tree, response_finalizer: ResponseFinalizer) -> Router {
let router_data = RouterData::new(tree, response_finalizer);
Router {
data: Arc::new(router_data),
}
}
fn dispatch<'a>(
&self,
mut state: State,
params: SegmentMapping<'a>,
route: &Box<dyn Route<ResBody = Body> + Send + Sync>,
) -> Pin<Box<HandlerFuture>> {
match route.extract_request_path(&mut state, params) {
Ok(()) => {
trace!("[{}] extracted request path", request_id(&state));
match route.extract_query_string(&mut state) {
Ok(()) => {
trace!("[{}] extracted query string", request_id(&state));
trace!("[{}] dispatching", request_id(&state));
route.dispatch(state)
}
Err(_) => {
error!("[{}] the server cannot or will not process the request due to a client error within the query string",
request_id(&state));
let mut res = Response::new(Body::empty());
route.extend_response_on_query_string_error(&mut state, &mut res);
future::ok((state, res)).boxed()
}
}
}
Err(_) => {
error!(
"[{}] the server cannot or will not process the request due to a client error on the request path",
request_id(&state)
);
let mut res = Response::new(Body::empty());
route.extend_response_on_path_error(&mut state, &mut res);
future::ok((state, res)).boxed()
}
}
}
fn finalize_response(&self, result: Pin<Box<HandlerFuture>>) -> Pin<Box<HandlerFuture>> {
let response_finalizer = self.data.response_finalizer.clone();
result
.or_else(|(state, err)| {
trace!(
"[{}] converting error into http response \
during finalization: {:?}",
request_id(&state),
err
);
let response = err.into_response(&state);
future::ok((state, response))
})
.and_then(move |(state, res)| {
trace!("[{}] handler complete", request_id(&state));
response_finalizer.finalize(state, res)
})
.boxed()
}
}
#[cfg(test)]
mod tests {
use super::*;
use hyper::header::{HeaderMap, CONTENT_LENGTH, CONTENT_TYPE};
use hyper::{Body, Method, Uri};
use mime::TEXT_PLAIN;
use std::str::FromStr;
use crate::extractor::{NoopPathExtractor, NoopQueryStringExtractor};
use crate::handler::HandlerError;
use crate::pipeline::{finalize_pipeline_set, new_pipeline_set};
use crate::router::response::ResponseFinalizerBuilder;
use crate::router::route::dispatch::DispatcherImpl;
use crate::router::route::matcher::{
AndRouteMatcher, ContentTypeHeaderRouteMatcher, MethodOnlyRouteMatcher,
};
use crate::router::route::{Extractors, RouteImpl};
use crate::router::tree::node::Node;
use crate::router::tree::segment::SegmentType;
use crate::router::tree::Tree;
use crate::state::set_request_id;
fn handler(state: State) -> (State, Response<Body>) {
(state, Response::new(Body::empty()))
}
fn send_request(
r: Router,
method: Method,
uri: &str,
) -> ::std::result::Result<(State, Response<Body>), (State, HandlerError)> {
let uri = Uri::from_str(uri).unwrap();
let mut headers = HeaderMap::new();
if method == Method::POST {
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
headers.insert(CONTENT_LENGTH, 0.into());
}
let mut state = State::new();
state.put(RequestPathSegments::new(uri.path()));
state.put(method);
state.put(uri);
state.put(headers);
set_request_id(&mut state);
futures_executor::block_on(r.handle(state))
}
#[test]
fn internal_server_error_if_no_request_path_segments() {
let tree = Tree::new();
let router = Router::new(tree, ResponseFinalizerBuilder::new().finalize());
let method = Method::GET;
let uri = Uri::from_str("https://test.gotham.rs").unwrap();
let mut state = State::new();
state.put(method);
state.put(uri);
state.put(HeaderMap::new());
set_request_id(&mut state);
match futures_executor::block_on(router.handle(state)) {
Ok((_state, res)) => {
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
Err(_) => unreachable!("Router should have handled request"),
};
}
#[test]
fn not_found_error_if_request_path_is_not_found() {
let tree = Tree::new();
let router = Router::new(tree, ResponseFinalizerBuilder::new().finalize());
match send_request(router, Method::GET, "https://test.gotham.rs") {
Ok((_state, res)) => {
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
Err(_) => unreachable!("Router should have handled request"),
};
}
#[test]
fn custom_error_if_leaf_found_but_matching_route_not_found() {
let pipeline_set = finalize_pipeline_set(new_pipeline_set());
let mut tree = Tree::new();
let route = {
let methods = vec![Method::POST];
let matcher = AndRouteMatcher::new(
MethodOnlyRouteMatcher::new(methods),
ContentTypeHeaderRouteMatcher::new(vec![TEXT_PLAIN]),
);
let dispatcher = Box::new(DispatcherImpl::new(|| Ok(handler), (), pipeline_set));
let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> =
Extractors::new();
let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::Internal);
Box::new(route)
};
tree.add_route(route);
let router = Router::new(tree, ResponseFinalizerBuilder::new().finalize());
match send_request(router.clone(), Method::GET, "https://test.gotham.rs") {
Ok((_state, res)) => {
assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
assert_eq!(
res.headers()
.get_all(ALLOW)
.iter()
.map(|it| it.to_str().unwrap())
.collect::<Vec<&str>>(),
vec!["POST"]
);
}
Err(_) => unreachable!("Router should have handled request"),
};
match send_request(router, Method::POST, "https://test.gotham.rs") {
Ok((_state, res)) => {
assert_eq!(res.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
assert!(res.headers().get_all(ALLOW).iter().next().is_none());
}
Err(_) => unreachable!("Router should have handled request"),
};
}
#[test]
fn success_if_leaf_and_route_found() {
let pipeline_set = finalize_pipeline_set(new_pipeline_set());
let mut tree = Tree::new();
let route = {
let methods = vec![Method::GET];
let matcher = MethodOnlyRouteMatcher::new(methods);
let dispatcher = Box::new(DispatcherImpl::new(|| Ok(handler), (), pipeline_set));
let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> =
Extractors::new();
let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::Internal);
Box::new(route)
};
tree.add_route(route);
let router = Router::new(tree, ResponseFinalizerBuilder::new().finalize());
match send_request(router, Method::GET, "https://test.gotham.rs") {
Ok((_state, res)) => {
assert_eq!(res.status(), StatusCode::OK);
}
Err(_) => unreachable!("Router should have handled request"),
};
}
#[test]
fn delegates_to_secondary_router() {
let delegated_router = {
let pipeline_set = finalize_pipeline_set(new_pipeline_set());
let mut tree = Tree::new();
let route = {
let methods = vec![Method::GET];
let matcher = MethodOnlyRouteMatcher::new(methods);
let dispatcher = Box::new(DispatcherImpl::new(|| Ok(handler), (), pipeline_set));
let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> =
Extractors::new();
let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::Internal);
Box::new(route)
};
tree.add_route(route);
Router::new(tree, ResponseFinalizerBuilder::new().finalize())
};
let pipeline_set = finalize_pipeline_set(new_pipeline_set());
let mut tree = Tree::new();
let mut delegated_node = Node::new("var", SegmentType::Dynamic);
let route = {
let methods = vec![Method::GET];
let matcher = MethodOnlyRouteMatcher::new(methods);
let dispatcher = Box::new(DispatcherImpl::new(delegated_router, (), pipeline_set));
let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> =
Extractors::new();
let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::External);
Box::new(route)
};
delegated_node.add_route(route);
tree.add_child(delegated_node);
let router = Router::new(tree, ResponseFinalizerBuilder::new().finalize());
match send_request(router.clone(), Method::GET, "https://test.gotham.rs") {
Ok((_state, res)) => {
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
Err(_) => unreachable!("Router should have handled request"),
};
match send_request(router, Method::GET, "https://test.gotham.rs/api") {
Ok((_state, res)) => {
assert_eq!(res.status(), StatusCode::OK);
}
Err(_) => unreachable!("Router should have handled request"),
};
}
#[test]
fn executes_response_finalizer_when_present() {
let tree = Tree::new();
let mut response_finalizer_builder = ResponseFinalizerBuilder::new();
let not_found_extender = |_s: &mut State, r: &mut Response<Body>| {
r.headers_mut()
.insert(CONTENT_LENGTH, "3".to_owned().parse().unwrap());
};
response_finalizer_builder.add(StatusCode::NOT_FOUND, Box::new(not_found_extender));
let response_finalizer = response_finalizer_builder.finalize();
let router = Router::new(tree, response_finalizer);
match send_request(router, Method::GET, "https://test.gotham.rs/api") {
Ok((_state, res)) => {
assert_eq!(res.headers().get(CONTENT_LENGTH).unwrap(), "3");
}
Err(_) => unreachable!("Router should have correctly handled request"),
};
}
}