From 113a15a7132c02a07b32c9e0c3da721bc381a56a Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Tue, 22 Feb 2022 16:14:18 +0100 Subject: [PATCH] Document sharing state between handler and middleware (#783) * Fix heading levels * Document passing state from middleware to handlers --- axum/CHANGELOG.md | 2 ++ axum/src/docs/middleware.md | 64 +++++++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index 9b7316fb..1773f131 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **added:** `middleware::from_fn` for creating middleware from async functions. This previously lived in axum-extra but has been moved to axum ([#719]) +- **added:** Document sharing state between handler and middleware (#783]) - **breaking:** `sse::Event` now accepts types implementing `AsRef` instead of `Into` as field values. - **breaking:** `sse::Event` now panics if a setter method is called twice instead of silently @@ -65,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#733]: https://github.com/tokio-rs/axum/pull/733 [#734]: https://github.com/tokio-rs/axum/pull/734 [#755]: https://github.com/tokio-rs/axum/pull/755 +[#783]: https://github.com/tokio-rs/axum/pull/783 # 0.4.4 (13. January, 2022) diff --git a/axum/src/docs/middleware.md b/axum/src/docs/middleware.md index b7826e43..86116953 100644 --- a/axum/src/docs/middleware.md +++ b/axum/src/docs/middleware.md @@ -191,7 +191,7 @@ You should use these when - You want to perform a small ad hoc operation, such as adding a header. - You don't intend to publish your middleware as a crate for others to use. -# `tower::Service` and `Pin>` +## `tower::Service` and `Pin>` For maximum control (and a more low level API) you can write you own middleware by implementing [`tower::Service`]: @@ -254,7 +254,7 @@ where } ``` -# `tower::Service` and custom futures +## `tower::Service` and custom futures If you're comfortable implementing your own futures (or want to learn it) and need as much control as possible then using `tower::Service` without boxed @@ -374,6 +374,64 @@ Also note that handlers created from async functions don't care about backpressure and are always ready. So if you're not using any Tower middleware you don't have to worry about any of this. +# Sharing state between handlers and middleware + +State can be shared between middleware and handlers using [request extensions]: + +```rust +use axum::{ + Router, + http::{Request, StatusCode}, + routing::get, + response::IntoResponse, + middleware::{self, Next}, + extract::Extension, +}; + +#[derive(Clone)] +struct CurrentUser { /* ... */ } + +async fn auth(mut req: Request, next: Next) -> impl IntoResponse { + let auth_header = req.headers() + .get(http::header::AUTHORIZATION) + .and_then(|header| header.to_str().ok()); + + let auth_header = if let Some(auth_header) = auth_header { + auth_header + } else { + return Err(StatusCode::UNAUTHORIZED); + }; + + if let Some(current_user) = authorize_current_user(auth_header).await { + req.extensions_mut().insert(current_user); + Ok(next.run(req).await) + } else { + Err(StatusCode::UNAUTHORIZED) + } +} + +async fn authorize_current_user(auth_token: &str) -> Option { + // ... + # unimplemented!() +} + +async fn handler( + // extract the current user, set by the middleware + Extension(current_user): Extension, +) { + // ... +} + +let app = Router::new() + .route("/", get(handler)) + .route_layer(middleware::from_fn(auth)); +# let app: Router = app; +``` + +[Response extensions] can also be used but note that request extensions are not +automatically moved to response extensions. You need to manually do that for the +extensions you need. + [`tower`]: https://crates.io/crates/tower [`tower-http`]: https://crates.io/crates/tower-http [tower-guides]: https://github.com/tower-rs/tower/tree/master/guides @@ -390,3 +448,5 @@ middleware you don't have to worry about any of this. [`MethodRouter::layer`]: crate::routing::MethodRouter::layer [`Router::route_layer`]: crate::routing::Router::route_layer [`MethodRouter::route_layer`]: crate::routing::MethodRouter::route_layer +[request extensions]: https://docs.rs/http/latest/http/request/struct.Request.html#method.extensions +[Response extensions]: https://docs.rs/http/latest/http/response/struct.Response.html#method.extensions