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

Consider removing support for trailing slash redirects #1118

Closed
davidpdrsn opened this issue Jun 25, 2022 · 18 comments · Fixed by #1119
Closed

Consider removing support for trailing slash redirects #1118

davidpdrsn opened this issue Jun 25, 2022 · 18 comments · Fixed by #1119
Labels
Milestone

Comments

@davidpdrsn
Copy link
Member

If you define a route like .route("/foo", _) and someone calls /foo/ axum will issue a redirect response to /foo. Same goes if you define /foo/ and someone calls /foo. If both routes are defined (i.e. /foo/ and /foo) axum will not redirect between them.

We've had this feature since 0.3.0 (since moving to matchit) but I've started to think that it was a mistake to add. I think its a surprising feature and not something most users would expect the default behavior to be. Its almost like adding a hidden route.

I think for axum 0.6 we should consider removing this feature and instead treat such requests as "not found" and send them to the fallback.

I don't think we should provide a configuration option to get the old behavior back. If users want both /foo and /foo/ they can add those routes themselves.

We might also consider providing a NormalizePath middleware that removes trailing slashes from incoming requests but I'd treat that as separate and a nice to have.

Thoughts @jplatte @programatik29 @ibraheemdev?

@davidpdrsn davidpdrsn added I-needs-decision Issues in need of decision. A-axum labels Jun 25, 2022
@davidpdrsn davidpdrsn added this to the 0.6 milestone Jun 25, 2022
@programatik29
Copy link
Contributor

Makes sense. It might be nice to have a configuration option to turn this behavior on though. I'm not sure if it should be removed entirely.

@jplatte
Copy link
Member

jplatte commented Jun 25, 2022

The middleware would be in tower-http, right? I think it would make sense to get that out before 0.6.0 in any case, just so there's a clear upgrade path.

Overall I agree this can be surprising, and it makes sense to remove it.

@davidpdrsn
Copy link
Member Author

The middleware would be in tower-http, right? I think it would make sense to get that out before 0.6.0 in any case, just so there's a clear upgrade path.

Yep makes sense to put in tower-http.

@kellytk
Copy link
Contributor

kellytk commented Jun 28, 2022

Will API remain to easily 301 redirect a trailing-slashed route to its proper unslashed equivalent? /foo/ -> /foo

@davidpdrsn
Copy link
Member Author

@kellytk yes. We are adding tower-rs/tower-http#275 to tower-http.

@jplatte
Copy link
Member

jplatte commented Jun 29, 2022

@davidpdrsn but that makes both return the same response, it doesn't mean one will redirect to the other.

@davidpdrsn
Copy link
Member Author

I mean that comes out to the same thing in the end.

@kellytk
Copy link
Contributor

kellytk commented Jun 29, 2022

Not strictly.

@jplatte
Copy link
Member

jplatte commented Jun 29, 2022

Yeah, there's a few differences, to name some of the disadvantages of serving the same thing on both:

  • If it's an HTML page, visiting it at both URIs in a browser will lead to duplicate history entries
  • If it's a public HTML site and you don't add a <link rel="canonical" …> to the <head>, your site might get penalized by search engines for hosting the same content at multiple URLs

@davidpdrsn
Copy link
Member Author

davidpdrsn commented Jun 29, 2022

That is what this whole discussion what about. Sounded like you were onboard with removing it.

Like I stated in my original post I don't think we should remove trailing slash redirects and then also add a config for bringing it back.

Its straight forward to add a little helper for:

use axum::{
    body::HttpBody,
    handler::Handler,
    http::Request,
    response::{Redirect, Response},
    routing::get,
    Router,
};
use std::{convert::Infallible, net::SocketAddr};
use tower::Service;

#[tokio::main]
async fn main() {
    let app = Router::new().route_with_tsr("/foo", get(|| async { "Hello, World!" }));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

trait RouterExt<B> {
    fn route_with_tsr<T>(self, path: &str, service: T) -> Self
    where
        T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
        T::Future: Send + 'static;
}

impl<B> RouterExt<B> for Router<B>
where
    B: HttpBody + Send + 'static,
{
    fn route_with_tsr<T>(self, path: &str, service: T) -> Self
    where
        T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
        T::Future: Send + 'static,
    {
        let path = path.to_owned();
        self
            .route(&path, service)
            .route(
                &format!("{}/", path),
                (move || async move { Redirect::permanent(&path) }).into_service(),
            )
    }
}

So imo nothing is lost by requiring users to do this.

@davidpdrsn
Copy link
Member Author

I'd be fine with adding that to RouterExt in axum-extra.

@jplatte
Copy link
Member

jplatte commented Jun 29, 2022

I was and I'm still onboard with removing the current behavior from axum. I just think it's important to highlight that the existing NormalizePath PR might not be helpful for everybody who is currently relying on it.

I'd be fine with adding that to RouterExt in axum-extra.

I think this is what we should do, with helpers for either w/ or w/o slash being canonical. It might also work in more situations, i.e. the ones that were causing crashes in axum <0.5.10?

@davidpdrsn
Copy link
Member Author

Cool! I'll add it to axum-extra.

@jplatte
Copy link
Member

jplatte commented Jun 29, 2022

I'm curious what names you'll come up with 😄

@Rapptz
Copy link
Contributor

Rapptz commented Jul 3, 2022

So imo nothing is lost by requiring users to do this.

Personally, the fact that removing this makes it hard to do this for external people should be a reason to maybe reconsider it. I don't really see what the harm is in supporting trailing slashes. Actix has a middleware for this purpose but making the same thing in Axum is not possible (see #1120). The proposed solution also just duplicates routing which doesn't seem ideal.

@davidpdrsn
Copy link
Member Author

davidpdrsn commented Jul 3, 2022

but making the same thing in Axum is not possible (see #1120).

That issue is open because we intend to fix it for 0.6. Trailing slash redirects isn't removed in a published version yet.

Also like discussed above the middleware doesn't have quite the same effect. That was my assumption at first but people pointed out that wasn't the case.

The proposed solution also just duplicates routing which doesn't seem ideal.

Why not? How is it different from what Axum used to implicitly do?

@Rapptz
Copy link
Contributor

Rapptz commented Jul 3, 2022

That issue is open because we intend to fix it for 0.6.

Right, I can sympathise and understand that.

Trailing slash redirects isn't removed in a published version yet.

Yup, but I came across this in a published version due to it not working in 0.5.10. It seems that it doesn't work if there's a nested route. e.g. /foo works but if you have a nest with /bar and routes /test and /test/a then /bar/test/ doesn't redirect to /bar/test.

I figured I'd try writing a middleware to fix it since I didn't mind doing it but found that it isn't quite possible which led me to this issue and the one you posted earlier, so I thought I'd just give my opinion.

Why not? How is it different from what Axum used to implicitly do?

If you want to do this for every route you have (for my project so far it's small since I only have 30 routes) then you've essentially doubled the amount of routes + handlers, just doesn't quite seem ideal.

@davidpdrsn
Copy link
Member Author

It seems that it doesn't work if there's a nested route. e.g. /foo works but if you have a nest with /bar and routes /test and /test/a then /bar/test/ doesn't redirect to /bar/test.

Hm that seems like a bug 🤔

If you want to do this for every route you have (for my project so far it's small since I only have 30 routes) then you've essentially doubled the amount of routes + handlers, just doesn't quite seem ideal.

I don't think hidden routes in the way axum used to it is the answer. I think this is a good argument for fixing #1120.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants