mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-25 00:00:23 +02:00
Fallback to calling next route if no methods match (#224)
This removes a small foot gun from the routing.
This means matching different HTTP methods for the same route that
aren't defined together now works.
So `Router::new().route("/", get(...)).route("/", post(...))` now
accepts both `GET` and `POST`. Previously only `POST` would be accepted.
This commit is contained in:
+22
-39
@@ -597,6 +597,7 @@ impl<S, F, B> Service<Request<B>> for Route<S, F>
|
||||
where
|
||||
S: Service<Request<B>, Response = Response<BoxBody>> + Clone,
|
||||
F: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error> + Clone,
|
||||
B: Send + Sync + 'static,
|
||||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = S::Error;
|
||||
@@ -610,7 +611,7 @@ where
|
||||
if let Some(captures) = self.pattern.full_match(&req) {
|
||||
insert_url_params(&mut req, captures);
|
||||
let fut = self.svc.clone().oneshot(req);
|
||||
RouteFuture::a(fut)
|
||||
RouteFuture::a(fut, self.fallback.clone())
|
||||
} else {
|
||||
let fut = self.fallback.clone().oneshot(req);
|
||||
RouteFuture::b(fut)
|
||||
@@ -689,12 +690,23 @@ where
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, request: Request<B>) -> Self::Future {
|
||||
fn call(&mut self, mut request: Request<B>) -> Self::Future {
|
||||
if self.status == StatusCode::METHOD_NOT_ALLOWED {
|
||||
// we're inside a route but there was no method that matched
|
||||
// so record that so we can override the status if no other
|
||||
// routes match
|
||||
request.extensions_mut().insert(NoMethodMatch);
|
||||
}
|
||||
|
||||
if self.status == StatusCode::NOT_FOUND
|
||||
&& request.extensions().get::<NoMethodMatch>().is_some()
|
||||
{
|
||||
self.status = StatusCode::METHOD_NOT_ALLOWED;
|
||||
}
|
||||
|
||||
let mut res = Response::new(crate::body::empty());
|
||||
|
||||
if request.extensions().get::<OrDepth>().is_some() {
|
||||
res.extensions_mut().insert(FromEmptyRouter { request });
|
||||
}
|
||||
res.extensions_mut().insert(FromEmptyRouter { request });
|
||||
|
||||
*res.status_mut() = self.status;
|
||||
EmptyRouterFuture {
|
||||
@@ -703,6 +715,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct NoMethodMatch;
|
||||
|
||||
/// Response extension used by [`EmptyRouter`] to send the request back to [`Or`] so
|
||||
/// the other service can be called.
|
||||
///
|
||||
@@ -713,39 +728,6 @@ struct FromEmptyRouter<B> {
|
||||
request: Request<B>,
|
||||
}
|
||||
|
||||
/// We need to track whether we're inside an `Or` or not, and only if we then
|
||||
/// should we save the request into the response extensions.
|
||||
///
|
||||
/// This is to work around https://github.com/hyperium/hyper/issues/2621.
|
||||
///
|
||||
/// Since ours can be nested we have to track the depth to know when we're
|
||||
/// leaving the top most `Or`.
|
||||
///
|
||||
/// Hopefully when https://github.com/hyperium/hyper/issues/2621 is resolved we
|
||||
/// can remove this nasty hack.
|
||||
#[derive(Debug)]
|
||||
struct OrDepth(usize);
|
||||
|
||||
impl OrDepth {
|
||||
fn new() -> Self {
|
||||
Self(1)
|
||||
}
|
||||
|
||||
fn increment(&mut self) {
|
||||
self.0 += 1;
|
||||
}
|
||||
|
||||
fn decrement(&mut self) {
|
||||
self.0 -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<usize> for &mut OrDepth {
|
||||
fn eq(&self, other: &usize) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PathPattern(Arc<Inner>);
|
||||
|
||||
@@ -945,6 +927,7 @@ impl<S, F, B> Service<Request<B>> for Nested<S, F>
|
||||
where
|
||||
S: Service<Request<B>, Response = Response<BoxBody>> + Clone,
|
||||
F: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error> + Clone,
|
||||
B: Send + Sync + 'static,
|
||||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = S::Error;
|
||||
@@ -966,7 +949,7 @@ where
|
||||
|
||||
insert_url_params(&mut req, captures);
|
||||
let fut = self.svc.clone().oneshot(req);
|
||||
RouteFuture::a(fut)
|
||||
RouteFuture::a(fut, self.fallback.clone())
|
||||
} else {
|
||||
let fut = self.fallback.clone().oneshot(req);
|
||||
RouteFuture::b(fut)
|
||||
|
||||
Reference in New Issue
Block a user