Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing HTTP 405 #1335

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1cc49ff
Returning 405 if http method doesn't match
Drunpy Apr 14, 2020
ddc59f5
Removed matching with request_status
Drunpy Apr 15, 2020
10d143e
Checking if requests method matches with route method.
Drunpy Apr 15, 2020
d93b4b1
Fixing form tests
Drunpy Apr 15, 2020
1d42433
Tests
Drunpy Apr 15, 2020
36544ba
Code formatting using rustfmt
Drunpy Apr 15, 2020
684d4d4
Code formatting, no additions..
Drunpy Apr 15, 2020
cd57e47
Fix route not interating in all matches.
Drunpy Apr 16, 2020
42b4a14
Update tests
Drunpy Apr 16, 2020
ef171cc
To find a matching 405 template to test_root
Drunpy Apr 16, 2020
5d526c8
After 2 freaking days I think finally finished fixing these tests lol
Drunpy Apr 18, 2020
71abd12
Code formatting
Drunpy Apr 18, 2020
12ee5d8
Merge branch 'master' of https://github.com/SergioBenitez/Rocket
Drunpy Apr 22, 2020
e75ab5e
Remove format changes
Drunpy Jun 11, 2020
ad0e9ef
More code changes removals
Drunpy Jun 11, 2020
d3ba36e
Now code formatting is as it was
Drunpy Jun 11, 2020
4c3e516
Merge branch 'master' into master
Drunpy Jun 11, 2020
13b6386
[WIP] Passing the logic to the router and performance corrections
Drunpy Jun 23, 2020
197f8ee
Simplified the logic at Rocket.route
Drunpy Jul 3, 2020
f288f8d
Fix formatting in Rocket.route
Drunpy Jul 3, 2020
a6ad90f
Added description to `restrict` param at Router.route
Drunpy Jul 3, 2020
020af51
Removed extra comments
Drunpy Aug 7, 2020
565151b
Merge branch 'master' of https://github.com/SergioBenitez/Rocket
Drunpy Aug 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/codegen/tests/route-format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fn test_formats() {
assert_eq!(response.into_string().unwrap(), "plain");

let response = client.put("/").header(ContentType::HTML).dispatch();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.status(), Status::MethodNotAllowed);
}

// Test custom formats.
Expand Down
72 changes: 53 additions & 19 deletions core/lib/src/rocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,30 +386,64 @@ impl Rocket {
) -> impl Future<Output = handler::Outcome<'r>> + 's {
async move {
// Go through the list of matching routes until we fail or succeed.
let matches = self.router.route(request);
for route in matches {
// Retrieve and set the requests parameters.
info_!("Matched: {}", route);
request.set_route(route);

// Dispatch the request to the handler.
let outcome = route.handler.handle(request, data).await;

// Check if the request processing completed (Some) or if the
// request needs to be forwarded. If it does, continue the loop
// (None) to try again.
info_!("{} {}", Paint::default("Outcome:").bold(), outcome);
match outcome {
o@Outcome::Success(_) | o@Outcome::Failure(_) => return o,
Outcome::Forward(unused_data) => data = unused_data,
//let matches = self.router.route(request);
let method_matches = self.router.route(request, true);

if method_matches.len() > 0 {
for route in &method_matches {
// Retrieve and set the requests parameters.
info_!("Matched: {}", route);

request.set_route(route);

// Dispatch the request to the handler.
let outcome = route.handler.handle(request, data);

// Check if the request processing completed or if the request needs
// to be forwarded. If it does, continue the loop to try again.
info_!("{} {}", Paint::default("Outcome:").bold(), outcome);
match outcome {
o @ Outcome::Success(_) | o @ Outcome::Failure(_) => return o,
Outcome::Forward(unused_data) => data = unused_data,
};
}
}

error_!("No matching routes for {}.", request);
Outcome::Forward(data)

let match_any = self.router.route(request, false);

if match_any.len() > 0 {

info_!(
"{}",
Paint::yellow("A similar route exists: ").bold()
);

for route in &match_any {

info_!(
" - {}",
Paint::yellow(&route).bold()
);

// Must pass HEAD requests foward
if &request.method() == &Method::Head {
continue
}

else if &route.method != &request.method() {
return Outcome::Failure(Status::MethodNotAllowed);
}

continue

}
}
}
}


// FIM ------------------------------
#[inline]
pub(crate) async fn dispatch<'s, 'r: 's>(
&'s self,
Expand Down
17 changes: 12 additions & 5 deletions core/lib/src/router/collider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl Route {
&& formats_collide(self, other)
}

/// Determines if this route matches against the given request. This means
/// Determines if this route matches against the given request if . This means
/// that:
///
/// * The route's method matches that of the incoming request.
Expand All @@ -38,12 +38,19 @@ impl Route {
/// request query string, though in any position.
/// - If no query in route, requests with/without queries match.
#[doc(hidden)]
pub fn matches(&self, req: &Request<'_>) -> bool {
pub fn matches_by_method(&self, req: &Request<'_>) -> bool {
self.method == req.method()
&& paths_match(self, req)
&& queries_match(self, req)
&& queries_match(self, req)
&& formats_match(self, req)
}

/// Match agoinst any method.
#[doc(hidden)]
pub fn match_any(&self, req: &Request<'_>) -> bool {
paths_match(self, req) && queries_match(self, req) && formats_match(self, req)
}

}

fn paths_collide(route: &Route, other: &Route) -> bool {
Expand Down Expand Up @@ -416,7 +423,7 @@ mod tests {
route.format = Some(mt_str.parse::<MediaType>().unwrap());
}

route.matches(&req)
route.matches_by_method(&req)
}

#[test]
Expand Down Expand Up @@ -471,7 +478,7 @@ mod tests {
let rocket = Rocket::custom(Config::development());
let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI"));
let route = Route::ranked(0, Get, b.to_string(), dummy);
route.matches(&req)
route.matches_by_method(&req)
}

#[test]
Expand Down
49 changes: 30 additions & 19 deletions core/lib/src/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct Router {
routes: HashMap<Selector, Vec<Route>>,
}


impl Router {
pub fn new() -> Router {
Router { routes: HashMap::new() }
Expand All @@ -31,15 +32,26 @@ impl Router {
entries.insert(i, route);
}

pub fn route<'b>(&'b self, req: &Request<'_>) -> Vec<&'b Route> {
// Note that routes are presorted by rank on each `add`.
let matches = self.routes.get(&req.method()).map_or(vec![], |routes| {
routes.iter()
.filter(|r| r.matches(req))
.collect()
});
// Param `restrict` will restrict the route matching by the http method of `req`
// With `restrict` == false and `req` method == GET both will be matched:
// - GET hello/world <-
// - POST hello/world <-
// With `restrict` == true and `req` method == GET only the first one will be matched:
// - GET foo/bar <-
// - POST foo/bar
pub fn route<'b>(&'b self, req: &Request<'_>, restrict: bool) -> Vec<&'b Route> {
let mut matches = Vec::new();
for (_method, routes_vec) in self.routes.iter() {
for _route in routes_vec {
if _route.matches_by_method(req) {
matches.push(_route);
} else if !restrict && _route.match_any(req){
matches.push(_route);
}
}
}

trace_!("Routing the request: {}", req);
trace_!("Routing(restrict: {}): {}", &restrict, req);
trace_!("All matches: {:?}", matches);
matches
}
Expand Down Expand Up @@ -226,7 +238,7 @@ mod test {
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
let rocket = Rocket::custom(Config::development());
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
let matches = router.route(&request);
let matches = router.route(&request, false);
if matches.len() > 0 {
Some(matches[0])
} else {
Expand All @@ -237,7 +249,7 @@ mod test {
fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> {
let rocket = Rocket::custom(Config::development());
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
router.route(&request)
router.route(&request, false)
}

#[test]
Expand Down Expand Up @@ -275,30 +287,29 @@ mod test {
#[test]
fn test_err_routing() {
let router = router_with_routes(&["/hello"]);
assert!(route(&router, Put, "/hello").is_none());
assert!(route(&router, Post, "/hello").is_none());
assert!(route(&router, Options, "/hello").is_none());
assert!(route(&router, Put, "/hello").is_some());
assert!(route(&router, Post, "/hello").is_some());
assert!(route(&router, Options, "/hello").is_some());
assert!(route(&router, Get, "/hell").is_none());
assert!(route(&router, Get, "/hi").is_none());
assert!(route(&router, Get, "/hello/there").is_none());
assert!(route(&router, Get, "/hello/i").is_none());
assert!(route(&router, Get, "/hillo").is_none());

let router = router_with_routes(&["/<a>"]);
assert!(route(&router, Put, "/hello").is_none());
assert!(route(&router, Post, "/hello").is_none());
assert!(route(&router, Options, "/hello").is_none());
assert!(route(&router, Put, "/hello").is_some());
assert!(route(&router, Post, "/hello").is_some());
assert!(route(&router, Options, "/hello").is_some());
assert!(route(&router, Get, "/hello/there").is_none());
assert!(route(&router, Get, "/hello/i").is_none());

let router = router_with_routes(&["/<a>/<b>"]);
assert!(route(&router, Put, "/a/b").is_some());
assert!(route(&router, Put, "/hello/hi").is_some());
assert!(route(&router, Get, "/a/b/c").is_none());
assert!(route(&router, Get, "/a").is_none());
assert!(route(&router, Get, "/a/").is_none());
assert!(route(&router, Get, "/a/b/c/d").is_none());
assert!(route(&router, Put, "/hello/hi").is_none());
assert!(route(&router, Put, "/a/b").is_none());
assert!(route(&router, Put, "/a/b").is_none());
}

macro_rules! assert_ranked_routes {
Expand Down
2 changes: 1 addition & 1 deletion core/lib/tests/form_method-issue-45.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ mod tests {
.body("_method=patch&form_data=Form+data")
.dispatch();

assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.status(), Status::MethodNotAllowed);
}
}
103 changes: 103 additions & 0 deletions core/lib/tests/return_method_not_allowed_issue_1224.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#![feature(proc_macro_hygiene)]

#[macro_use]
extern crate rocket;

#[get("/index")]
fn get_index() -> &'static str {
"GET index :)"
}

#[post("/index")]
fn post_index() -> &'static str {
"POST index :)"
}

#[post("/hello")]
fn post_hello() -> &'static str {
"POST Hello, world!"
}

mod tests {
use super::*;
use rocket::http::Status;
use rocket::local::Client;

#[test]
fn test_http_200_when_same_route_with_diff_meth() {
let rocket = rocket::ignite()
.mount("/", routes![get_index, post_index]);

let client = Client::new(rocket).unwrap();

let response = client.post("/index").dispatch();

assert_eq!(response.status(), Status::Ok);
}

#[test]
fn test_http_200_when_head_request() {
let rocket = rocket::ignite().mount("/", routes![get_index]);

let client = Client::new(rocket).unwrap();

let response = client.head("/index").dispatch();

assert_eq!(response.status(), Status::Ok);
}

#[test]
fn test_http_200_when_route_is_ok() {
let rocket = rocket::ignite().mount("/", routes![get_index]);

let client = Client::new(rocket).unwrap();

let response = client.get("/index").dispatch();

assert_eq!(response.status(), Status::Ok);
}

#[test]
fn test_http_200_with_params() {
let rocket = rocket::ignite().mount("/", routes![get_index]);

let client = Client::new(rocket).unwrap();

let response = client.get("/index?say=hi").dispatch();

assert_eq!(response.status(), Status::Ok);
}

#[test]
fn test_http_404_when_route_not_match() {
let rocket = rocket::ignite().mount("/", routes![get_index]);

let client = Client::new(rocket).unwrap();

let response = client.get("/abc").dispatch();

assert_eq!(response.status(), Status::NotFound);
}

#[test]
fn test_http_405_when_method_not_match() {
let rocket = rocket::ignite().mount("/", routes![get_index]);

let client = Client::new(rocket).unwrap();

let response = client.post("/index").dispatch();

assert_eq!(response.status(), Status::MethodNotAllowed);
}

#[test]
fn test_http_405_with_params() {
let rocket = rocket::ignite().mount("/", routes![post_hello]);

let client = Client::new(rocket).unwrap();

let response = client.get("/hello?say=hi").dispatch();

assert_eq!(response.status(), Status::MethodNotAllowed);
}
}
2 changes: 1 addition & 1 deletion examples/errors/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn forced_error_and_default_catcher() {
fn test_hello_invalid_age() {
let client = Client::new(super::rocket()).unwrap();

for &(name, age) in &[("Ford", -129), ("Trillian", 128)] {
for &(name, age) in &[("Ford", "s"), ("Trillian", "f")] {
let request = client.get(format!("/hello/{}/{}", name, age));
let expected = super::not_found(request.inner());
let response = request.dispatch();
Expand Down
7 changes: 4 additions & 3 deletions examples/handlebars_templates/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ fn test_root() {
dispatch!(*method, "/", |client, response| {
let mut map = std::collections::HashMap::new();
map.insert("path", "/");
let expected = Template::show(client.cargo(), "error/404", &map).unwrap();
//let expected = Template::show(client.cargo(), "error/404", &map).unwrap();

assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.into_string(), Some(expected));
assert_eq!(response.status(), Status::MethodNotAllowed);
// FIND A MATCHING TEMPLATE TO HTTP 405 HERE
//assert_eq!(response.body_string(), Some(expected));
});
}
}
Expand Down
17 changes: 17 additions & 0 deletions examples/handlebars_templates/templates/error/405.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>405 Method Not Allowed</title>
</head>
<body align="center">
<div role="main" align="center">
<h1>405: Method Not Allowed</h1>
<p>The request method is not supported for the requested resource.</p>
<hr />
</div>
<div role="contentinfo" align="center">
<small>Rocket</small>
</div>
</body>
</html>
Loading