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

Support turning any Service into a MakeService #1302

Merged
merged 3 commits into from Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions axum/CHANGELOG.md
Expand Up @@ -363,6 +363,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` ([#1302])

[#1077]: https://github.com/tokio-rs/axum/pull/1077
[#1086]: https://github.com/tokio-rs/axum/pull/1086
Expand All @@ -375,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)
Expand Down
43 changes: 43 additions & 0 deletions axum/src/docs/middleware.md
Expand Up @@ -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

Expand Down Expand Up @@ -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<B>(req: Request<B>, next: Next<B>) -> 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
Expand All @@ -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
5 changes: 5 additions & 0 deletions axum/src/docs/routing/layer.md
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions axum/src/ext_traits/mod.rs
@@ -1,5 +1,6 @@
pub(crate) mod request;
pub(crate) mod request_parts;
pub(crate) mod service;

#[cfg(test)]
mod tests {
Expand Down
43 changes: 43 additions & 0 deletions 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<R>: Service<R> + 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<Self>;

/// 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<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C>;
}

impl<S, R> ServiceExt<R> for S
where
S: Service<R> + Sized,
{
fn into_make_service(self) -> IntoMakeService<Self> {
IntoMakeService::new(self)
}

fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
IntoMakeServiceWithConnectInfo::new(self)
}
}
4 changes: 3 additions & 1 deletion axum/src/lib.rs
Expand Up @@ -486,4 +486,6 @@ pub use axum_core::{BoxError, Error};
#[cfg(feature = "macros")]
pub use axum_macros::debug_handler;

pub use self::ext_traits::{request::RequestExt, request_parts::RequestPartsExt};
pub use self::ext_traits::{
request::RequestExt, request_parts::RequestPartsExt, service::ServiceExt,
};