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
Middleware that return early break Router::or
#380
Comments
#363 won't fix this problem? |
Don't you think that current documentation is misleading then? |
I don't believe so.
You're right, we probably shouldn't use the particular example we do since it wont work. |
I looked at #363, and (if I understood correctly), now it will
If so, I think it is possible to somehow store IDs of routes in Router itself, and before call Router's inners (when handling new request), check, whether Router have current ID or not, so we could skip all Routers but the one we need. Do you think it is viable solution for axum? |
I'm not sure. Made a note to look into it. You're welcome to test it if you want. |
Router::or
I've tweaked the title a bit to make it more clear what the issue is. I think this is something we should attempt to fix for 0.3. |
the same problem is encountered in this code. let no_auth_routes = Router::new()
.nest(
"/public",
service::get(ServeDir::new("./public")).handle_error(|_| {
(
StatusCode::INTERNAL_SERVER_ERROR,
"Unhandled internal error",
)
}),
)
.route("/ping", get(get_ping))
.route("/version", get(get_version));
let auth_routes = Router::new()
.route("/auth", get(get_auth))
// the rest of authorized routes
.layer(RequireAuthorizationLayer::bearer("********"));
let app = no_auth_routes.or(auth_routes); |
With #404 and #402 all routes now have the same types and thus we don't need to nest them but can instead store them all in a map. This simplifies the routing quite a bit and is faster as well. High level changes: - Routes are now stored in a `HashMap<RouteId, Route<B>>`. - `Router::or` is renamed to `Router::merge` because thats what it does now. It copies all routes from one router to another. This also means overlapping routes will cause a panic which is nice win. - `Router::merge` now only accepts `Router`s so added `Router::fallback` for adding a global 404 handler. - The `Or` service has been removed. - `Router::layer` now only adds layers to the routes you actually have meaning middleware runs _after_ routing. I believe that addresses #380 but will test that on another branch.
This was fixed in #408 |
Turns out I was too quick on this one. Fixing this requires doing routing first, before running any middleware. Because if we run the middleware first they might return early and hijack something that would otherwise be a 404. However by running middleware after routing we break other middleware like logging which expect to be called even for 404s... So regardless which path we break something. I think the best compromise is:
I feel this is the best approach for now. Maybe we'll come up with a general solution in the future. See #422 for more details. |
While thinking about #419 I realized that `main` had a bug where middleware wouldn't be called if no route matched the incoming request. `Router` would just directly return a 404 without calling any middleware. This fixes by making the fallback default to a service that always returns 404 and always calling that if no route matches. Layers applied to the router is then also applied to the fallback. Unfortunately this breaks #380 but I don't currently see a way to support both. Auth middleware need to run _after_ routing because you don't care about auth for unknown paths, but logging middleware need to run _before_ routing because they do care about seeing requests for unknown paths so they can log them... Part of #419
While thinking about #419 I realized that `main` had a bug where middleware wouldn't be called if no route matched the incoming request. `Router` would just directly return a 404 without calling any middleware. This fixes by making the fallback default to a service that always returns 404 and always calling that if no route matches. Layers applied to the router is then also applied to the fallback. Unfortunately this breaks #380 but I don't currently see a way to support both. Auth middleware need to run _after_ routing because you don't care about auth for unknown paths, but logging middleware need to run _before_ routing because they do care about seeing requests for unknown paths so they can log them... Part of #419
I think renaming But I think it would be cool to have something like
This will allow to create tree-like structure of Routers, where layers can be applied only to certain routes, which do not share common prefix. |
How is that different from |
Layers from both routers will mix again. What I think is something like let router_a = Router::new().route("/metrics", ...).route("/health", ...);
let router_b = Router::new().route("/shutdown", ...).route("/kill", ...).layer(SomeAuthLayer {});
let router = Route::new().nest("/manage", router_a).nest("/manage", router_b); Ofc there is always option to apply layers to handlers directly, even though it is verbose. |
What do mean by this? Layers only apply to routes added to the router at the time the layer is added. |
Seems I got it wrong. If replace use axum::{
handler::Handler,
response::IntoResponse,
routing::{get, post},
Router,
};
use hyper::StatusCode;
use std::net::SocketAddr;
use tower_http::auth::RequireAuthorizationLayer;
async fn handler_404() -> impl IntoResponse {
StatusCode::NOT_FOUND
}
#[tokio::main]
async fn main() {
let foo = Router::new()
.route("/", get(|| async {}))
.route("/foo", post(|| async {}));
let bar = Router::new()
.route("/requires-auth", get(|| async {}))
.layer(RequireAuthorizationLayer::basic("a", "a"));
let app = bar.merge(foo).fallback(handler_404.into_service()); //NOTE: bar is first, not foo
let addr = "0.0.0.0:8080".parse::<SocketAddr>().unwrap();
axum::Server::try_bind(&addr)
.unwrap()
.serve(app.into_make_service())
.await;
} Thanks! |
A proper fix for this was released in 0.3.2: |
Bug Report
Version
Platform
Linux fdr12.tcsbank.ru 5.14.9-200.fc34.x86_64 #1 SMP Thu Sep 30 11:55:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
Description
When I try to split app into multiple Routers (like in docs https://docs.rs/axum/0.2.8/axum/#to-groups-of-routes), layers from "leaf" Routers affect other "leaf" Routers.
I tried this code:
I expected to see this happen: when I do
curl localhost:8080/
, I get200 OK
Instead, this happened:
401 Unauthorized
.Basically now all routes require auth from
bar
"leaf" Router.If I apply auth layer to handler directly, everything works, but this mean that I cannot apply layer to group of routes, only to all of them at once.
I think that layers should apply only to current Router, at least this is how I understand current documentation.
The text was updated successfully, but these errors were encountered: