Skip to content

Commit

Permalink
initial implementation of default handler completed
Browse files Browse the repository at this point in the history
  • Loading branch information
tzilist committed Nov 28, 2018
1 parent c6a03ee commit 325eae9
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 37 deletions.
19 changes: 19 additions & 0 deletions examples/default_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![feature(async_await)]

use http::status::StatusCode;
use tide::body;

fn main() {
let mut app = tide::App::new(());
app.at("/").get(async || "Hello, world!");

app.default_handler(async || {
http::Response::builder()
.status(StatusCode::NOT_FOUND)
.header("Content-Type", "text/plain")
.body(body::Body::from(\\_(ツ)_/¯".to_string().into_bytes()))
.unwrap()
});

app.serve("127.0.0.1:7878")
}
9 changes: 9 additions & 0 deletions path_table/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ pub struct RouteMatch<'a> {
pub map: HashMap<&'a str, &'a str>,
}

impl<'a> RouteMatch<'a> {
pub fn empty() -> Self {
RouteMatch {
vec: Vec::new(),
map: HashMap::new(),
}
}
}

impl<R> std::fmt::Debug for PathTable<R> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
struct Children<'a, R>(&'a HashMap<String, PathTable<R>>, Option<&'a Wildcard<R>>);
Expand Down
37 changes: 19 additions & 18 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::{

use crate::{
body::Body,
endpoint::Endpoint,
extract::Extract,
middleware::{logger::RootLogger, RequestContext},
router::{Resource, RouteResult, Router},
Expand Down Expand Up @@ -52,6 +53,11 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
self.router.at(path)
}

pub fn default_handler<T: Endpoint<Data, U>, U>(&mut self, ep: T) -> &mut Self {
self.router.set_default_handler(ep);
self
}

/// Apply `middleware` to the whole app. Note that the order of nesting subrouters and applying
/// middleware matters; see `Router` for details.
pub fn middleware(&mut self, middleware: impl Middleware<Data> + 'static) -> &mut Self {
Expand Down Expand Up @@ -112,27 +118,22 @@ impl<Data: Clone + Send + Sync + 'static> Service for Server<Data> {

FutureObj::new(Box::new(
async move {
if let Some(RouteResult {
let RouteResult {
endpoint,
params,
middleware,
}) = router.route(&path, &method)
{
let ctx = RequestContext {
app_data: data,
req,
params,
endpoint,
next_middleware: middleware,
};
let res = await!(ctx.next());
Ok(res.map(Into::into))
} else {
Ok(http::Response::builder()
.status(http::status::StatusCode::NOT_FOUND)
.body(hyper::Body::empty())
.unwrap())
}
} = router.route(&path, &method);

let ctx = RequestContext {
app_data: data,
req,
params,
endpoint,
next_middleware: middleware,
};
let res = await!(ctx.next());

Ok(res.map(Into::into))
},
))
.compat()
Expand Down
2 changes: 2 additions & 0 deletions src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{Extract, IntoResponse, Request, Response, RouteMatch};
/// A body is a stream of `BodyChunk`s, which are essentially `Vec<u8>` values.
/// Both `Body` and `BodyChunk` values can be easily created from standard byte buffer types,
/// using the `From` trait.
#[derive(Debug)]
pub struct Body {
inner: BodyInner,
}
Expand All @@ -43,6 +44,7 @@ impl From<String> for BodyChunk {
}
}

#[derive(Debug)]
enum BodyInner {
Streaming(BodyStream),
Fixed(Vec<u8>),
Expand Down
94 changes: 75 additions & 19 deletions src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use path_table::{PathTable, RouteMatch};
pub struct Router<Data> {
table: PathTable<ResourceData<Data>>,
middleware_base: Vec<Arc<dyn Middleware<Data> + Send + Sync>>,
default_handler: Arc<DefaultHandler<Data>>,
}

pub(crate) struct RouteResult<'a, Data> {
Expand All @@ -21,6 +22,47 @@ pub(crate) struct RouteResult<'a, Data> {
pub(crate) middleware: &'a [Arc<dyn Middleware<Data> + Send + Sync>],
}

pub async fn base_default_handler() -> http::status::StatusCode {
http::status::StatusCode::NOT_FOUND
}

fn route_match_success<'a, Data>(
route: &'a ResourceData<Data>,
route_match: RouteMatch<'a>,
method: &http::Method,
) -> Option<RouteResult<'a, Data>> {
// If it is a HTTP HEAD request then check if there is a callback in the endpoints map
// if not then fallback to the behavior of HTTP GET else proceed as usual
let endpoint =
if method == http::Method::HEAD && !route.endpoints.contains_key(&http::Method::HEAD) {
route.endpoints.get(&http::Method::GET)?
} else {
route.endpoints.get(method)?
};
let middleware = &*route.middleware;

Some(RouteResult {
endpoint,
params: route_match,
middleware,
})
}

fn route_match_failure<'a, Data>(
default_handler: &'a Arc<DefaultHandler<Data>>,
middleware: &'a Vec<Arc<dyn Middleware<Data> + Send + Sync>>,
) -> RouteResult<'a, Data> {
let endpoint = &default_handler.endpoint;

let params = RouteMatch::empty();

RouteResult {
endpoint: &endpoint,
params,
middleware: &*middleware,
}
}

impl<Data: Clone + Send + Sync + 'static> Router<Data> {
/// Add a new resource at the given `path`, relative to this router.
///
Expand Down Expand Up @@ -61,6 +103,7 @@ impl<Data: Clone + Send + Sync + 'static> Router<Data> {
Resource {
table,
middleware_base: &self.middleware_base,
default_handler: &self.default_handler,
}
}

Expand Down Expand Up @@ -109,30 +152,41 @@ impl<Data: Clone + Send + Sync + 'static> Router<Data> {
self
}

pub fn set_default_handler<T: Endpoint<Data, U>, U>(&mut self, ep: T) -> &mut Self {
self.default_handler = Arc::new(DefaultHandler::new(ep));
self
}

pub(crate) fn route<'a>(
&'a self,
path: &'a str,
method: &http::Method,
) -> Option<RouteResult<'a, Data>> {
let (route, route_match) = self.table.route(path)?;
// If it is a HTTP HEAD request then check if there is a callback in the endpoints map
// if not then fallback to the behavior of HTTP GET else proceed as usual
let endpoint =
if method == http::Method::HEAD && !route.endpoints.contains_key(&http::Method::HEAD) {
route.endpoints.get(&http::Method::GET)?
} else {
route.endpoints.get(method)?
};
let middleware = &*route.middleware;

Some(RouteResult {
endpoint,
params: route_match,
middleware,
})
) -> RouteResult<'a, Data> {
match self.table.route(path) {
Some((route, route_match)) => route_match_success(route, route_match, method)
.or_else(|| {
Some(route_match_failure(
&self.default_handler,
&self.middleware_base,
))
})
.unwrap(),
None => route_match_failure(&self.default_handler, &self.middleware_base),
}
}
}

struct DefaultHandler<Data> {
endpoint: BoxedEndpoint<Data>,
}

impl<Data> DefaultHandler<Data> {
pub fn new<T: Endpoint<Data, U>, U>(ep: T) -> Self {
DefaultHandler {
endpoint: BoxedEndpoint::new(ep),
}
}
}
/// A handle to a resource (identified by a path).
///
/// All HTTP requests are made against resources. After using `Router::at` (or `App::at`) to
Expand All @@ -141,6 +195,7 @@ impl<Data: Clone + Send + Sync + 'static> Router<Data> {
pub struct Resource<'a, Data> {
table: &'a mut PathTable<ResourceData<Data>>,
middleware_base: &'a Vec<Arc<dyn Middleware<Data> + Send + Sync>>,
default_handler: &'a Arc<DefaultHandler<Data>>,
}

struct ResourceData<Data> {
Expand All @@ -160,6 +215,7 @@ impl<'a, Data> Resource<'a, Data> {
let mut subrouter = Router {
table: PathTable::new(),
middleware_base: self.middleware_base.clone(),
default_handler: self.default_handler.clone(),
};
builder(&mut subrouter);
*self.table = subrouter.table;
Expand Down Expand Up @@ -252,7 +308,7 @@ mod tests {
endpoint,
params,
middleware,
} = router.route(path, method)?;
} = router.route(path, method);

let data = Data::default();
let req = http::Request::builder()
Expand All @@ -276,7 +332,7 @@ mod tests {
path: &str,
method: &http::Method,
) -> Option<usize> {
let route_result = router.route(path, method)?;
let route_result = router.route(path, method);
Some(route_result.middleware.len())
}

Expand Down

0 comments on commit 325eae9

Please sign in to comment.