From 8e58294f8dff8fdb64c58e900ad09fc26470a756 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 22 Aug 2022 18:29:22 +0200 Subject: [PATCH 1/2] Add `ServiceExt` --- axum/CHANGELOG.md | 2 ++ axum/src/docs/middleware.md | 43 ++++++++++++++++++++++++++++++++++ axum/src/docs/routing/layer.md | 5 ++++ axum/src/ext_traits/mod.rs | 1 + axum/src/ext_traits/service.rs | 43 ++++++++++++++++++++++++++++++++++ axum/src/lib.rs | 3 +++ 6 files changed, 97 insertions(+) create mode 100644 axum/src/ext_traits/mod.rs create mode 100644 axum/src/ext_traits/service.rs diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index b1ce5a204d..ef6663205f 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -361,6 +361,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **fixed:** Annotate panicking functions with `#[track_caller]` so the error message points to where the user added the invalid route, rather than somewhere internally in axum ([#1248]) +- **added:** Add `ServiceExt` with methods for turning any `Service` into a + `MakeService` similarly to `Router::into_make_service` [#1077]: https://github.com/tokio-rs/axum/pull/1077 [#1086]: https://github.com/tokio-rs/axum/pull/1086 diff --git a/axum/src/docs/middleware.md b/axum/src/docs/middleware.md index 8d0e970c4f..1830bbd65b 100644 --- a/axum/src/docs/middleware.md +++ b/axum/src/docs/middleware.md @@ -8,6 +8,7 @@ - [Routing to services/middleware and backpressure](#routing-to-servicesmiddleware-and-backpressure) - [Accessing state in middleware](#accessing-state-in-middleware) - [Passing state from middleware to handlers](#passing-state-from-middleware-to-handlers) +- [Rewriting request URI in middleware](#rewriting-request-uri-in-middleware) # Intro @@ -557,6 +558,47 @@ let app = Router::new() automatically moved to response extensions. You need to manually do that for the extensions you need. +# Rewriting request URI in middleware + +Middleware added with [`Router::layer`] will run after routing. That means it +cannot be used to run middleware that rewrites the request URI. By the time the +middleware runs the routing is already done. + +The workaround is to wrap the middleware around the entire `Router` (this works +because `Router` implements [`Service`]): + +```rust +use tower::Layer; +use axum::{ + Router, + ServiceExt, // for `into_make_service` + response::Response, + middleware::Next, + http::Request, +}; + +async fn rewrite_request_uri(req: Request, next: Next) -> Response { + // ... + # next.run(req).await +} + +// this can be any `tower::Layer` +let middleware = axum::middleware::from_fn(rewrite_request_uri); + +let app = Router::new(); + +// apply the layer around the whole `Router` +// this way the middleware will run before `Router` receives the request +let app_with_middleware = middleware.layer(app); + +# async { +axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) + .serve(app_with_middleware.into_make_service()) + .await + .unwrap(); +# }; +``` + [`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 @@ -576,3 +618,4 @@ extensions you need. [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 [`State`]: crate::extract::State +[`Service`]: tower::Service diff --git a/axum/src/docs/routing/layer.md b/axum/src/docs/routing/layer.md index f659babba4..0f835e4134 100644 --- a/axum/src/docs/routing/layer.md +++ b/axum/src/docs/routing/layer.md @@ -9,6 +9,11 @@ of routes. Note this differs from [`Handler::layer`](crate::handler::Handler::layer) which adds a middleware to a single handler. +Middleware added with this method will run _after_ routing and thus cannot be +used to rewrite the request URI. See ["Rewriting request URI in +middleware"](crate::middleware#rewriting-request-uri-in-middleware) for more +details and a workaround. + # Example Adding the [`tower::limit::ConcurrencyLimit`] middleware to a group of diff --git a/axum/src/ext_traits/mod.rs b/axum/src/ext_traits/mod.rs new file mode 100644 index 0000000000..60f97844a5 --- /dev/null +++ b/axum/src/ext_traits/mod.rs @@ -0,0 +1 @@ +pub(crate) mod service; diff --git a/axum/src/ext_traits/service.rs b/axum/src/ext_traits/service.rs new file mode 100644 index 0000000000..b901a34bc4 --- /dev/null +++ b/axum/src/ext_traits/service.rs @@ -0,0 +1,43 @@ +use crate::{extract::connect_info::IntoMakeServiceWithConnectInfo, routing::IntoMakeService}; +use tower_service::Service; + +/// Extension trait that adds additional methods to any [`Service`]. +pub trait ServiceExt: Service + Sized { + /// Convert this service into a [`MakeService`], that is a [`Service`] whose + /// response is another service. + /// + /// This is commonly used when applying middleware around an entire [`Router`]. See ["Rewriting + /// request URI in middleware"] for more details. + /// + /// [`MakeService`]: tower::make::MakeService + /// ["Rewriting request URI in middleware"]: crate::middleware#rewriting-request-uri-in-middleware + /// [`Router`]: crate::Router + fn into_make_service(self) -> IntoMakeService; + + /// Convert this service into a [`MakeService`], that will store `C`'s + /// associated `ConnectInfo` in a request extension such that [`ConnectInfo`] + /// can extract it. + /// + /// This enables extracting things like the client's remote address. + /// This is commonly used when applying middleware around an entire [`Router`]. See ["Rewriting + /// request URI in middleware"] for more details. + /// + /// [`MakeService`]: tower::make::MakeService + /// ["Rewriting request URI in middleware"]: crate::middleware#rewriting-request-uri-in-middleware + /// [`Router`]: crate::Router + /// [`ConnectInfo`]: crate::extract::connect_info::ConnectInfo + fn into_make_service_with_connect_info(self) -> IntoMakeServiceWithConnectInfo; +} + +impl ServiceExt for S +where + S: Service + Sized, +{ + fn into_make_service(self) -> IntoMakeService { + IntoMakeService::new(self) + } + + fn into_make_service_with_connect_info(self) -> IntoMakeServiceWithConnectInfo { + IntoMakeServiceWithConnectInfo::new(self) + } +} diff --git a/axum/src/lib.rs b/axum/src/lib.rs index df51086828..a93dcae28b 100644 --- a/axum/src/lib.rs +++ b/axum/src/lib.rs @@ -433,6 +433,7 @@ #[macro_use] pub(crate) mod macros; +mod ext_traits; mod extension; #[cfg(feature = "form")] mod form; @@ -484,3 +485,5 @@ pub use axum_core::{BoxError, Error}; #[cfg(feature = "macros")] pub use axum_macros::debug_handler; + +pub use self::ext_traits::service::ServiceExt; From e7bc484093f50e506c587df2305f1e2c0ca831ed Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 22 Aug 2022 18:36:05 +0200 Subject: [PATCH 2/2] changelog --- axum/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index b93f012895..96f783d16b 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -364,7 +364,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 message points to where the user added the invalid route, rather than somewhere internally in axum ([#1248]) - **added:** Add `ServiceExt` with methods for turning any `Service` into a - `MakeService` similarly to `Router::into_make_service` + `MakeService` similarly to `Router::into_make_service` ([#1302]) [#1077]: https://github.com/tokio-rs/axum/pull/1077 [#1086]: https://github.com/tokio-rs/axum/pull/1086 @@ -377,6 +377,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#1248]: https://github.com/tokio-rs/axum/pull/1248 [#1272]: https://github.com/tokio-rs/axum/pull/1272 [#1301]: https://github.com/tokio-rs/axum/pull/1301 +[#1302]: https://github.com/tokio-rs/axum/pull/1302 [#924]: https://github.com/tokio-rs/axum/pull/924 # 0.5.15 (9. August, 2022)