-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Support fallbacks for nested routers #1161
Changes from all commits
2ddd0a2
23721f2
2c54bb5
014067e
79daaf0
276a950
b220778
fb5219f
657a089
bbf1852
7ed35d2
ca95dd6
581b0d9
13011d5
ef34ef5
4014970
a0ac60e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,4 +1,4 @@ | ||||||||||||||||||
Nest a group of routes (or a [`Service`]) at some path. | ||||||||||||||||||
Nest a router at some path. | ||||||||||||||||||
|
||||||||||||||||||
This allows you to break your application into smaller pieces and compose | ||||||||||||||||||
them together. | ||||||||||||||||||
|
@@ -64,36 +64,6 @@ let app = Router::new().nest("/:version/api", users_api); | |||||||||||||||||
# }; | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
# Nesting services | ||||||||||||||||||
|
||||||||||||||||||
`nest` also accepts any [`Service`]. This can for example be used with | ||||||||||||||||||
[`tower_http::services::ServeDir`] to serve static files from a directory: | ||||||||||||||||||
|
||||||||||||||||||
```rust | ||||||||||||||||||
use axum::{ | ||||||||||||||||||
Router, | ||||||||||||||||||
routing::get_service, | ||||||||||||||||||
http::StatusCode, | ||||||||||||||||||
error_handling::HandleErrorLayer, | ||||||||||||||||||
}; | ||||||||||||||||||
use std::{io, convert::Infallible}; | ||||||||||||||||||
use tower_http::services::ServeDir; | ||||||||||||||||||
|
||||||||||||||||||
// Serves files inside the `public` directory at `GET /public/*` | ||||||||||||||||||
let serve_dir_service = get_service(ServeDir::new("public")) | ||||||||||||||||||
.handle_error(|error: io::Error| async move { | ||||||||||||||||||
( | ||||||||||||||||||
StatusCode::INTERNAL_SERVER_ERROR, | ||||||||||||||||||
format!("Unhandled internal error: {}", error), | ||||||||||||||||||
) | ||||||||||||||||||
}); | ||||||||||||||||||
|
||||||||||||||||||
let app = Router::new().nest("/public", serve_dir_service); | ||||||||||||||||||
# async { | ||||||||||||||||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); | ||||||||||||||||||
# }; | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
# Differences to wildcard routes | ||||||||||||||||||
|
||||||||||||||||||
Nested routes are similar to wildcard routes. The difference is that | ||||||||||||||||||
|
@@ -103,18 +73,79 @@ the prefix stripped: | |||||||||||||||||
```rust | ||||||||||||||||||
use axum::{routing::get, http::Uri, Router}; | ||||||||||||||||||
|
||||||||||||||||||
let nested_router = Router::new() | ||||||||||||||||||
.route("/", get(|uri: Uri| async { | ||||||||||||||||||
// `uri` will _not_ contain `/bar` | ||||||||||||||||||
})); | ||||||||||||||||||
|
||||||||||||||||||
let app = Router::new() | ||||||||||||||||||
.route("/foo/*rest", get(|uri: Uri| async { | ||||||||||||||||||
// `uri` will contain `/foo` | ||||||||||||||||||
})) | ||||||||||||||||||
.nest("/bar", get(|uri: Uri| async { | ||||||||||||||||||
// `uri` will _not_ contain `/bar` | ||||||||||||||||||
})); | ||||||||||||||||||
.nest("/bar", nested_router); | ||||||||||||||||||
# async { | ||||||||||||||||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); | ||||||||||||||||||
# }; | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
# Differences between `nest` and `nest_service` | ||||||||||||||||||
|
||||||||||||||||||
When [fallbacks] are called differs between `nest` and `nested_service`. Routers | ||||||||||||||||||
nested with `nest` will delegate to the outer router's fallback if they don't | ||||||||||||||||||
have a matching route, whereas `nested_service` will not. | ||||||||||||||||||
Comment on lines
+93
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
```rust | ||||||||||||||||||
use axum::{ | ||||||||||||||||||
Router, | ||||||||||||||||||
routing::get, | ||||||||||||||||||
handler::Handler, | ||||||||||||||||||
}; | ||||||||||||||||||
|
||||||||||||||||||
let nested_router = Router::new().route("/users", get(|| async {})); | ||||||||||||||||||
|
||||||||||||||||||
let nested_service = Router::new().route("/app.js", get(|| async {})); | ||||||||||||||||||
|
||||||||||||||||||
let app = Router::new() | ||||||||||||||||||
.nest("/api", nested_router) | ||||||||||||||||||
.nest_service("/assets", nested_service) | ||||||||||||||||||
// the fallback is not called for request starting with `/assets` but will be | ||||||||||||||||||
// called for requests starting with `/api` if `nested_router` doesn't have | ||||||||||||||||||
// a matching route | ||||||||||||||||||
.fallback(fallback.into_service()); | ||||||||||||||||||
Comment on lines
+111
to
+114
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be correct to add this? It's the semantics I would intuitively expect.
Suggested change
|
||||||||||||||||||
# let _: Router = app; | ||||||||||||||||||
|
||||||||||||||||||
async fn fallback() {} | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
You can still add fallbacks explicitly to the inner router: | ||||||||||||||||||
|
||||||||||||||||||
```rust | ||||||||||||||||||
use axum::{ | ||||||||||||||||||
Router, | ||||||||||||||||||
routing::get, | ||||||||||||||||||
handler::Handler, | ||||||||||||||||||
}; | ||||||||||||||||||
|
||||||||||||||||||
let nested_service = Router::new() | ||||||||||||||||||
.route("/app.js", get(|| async {})) | ||||||||||||||||||
.fallback(nested_service_fallback.into_service()); | ||||||||||||||||||
|
||||||||||||||||||
let app = Router::new() | ||||||||||||||||||
.nest_service("/assets", nested_service) | ||||||||||||||||||
.fallback(outer_router_fallback.into_service()); | ||||||||||||||||||
# let _: Router = app; | ||||||||||||||||||
|
||||||||||||||||||
// this handler is used for `nested_service` | ||||||||||||||||||
async fn nested_service_fallback() { | ||||||||||||||||||
// .. | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// this handler is used for the outer router | ||||||||||||||||||
async fn outer_router_fallback() { | ||||||||||||||||||
// ... | ||||||||||||||||||
} | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
# Panics | ||||||||||||||||||
|
||||||||||||||||||
- If the route overlaps with another route. See [`Router::route`] | ||||||||||||||||||
|
@@ -125,3 +156,4 @@ for more details. | |||||||||||||||||
`Router` only allows a single fallback. | ||||||||||||||||||
|
||||||||||||||||||
[`OriginalUri`]: crate::extract::OriginalUri | ||||||||||||||||||
[fallbacks]: Router::fallback |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
Nest a [`Service`] at some path. | ||
|
||
`nest_service` behaves in the same way as `nest` in terms of | ||
|
||
- [How the URI changes](#how-the-uri-changes) | ||
- [Captures from outer routes](#captures-from-outer-routes) | ||
- [Differences to wildcard routes](#differences-to-wildcard-routes) | ||
|
||
But differs with regards to [fallbacks]. See ["Differences between `nest` and | ||
`nest_service`"](#differences-between-nest-and-nest_service) for more details. | ||
|
||
# Example | ||
|
||
`nest_service` can for example be used with [`tower_http::services::ServeDir`] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know where the link definition for this is, but we should make sure to link to a specific version of tower-http, given the example below is going to stop working with the next breaking release (due to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My intention is so somewhat coordinate the next release of axum and tower-http but yeah will need to be updated. |
||
to serve static files from a directory: | ||
|
||
```rust | ||
use axum::{ | ||
Router, | ||
routing::get_service, | ||
http::StatusCode, | ||
error_handling::HandleErrorLayer, | ||
}; | ||
use std::{io, convert::Infallible}; | ||
use tower_http::services::ServeDir; | ||
|
||
// Serves files inside the `public` directory at `GET /assets/*` | ||
let serve_dir_service = get_service(ServeDir::new("public")) | ||
.handle_error(|error: io::Error| async move { | ||
( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
format!("Unhandled internal error: {}", error), | ||
) | ||
}); | ||
|
||
let app = Router::new().nest_service("/assets", serve_dir_service); | ||
# let _: Router = app; | ||
``` | ||
|
||
[fallbacks]: Router::fallback |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in #1086 (comment), these docs need to be updated to describe how nested routers handle fallbacks, as well as the following example (which uses
nest_service
to allow the nested router to have a fallback).