mirror of
https://github.com/tokio-rs/axum.git
synced 2024-12-29 15:49:16 +01:00
Expand the docs for Router::with_state
(#1580)
This commit is contained in:
parent
8d62697c72
commit
eb2e933054
3 changed files with 238 additions and 59 deletions
|
@ -5,10 +5,9 @@
|
||||||
///
|
///
|
||||||
/// See [`State`] for more details on how library authors should use this trait.
|
/// See [`State`] for more details on how library authors should use this trait.
|
||||||
///
|
///
|
||||||
/// This trait can be derived using `#[derive(axum_macros::FromRef)]`.
|
/// This trait can be derived using `#[derive(FromRef)]`.
|
||||||
///
|
///
|
||||||
/// [`State`]: https://docs.rs/axum/0.6/axum/extract/struct.State.html
|
/// [`State`]: https://docs.rs/axum/0.6/axum/extract/struct.State.html
|
||||||
/// [`#[derive(axum_macros::FromRef)]`]: https://docs.rs/axum-macros/latest/axum_macros/derive.FromRef.html
|
|
||||||
// NOTE: This trait is defined in axum-core, even though it is mainly used with `State` which is
|
// NOTE: This trait is defined in axum-core, even though it is mainly used with `State` which is
|
||||||
// defined in axum. That allows crate authors to use it when implementing extractors.
|
// defined in axum. That allows crate authors to use it when implementing extractors.
|
||||||
pub trait FromRef<T> {
|
pub trait FromRef<T> {
|
||||||
|
|
236
axum/src/docs/routing/with_state.md
Normal file
236
axum/src/docs/routing/with_state.md
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
Provide the state for the router.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use axum::{Router, routing::get, extract::State};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {}
|
||||||
|
|
||||||
|
let routes = Router::new()
|
||||||
|
.route("/", get(|State(state): State<AppState>| async {
|
||||||
|
// use state
|
||||||
|
}))
|
||||||
|
.with_state(AppState {});
|
||||||
|
|
||||||
|
# async {
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(routes.into_make_service())
|
||||||
|
.await;
|
||||||
|
# };
|
||||||
|
```
|
||||||
|
|
||||||
|
# Returning routers with states from functions
|
||||||
|
|
||||||
|
When returning `Router`s from functions it is generally recommend not set the
|
||||||
|
state directly:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use axum::{Router, routing::get, extract::State};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {}
|
||||||
|
|
||||||
|
// Don't call `Router::with_state` here
|
||||||
|
fn routes() -> Router<AppState> {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(|_: State<AppState>| async {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instead do it before you run the server
|
||||||
|
let routes = routes().with_state(AppState {});
|
||||||
|
|
||||||
|
# async {
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(routes.into_make_service())
|
||||||
|
.await;
|
||||||
|
# };
|
||||||
|
```
|
||||||
|
|
||||||
|
If you do need to provide the state, and you're _not_ nesting/merging the router
|
||||||
|
into another router, then return `Router` without any type parameters:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# use axum::{Router, routing::get, extract::State};
|
||||||
|
# #[derive(Clone)]
|
||||||
|
# struct AppState {}
|
||||||
|
#
|
||||||
|
// Don't return `Router<AppState>`
|
||||||
|
fn routes(state: AppState) -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(|_: State<AppState>| async {}))
|
||||||
|
.with_state(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
let routes = routes(AppState {});
|
||||||
|
|
||||||
|
# async {
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(routes.into_make_service())
|
||||||
|
.await;
|
||||||
|
# };
|
||||||
|
```
|
||||||
|
|
||||||
|
This is because we can only call `Router::into_make_service` on `Router<()>`,
|
||||||
|
not `Router<AppState>`. See below for more details about why that is.
|
||||||
|
|
||||||
|
Note that the state defaults to `()` so `Router` and `Router<()>` is the same.
|
||||||
|
|
||||||
|
If you are nesting/merging the router it is recommended to use a generic state
|
||||||
|
type on the resulting router:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# use axum::{Router, routing::get, extract::State};
|
||||||
|
# #[derive(Clone)]
|
||||||
|
# struct AppState {}
|
||||||
|
#
|
||||||
|
fn routes<S>(state: AppState) -> Router<S> {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(|_: State<AppState>| async {}))
|
||||||
|
.with_state(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
let routes = Router::new().nest("/api", routes(AppState {}));
|
||||||
|
|
||||||
|
# async {
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(routes.into_make_service())
|
||||||
|
.await;
|
||||||
|
# };
|
||||||
|
```
|
||||||
|
|
||||||
|
# What `S` in `Router<S>` means
|
||||||
|
|
||||||
|
`Router<S>` means a router that is _missing_ a state of type `S` to be able to
|
||||||
|
handle requests. It does _not_ mean a `Router` that _has_ a state of type `S`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# use axum::{Router, routing::get, extract::State};
|
||||||
|
# #[derive(Clone)]
|
||||||
|
# struct AppState {}
|
||||||
|
#
|
||||||
|
// A router that _needs_ an `AppState` to handle requests
|
||||||
|
let router: Router<AppState> = Router::new()
|
||||||
|
.route("/", get(|_: State<AppState>| async {}));
|
||||||
|
|
||||||
|
// Once we call `Router::with_state` the router isn't missing
|
||||||
|
// the state anymore, because we just provided it
|
||||||
|
//
|
||||||
|
// Therefore the router type becomes `Router<()>`, i.e a router
|
||||||
|
// that is not missing any state
|
||||||
|
let router: Router<()> = router.with_state(AppState {});
|
||||||
|
|
||||||
|
// Only `Router<()>` has the `into_make_service` method.
|
||||||
|
//
|
||||||
|
// You cannot call `into_make_service` on a `Router<AppState>`
|
||||||
|
// because it is still missing an `AppState`.
|
||||||
|
# async {
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(router.into_make_service())
|
||||||
|
.await;
|
||||||
|
# };
|
||||||
|
```
|
||||||
|
|
||||||
|
Perhaps a little counter intuitively, `Router::with_state` doesn't always return a
|
||||||
|
`Router<()>`. Instead you get to pick what the new missing state type is:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# use axum::{Router, routing::get, extract::State};
|
||||||
|
# #[derive(Clone)]
|
||||||
|
# struct AppState {}
|
||||||
|
#
|
||||||
|
let router: Router<AppState> = Router::new()
|
||||||
|
.route("/", get(|_: State<AppState>| async {}));
|
||||||
|
|
||||||
|
// When we call `with_state` we're able to pick what the next missing state type is.
|
||||||
|
// Here we pick `String`.
|
||||||
|
let string_router: Router<String> = router.with_state(AppState {});
|
||||||
|
|
||||||
|
// That allows us to add new routes that uses `String` as the state type
|
||||||
|
let string_router = string_router
|
||||||
|
.route("/needs-string", get(|_: State<String>| async {}));
|
||||||
|
|
||||||
|
// Provide the `String` and choose `()` as the new missing state.
|
||||||
|
let final_router: Router<()> = string_router.with_state("foo".to_owned());
|
||||||
|
|
||||||
|
// Since we have a `Router<()>` we can run it.
|
||||||
|
# async {
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(final_router.into_make_service())
|
||||||
|
.await;
|
||||||
|
# };
|
||||||
|
```
|
||||||
|
|
||||||
|
This why this returning `Router<AppState>` after calling `with_state` doesn't
|
||||||
|
work:
|
||||||
|
|
||||||
|
```rust,compile_fail
|
||||||
|
# use axum::{Router, routing::get, extract::State};
|
||||||
|
# #[derive(Clone)]
|
||||||
|
# struct AppState {}
|
||||||
|
#
|
||||||
|
// This wont work because we're returning a `Router<AppState>`
|
||||||
|
// i.e. we're saying we're still missing an `AppState`
|
||||||
|
fn routes(state: AppState) -> Router<AppState> {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(|_: State<AppState>| async {}))
|
||||||
|
.with_state(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = routes(AppState {});
|
||||||
|
|
||||||
|
// We can only call `Router::into_make_service` on a `Router<()>`
|
||||||
|
// but `app` is a `Router<AppState>`
|
||||||
|
# async {
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.await;
|
||||||
|
# };
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead return `Router<()>` since we have provided all the state needed:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# use axum::{Router, routing::get, extract::State};
|
||||||
|
# #[derive(Clone)]
|
||||||
|
# struct AppState {}
|
||||||
|
#
|
||||||
|
// We've provided all the state necessary so return `Router<()>`
|
||||||
|
fn routes(state: AppState) -> Router<()> {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(|_: State<AppState>| async {}))
|
||||||
|
.with_state(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = routes(AppState {});
|
||||||
|
|
||||||
|
// We can now call `Router::into_make_service`
|
||||||
|
# async {
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.await;
|
||||||
|
# };
|
||||||
|
```
|
||||||
|
|
||||||
|
# A note about performance
|
||||||
|
|
||||||
|
If you need a `Router` that implements `Service` but you don't need any state (perhaps
|
||||||
|
you're making a library that uses axum internally) then it is recommended to call this
|
||||||
|
method before you start serving requests:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use axum::{Router, routing::get};
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(|| async { /* ... */ }))
|
||||||
|
// even though we don't need any state, call `with_state(())` anyway
|
||||||
|
.with_state(());
|
||||||
|
# let _: Router = app;
|
||||||
|
```
|
||||||
|
|
||||||
|
This is not required but it gives axum a chance to update some internals in the router
|
||||||
|
which may impact performance and reduce allocations.
|
||||||
|
|
||||||
|
Note that [`Router::into_make_service`] and [`Router::into_make_service_with_connect_info`]
|
||||||
|
do this automatically.
|
|
@ -103,8 +103,6 @@ where
|
||||||
pub(crate) const NEST_TAIL_PARAM: &str = "__private__axum_nest_tail_param";
|
pub(crate) const NEST_TAIL_PARAM: &str = "__private__axum_nest_tail_param";
|
||||||
pub(crate) const NEST_TAIL_PARAM_CAPTURE: &str = "/*__private__axum_nest_tail_param";
|
pub(crate) const NEST_TAIL_PARAM_CAPTURE: &str = "/*__private__axum_nest_tail_param";
|
||||||
|
|
||||||
impl<B> Router<(), B> where B: HttpBody + Send + 'static {}
|
|
||||||
|
|
||||||
impl<S, B> Router<S, B>
|
impl<S, B> Router<S, B>
|
||||||
where
|
where
|
||||||
B: HttpBody + Send + 'static,
|
B: HttpBody + Send + 'static,
|
||||||
|
@ -396,61 +394,7 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide the state for the router.
|
#[doc = include_str!("../docs/routing/with_state.md")]
|
||||||
///
|
|
||||||
/// This method returns a router with a different state type. This can be used to nest or merge
|
|
||||||
/// routers with different state types. See [`Router::nest`] and [`Router::merge`] for more
|
|
||||||
/// details.
|
|
||||||
///
|
|
||||||
/// # Implementing `Service`
|
|
||||||
///
|
|
||||||
/// This can also be used to get a `Router` that implements [`Service`], since it only does so
|
|
||||||
/// when the state is `()`:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use axum::{
|
|
||||||
/// Router,
|
|
||||||
/// body::Body,
|
|
||||||
/// http::Request,
|
|
||||||
/// };
|
|
||||||
/// use tower::{Service, ServiceExt};
|
|
||||||
///
|
|
||||||
/// #[derive(Clone)]
|
|
||||||
/// struct AppState {}
|
|
||||||
///
|
|
||||||
/// // this router doesn't implement `Service` because its state isn't `()`
|
|
||||||
/// let router: Router<AppState> = Router::new();
|
|
||||||
///
|
|
||||||
/// // by providing the state and setting the new state to `()`...
|
|
||||||
/// let router_service: Router<()> = router.with_state(AppState {});
|
|
||||||
///
|
|
||||||
/// // ...makes it implement `Service`
|
|
||||||
/// # async {
|
|
||||||
/// router_service.oneshot(Request::new(Body::empty())).await;
|
|
||||||
/// # };
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # A note about performance
|
|
||||||
///
|
|
||||||
/// If you need a `Router` that implements `Service` but you don't need any state (perhaps
|
|
||||||
/// you're making a library that uses axum internally) then it is recommended to call this
|
|
||||||
/// method before you start serving requests:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use axum::{Router, routing::get};
|
|
||||||
///
|
|
||||||
/// let app = Router::new()
|
|
||||||
/// .route("/", get(|| async { /* ... */ }))
|
|
||||||
/// // even though we don't need any state, call `with_state(())` anyway
|
|
||||||
/// .with_state(());
|
|
||||||
/// # let _: Router = app;
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// This is not required but it gives axum a chance to update some internals in the router
|
|
||||||
/// which may impact performance and reduce allocations.
|
|
||||||
///
|
|
||||||
/// Note that [`Router::into_make_service`] and [`Router::into_make_service_with_connect_info`]
|
|
||||||
/// do this automatically.
|
|
||||||
pub fn with_state<S2>(self, state: S) -> Router<S2, B> {
|
pub fn with_state<S2>(self, state: S) -> Router<S2, B> {
|
||||||
let routes = self
|
let routes = self
|
||||||
.routes
|
.routes
|
||||||
|
|
Loading…
Reference in a new issue