mirror of https://github.com/rwf2/Rocket.git
Simplify and optimize router.
This surfaced a dormant concurrency related issue. Prior to this commit, the router used `routed_segments()` to retrieve the path segments of the request. This was okay as there was no route in the request, and matched segments were retrieved eagerly. This commit makes segment matching lazy, so no matching occurs if unnecessary. Between two matches, a `route` is atomically set of `Request`. This is now visible in `routed_segments()`, which should not have considered the current route in the first place. This was fixed.
This commit is contained in:
parent
39d7931d6a
commit
8749d7293a
|
@ -525,8 +525,10 @@ impl Rocket {
|
||||||
/// * there were no fairing failures
|
/// * there were no fairing failures
|
||||||
/// * a secret key, if needed, is securely configured
|
/// * a secret key, if needed, is securely configured
|
||||||
pub(crate) async fn prelaunch_check(&mut self) -> Result<(), Error> {
|
pub(crate) async fn prelaunch_check(&mut self) -> Result<(), Error> {
|
||||||
if let Err(e) = self.router.collisions() {
|
let collisions: Vec<_> = self.router.collisions().collect();
|
||||||
return Err(Error::new(ErrorKind::Collision(e)));
|
if !collisions.is_empty() {
|
||||||
|
let owned = collisions.into_iter().map(|(a, b)| (a.clone(), b.clone()));
|
||||||
|
return Err(Error::new(ErrorKind::Collision(owned.collect())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(failures) = self.fairings.failures() {
|
if let Some(failures) = self.fairings.failures() {
|
||||||
|
|
|
@ -71,7 +71,7 @@ fn paths_collide(route: &Route, other: &Route) -> bool {
|
||||||
|
|
||||||
fn paths_match(route: &Route, req: &Request<'_>) -> bool {
|
fn paths_match(route: &Route, req: &Request<'_>) -> bool {
|
||||||
let route_segments = &route.uri.metadata.path_segs;
|
let route_segments = &route.uri.metadata.path_segs;
|
||||||
let req_segments = req.routed_segments(0..);
|
let req_segments = req.uri().path_segments();
|
||||||
if route_segments.len() > req_segments.len() + 1 {
|
if route_segments.len() > req_segments.len() + 1 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::http::Method;
|
use crate::http::Method;
|
||||||
use crate::handler::dummy;
|
|
||||||
|
|
||||||
pub use self::route::Route;
|
pub use self::route::Route;
|
||||||
pub use self::uri::RouteUri;
|
pub use self::uri::RouteUri;
|
||||||
|
@ -34,64 +33,33 @@ impl Router {
|
||||||
entries.insert(i, route);
|
entries.insert(i, route);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn route<'b>(&'b self, req: &Request<'_>) -> Vec<&'b Route> {
|
pub fn route<'r, 'a: 'r>(&'a self, req: &'r Request<'r>) -> impl Iterator<Item = &'a Route> + 'r {
|
||||||
// Note that routes are presorted by rank on each `add`.
|
// Note that routes are presorted by rank on each `add`.
|
||||||
let matches = self.routes.get(&req.method()).map_or(vec![], |routes| {
|
self.routes.get(&req.method())
|
||||||
routes.iter()
|
.into_iter()
|
||||||
.filter(|r| r.matches(req))
|
.flat_map(move |routes| routes.iter().filter(move |r| r.matches(req)))
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
|
|
||||||
trace_!("Routing the request: {}", req);
|
|
||||||
trace_!("All matches: {:?}", matches);
|
|
||||||
matches
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn collisions(&mut self) -> Result<(), Vec<(Route, Route)>> {
|
pub(crate) fn collisions(&self) -> impl Iterator<Item = (&Route, &Route)> {
|
||||||
let mut collisions = vec![];
|
let all_routes = self.routes.values().flat_map(|v| v.iter());
|
||||||
for routes in self.routes.values_mut() {
|
all_routes.clone().enumerate()
|
||||||
for i in 0..routes.len() {
|
.flat_map(move |(i, a)| {
|
||||||
let (left, right) = routes.split_at_mut(i);
|
all_routes.clone()
|
||||||
for a_route in left.iter_mut() {
|
.skip(i + 1)
|
||||||
for b_route in right.iter_mut() {
|
.filter(move |b| b.collides_with(a))
|
||||||
if a_route.collides_with(b_route) {
|
.map(move |b| (a, b))
|
||||||
let dummy_a = Route::new(Method::Get, "/", dummy);
|
})
|
||||||
let a = std::mem::replace(a_route, dummy_a);
|
|
||||||
let dummy_b = Route::new(Method::Get, "/", dummy);
|
|
||||||
let b = std::mem::replace(b_route, dummy_b);
|
|
||||||
collisions.push((a, b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if collisions.is_empty() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(collisions)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn routes<'a>(&'a self) -> impl Iterator<Item=&'a Route> + 'a {
|
pub fn routes(&self) -> impl Iterator<Item = &Route> {
|
||||||
self.routes.values().flat_map(|v| v.iter())
|
self.routes.values().flat_map(|v| v.iter())
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is slow. Don't expose this publicly; only for tests.
|
// This is slow. Don't expose this publicly; only for tests.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn has_collisions(&self) -> bool {
|
fn has_collisions(&self) -> bool {
|
||||||
for routes in self.routes.values() {
|
self.collisions().next().is_some()
|
||||||
for (i, a_route) in routes.iter().enumerate() {
|
|
||||||
for b_route in routes.iter().skip(i + 1) {
|
|
||||||
if a_route.collides_with(b_route) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,21 +234,17 @@ mod test {
|
||||||
assert!(!default_rank_route_collisions(&["/hi?<c>", "/hi?c"]));
|
assert!(!default_rank_route_collisions(&["/hi?<c>", "/hi?c"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
|
fn route<'a>(router: &'a Router, method: Method, uri: &'a str) -> Option<&'a Route> {
|
||||||
let rocket = Rocket::custom(Config::default());
|
let rocket = Rocket::custom(Config::default());
|
||||||
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
|
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
|
||||||
let matches = router.route(&request);
|
let route = router.route(&request).next();
|
||||||
if matches.len() > 0 {
|
route
|
||||||
Some(matches[0])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> {
|
fn matches<'a>(router: &'a Router, method: Method, uri: &'a str) -> Vec<&'a Route> {
|
||||||
let rocket = Rocket::custom(Config::default());
|
let rocket = Rocket::custom(Config::default());
|
||||||
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
|
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
|
||||||
router.route(&request)
|
router.route(&request).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -286,8 +286,7 @@ impl Rocket {
|
||||||
mut data: Data,
|
mut data: Data,
|
||||||
) -> handler::Outcome<'r> {
|
) -> handler::Outcome<'r> {
|
||||||
// Go through the list of matching routes until we fail or succeed.
|
// Go through the list of matching routes until we fail or succeed.
|
||||||
let matches = self.router.route(request);
|
for route in self.router.route(request) {
|
||||||
for route in matches {
|
|
||||||
// Retrieve and set the requests parameters.
|
// Retrieve and set the requests parameters.
|
||||||
info_!("Matched: {}", route);
|
info_!("Matched: {}", route);
|
||||||
request.set_route(route);
|
request.set_route(route);
|
||||||
|
|
Loading…
Reference in New Issue