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

Router::fallback does not get called for routes that are not added for different methods #1005

Closed
emirhantasdeviren opened this issue May 6, 2022 · 2 comments

Comments

@emirhantasdeviren
Copy link

Bug Report

Version

axum v0.5.4

Platform

Arch Linux
5.17.5-arch1-1

Description

Sorry for that complex title. Here is my code,

const HOST: [u8; 4] = [127, 0, 0, 1];
const PORT: u16 = 8080;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route(
            "/api/validateSession",
            get(controllers::api::validate_session),
        )
        .route("/login", post(controllers::user::sign_in))
        .fallback(
            get_service(ServeDir::new("public").fallback(ServeFile::new("public/index.html")))
                .handle_error(|_e: std::io::Error| async move {
                    (
                        axum::http::StatusCode::INTERNAL_SERVER_ERROR,
                        "You know what happened.",
                    )
                }),
        );

    let addr = SocketAddr::from((HOST, PORT));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

So my goal is to handle specific routes and leave other routes to be served by public folder. If the file does not exist in public, public/index.html will be served. Here the issue is I added /login router with only POST method.

My expectation is that GET /login route will call fallback since I only added POST /login.

Instead, I get 405 Method Not Allowed. If I delete the line route("/login", post(controllers::user::sign_in)), GET /login renders public/index.html.

@jplatte
Copy link
Member

jplatte commented May 6, 2022

There is some magic that makes the method router work a little bit as if it was part of the path router, but they are really separate things. This really seems like it's working as intended to me. I think this would be the best solution:

let static_files =
    get_service(ServeDir::new("public").fallback(ServeFile::new("public/index.html")))
        .handle_error(|_e: std::io::Error| async move {
            (
                axum::http::StatusCode::INTERNAL_SERVER_ERROR,
                "You know what happened.",
            )
        });

let app = Router::new()
    .route(
        "/api/validateSession",
        get(controllers::api::validate_session),
    )
    .route("/login", post(controllers::user::sign_in).merge(static_files.clone()))
    .fallback(static_files);

@davidpdrsn
Copy link
Member

That's intentional. The design we chose for axum means that a route for /login exists it just doesn't support POST, thus 405 is appropriate.

It's a trade off and making that go to the fallback has other downsides (see issues for rocket never returning 405)

If you want to handle 405 in a custom way you have to write a middleware to catch 405s and return a different response. Using axum::middleware::from_fn is the easiest way to do that.

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

No branches or pull requests

3 participants