mirror of
https://github.com/tokio-rs/axum.git
synced 2025-04-26 13:56:22 +02:00
Rework middleware docs (#732)
* rework middleware docs * Apply suggestions from code review Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
This commit is contained in:
parent
911c4a788e
commit
a04fc42d75
5 changed files with 320 additions and 225 deletions
|
@ -1,19 +1,24 @@
|
|||
axum is designed to take full advantage of the [`tower`] and [`tower-http`]
|
||||
ecosystem of middleware.
|
||||
axum is unique in that it doesn't have its own bespoke middleware system and
|
||||
instead integrates with [`tower`]. This means the ecosystem of [`tower`] and
|
||||
[`tower-http`] middleware all work with axum.
|
||||
|
||||
If you're new to tower we recommend you read its [guides][tower-guides] for
|
||||
a general introduction to tower and its concepts.
|
||||
While its not necessary to fully understand tower to write or use middleware
|
||||
with axum, having at least a basic understanding of tower's concepts is
|
||||
recommended. See [tower's guides][tower-guides] for a general introduction.
|
||||
Reading the documentation for [`tower::ServiceBuilder`] is also recommended.
|
||||
|
||||
axum supports adding middleware to both individual handlers and entire routers.
|
||||
For more details on that see
|
||||
# Applying middleware
|
||||
|
||||
- [Individual handlers](crate::handler::Handler::layer)
|
||||
- [Routers](crate::routing::Router::layer)
|
||||
axum allows you to add middleware just about anywhere
|
||||
|
||||
- To entire routers with [`Router::layer`] and [`Router::route_layer`].
|
||||
- To method routers with [`MethodRouter::layer`] and [`MethodRouter::route_layer`].
|
||||
- To individual handlers with [`Handler::layer`].
|
||||
|
||||
## Applying multiple middleware
|
||||
|
||||
It's recommended to use [`tower::ServiceBuilder`] to apply multiple middleware at
|
||||
once, instead of calling [`Router::layer`] repeatedly:
|
||||
Its recommended to use [`tower::ServiceBuilder`] to apply multiple middleware at
|
||||
once, instead of calling `layer` (or `route_layer`) repeatedly:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
|
@ -22,7 +27,7 @@ use axum::{
|
|||
Router,
|
||||
};
|
||||
use tower_http::{trace::TraceLayer};
|
||||
use tower::{ServiceBuilder, limit::ConcurrencyLimitLayer};
|
||||
use tower::ServiceBuilder;
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
|
@ -34,7 +39,6 @@ let app = Router::new()
|
|||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(ConcurrencyLimitLayer::new(64))
|
||||
.layer(AddExtensionLayer::new(State {}))
|
||||
);
|
||||
# async {
|
||||
|
@ -42,95 +46,185 @@ let app = Router::new()
|
|||
# };
|
||||
```
|
||||
|
||||
## Middleware and errors
|
||||
# Commonly used middleware
|
||||
|
||||
If you're applying middleware that produces errors you have to handle the errors
|
||||
so they're converted into responses. You can learn more about doing that
|
||||
[here](crate::error_handling).
|
||||
Some commonly used middleware are:
|
||||
|
||||
## Commonly used middleware
|
||||
- [`TraceLayer`](tower_http::trace) for high level tracing/logging.
|
||||
- [`CorsLayer`](tower_http::cors) for handling CORS.
|
||||
- [`CompressionLayer`](tower_http::compression) for automatic compression of
|
||||
responses.
|
||||
- [`RequestIdLayer`](tower_http::request_id) and
|
||||
[`PropagateRequestIdLayer`](tower_http::request_id) set and propagate request
|
||||
ids.
|
||||
- [`TimeoutLayer`](tower::timeout::TimeoutLayer) for timeouts. Note this
|
||||
requires using [`HandleErrorLayer`](crate::error_handling::HandleErrorLayer)
|
||||
to convert timeouts to responses.
|
||||
|
||||
[`tower`] and [`tower_http`] have a large collection of middleware that are
|
||||
compatible with axum. Some commonly used middleware are:
|
||||
# Ordering
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
response::Response,
|
||||
Router,
|
||||
body::{Body, BoxBody},
|
||||
error_handling::HandleErrorLayer,
|
||||
http::Request,
|
||||
routing::get,
|
||||
};
|
||||
use tower::{
|
||||
filter::AsyncFilterLayer,
|
||||
util::AndThenLayer,
|
||||
ServiceBuilder,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
use tower_http::trace::TraceLayer;
|
||||
When you add middleware with [`Router::layer`] (or similar) all previously added
|
||||
routes will be wrapped in the middleware. Generally speaking, this results in
|
||||
middleware being executed from bottom to top.
|
||||
|
||||
So if you do this:
|
||||
|
||||
```rust
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
# let layer_one = axum::AddExtensionLayer::new(());
|
||||
# let layer_two = axum::AddExtensionLayer::new(());
|
||||
# let layer_three = axum::AddExtensionLayer::new(());
|
||||
#
|
||||
# async fn handle_error<T>(error: T) -> axum::http::StatusCode {
|
||||
# axum::http::StatusCode::INTERNAL_SERVER_ERROR
|
||||
# }
|
||||
|
||||
let middleware_stack = ServiceBuilder::new()
|
||||
// Handle errors from middleware
|
||||
//
|
||||
// This middleware most be added above any fallible
|
||||
// ones if you're using `ServiceBuilder`, due to how ordering works
|
||||
.layer(HandleErrorLayer::new(handle_error))
|
||||
// `TraceLayer` adds high level tracing and logging
|
||||
.layer(TraceLayer::new_for_http())
|
||||
// `AsyncFilterLayer` lets you asynchronously transform the request
|
||||
.layer(AsyncFilterLayer::new(map_request))
|
||||
// `AndThenLayer` lets you asynchronously transform the response
|
||||
.layer(AndThenLayer::new(map_response));
|
||||
|
||||
async fn map_request(req: Request<Body>) -> Result<Request<Body>, Infallible> {
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
async fn map_response(res: Response) -> Result<Response, Infallible> {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { /* ... */ }))
|
||||
.layer(middleware_stack);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
.route("/", get(handler))
|
||||
.layer(layer_one)
|
||||
.layer(layer_two)
|
||||
.layer(layer_three);
|
||||
# let app: Router<axum::body::Body> = app;
|
||||
```
|
||||
|
||||
Additionally axum provides [`extract::extractor_middleware()`] for converting
|
||||
any extractor into a middleware. See [`extract::extractor_middleware()`] for
|
||||
more details.
|
||||
Think of the middleware as being layered like an onion where each new layer
|
||||
wraps all previous layers:
|
||||
|
||||
## Writing your own middleware with `axum_extra::middleware::from_fn`
|
||||
```not_rust
|
||||
requests
|
||||
|
|
||||
v
|
||||
+----- layer_three -----+
|
||||
| +---- layer_two ----+ |
|
||||
| | +-- layer_one --+ | |
|
||||
| | | | | |
|
||||
| | | handler | | |
|
||||
| | | | | |
|
||||
| | +-- layer_one --+ | |
|
||||
| +---- layer_two ----+ |
|
||||
+----- layer_three -----+
|
||||
|
|
||||
v
|
||||
responses
|
||||
```
|
||||
|
||||
The easiest way to write a custom middleware is using
|
||||
[`axum_extra::middleware::from_fn`]. See that function for more details.
|
||||
That is:
|
||||
|
||||
[`axum_extra::middleware::from_fn`]: https://docs.rs/axum-extra/0.1/axum_extra/middleware/middleware_fn/fn.from_fn.html
|
||||
- First `layer_three` receives the request
|
||||
- It then does its thing and passes the request onto `layer_two`
|
||||
- Which passes the request onto `layer_one`
|
||||
- Which passes the request onto `handler` where a response is produced
|
||||
- That response is then passes to `layer_one`
|
||||
- Then to `layer_two`
|
||||
- And finally to `layer_three` where it's returned out of your app
|
||||
|
||||
## Writing your own middleware with `tower::Service`
|
||||
It's a little more complicated in practice because any middleware is free to
|
||||
return early and not call the next layer, for example if a request cannot be
|
||||
authorized, but its a useful mental model to have.
|
||||
|
||||
As previously mentioned its recommended to add multiple middleware using
|
||||
`tower::ServiceBuilder`, however this impacts ordering:
|
||||
|
||||
```rust
|
||||
use tower::ServiceBuilder;
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
# let layer_one = axum::AddExtensionLayer::new(());
|
||||
# let layer_two = axum::AddExtensionLayer::new(());
|
||||
# let layer_three = axum::AddExtensionLayer::new(());
|
||||
#
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(layer_one)
|
||||
.layer(layer_two)
|
||||
.layer(layer_three),
|
||||
);
|
||||
# let app: Router<axum::body::Body> = app;
|
||||
```
|
||||
|
||||
`ServiceBuilder` works by composing all layers into one such that they run top
|
||||
to bottom. So with the previous code `layer_one` would receive the request
|
||||
first, then `layer_two`, then `layer_three`, then `handler`, and then the
|
||||
response would bubble back up through `layer_three`, then `layer_two`, and
|
||||
finally `layer_one`.
|
||||
|
||||
Executing middleware top to bottom is generally easier to understand and follow
|
||||
mentally which is one of the reasons `ServiceBuilder` is recommended.
|
||||
|
||||
# Writing middleware
|
||||
|
||||
axum offers many ways of writing middleware, at different levels of abstraction
|
||||
and with different pros and cons.
|
||||
|
||||
## `axum::middleware::from_fn`
|
||||
|
||||
Use [`axum::middleware::from_fn`] to write your middleware when:
|
||||
|
||||
- You're not comfortable with implementing your own futures and would rather use
|
||||
the familiar `async`/`await` syntax.
|
||||
- You don't intend to publish your middleware as a crate for others to use.
|
||||
Middleware written like this are only compatible with axum.
|
||||
|
||||
## `axum::extract::extractor_middleware`
|
||||
|
||||
Use [`axum::extract::extractor_middleware`] to write your middleware when:
|
||||
|
||||
- You have a type that you sometimes want to use as an extractor and sometimes
|
||||
as a middleware. If you only need your type as a middleware prefer
|
||||
[`middleware::from_fn`].
|
||||
|
||||
## tower's combinators
|
||||
|
||||
tower has several utility combinators that can be used to perform simple
|
||||
modifications to requests or responses. The most commonly used ones are
|
||||
|
||||
- [`ServiceBuilder::map_request`]
|
||||
- [`ServiceBuilder::map_response`]
|
||||
- [`ServiceBuilder::then`]
|
||||
- [`ServiceBuilder::and_then`]
|
||||
|
||||
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<Box<dyn Future>>`
|
||||
|
||||
For maximum control (and a more low level API) you can write you own middleware
|
||||
by implementing [`tower::Service`]:
|
||||
|
||||
Use [`tower::Service`] with `Pin<Box<dyn Future>>` to write your middleware when:
|
||||
|
||||
- Your middleware needs to be configurable for example via builder methods on
|
||||
your [`tower::Layer`] such as [`tower_http::trace::TraceLayer`].
|
||||
- You do intend to publish your middleware as a crate for others to use.
|
||||
- You're not comfortable with implementing your own futures.
|
||||
|
||||
A decent template for such a middleware could be:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
response::Response,
|
||||
Router,
|
||||
body::{Body, BoxBody},
|
||||
body::Body,
|
||||
http::Request,
|
||||
routing::get,
|
||||
};
|
||||
use futures::future::BoxFuture;
|
||||
use tower::{Service, layer::layer_fn};
|
||||
use tower::{Service, Layer};
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
struct MyLayer;
|
||||
|
||||
impl<S> Layer<S> for MyLayer {
|
||||
type Service = MyMiddleware<S>;
|
||||
|
||||
fn layer(&self, inner: S) -> Self::Service {
|
||||
MyMiddleware { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MyMiddleware<S> {
|
||||
inner: S,
|
||||
|
@ -138,39 +232,161 @@ struct MyMiddleware<S> {
|
|||
|
||||
impl<S> Service<Request<Body>> for MyMiddleware<S>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response> + Clone + Send + 'static,
|
||||
S: Service<Request<Body>, Response = Response> + Send + 'static,
|
||||
S::Future: Send + 'static,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
// `BoxFuture` is a type alias for `Pin<Box<dyn Future + Send + 'a>>`
|
||||
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: Request<Body>) -> Self::Future {
|
||||
println!("`MyMiddleware` called!");
|
||||
|
||||
// best practice is to clone the inner service like this
|
||||
// see https://github.com/tower-rs/tower/issues/547 for details
|
||||
let clone = self.inner.clone();
|
||||
let mut inner = std::mem::replace(&mut self.inner, clone);
|
||||
|
||||
fn call(&mut self, mut request: Request<Body>) -> Self::Future {
|
||||
let future = self.inner.call(request);
|
||||
Box::pin(async move {
|
||||
let res: Response = inner.call(req).await?;
|
||||
|
||||
println!("`MyMiddleware` received the response");
|
||||
|
||||
Ok(res)
|
||||
let response: Response = future.await?;
|
||||
Ok(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# `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
|
||||
futures is the way to go.
|
||||
|
||||
Use [`tower::Service`] with manual futures to write your middleware when:
|
||||
|
||||
- You want your middleware to have the lowest possible overhead.
|
||||
- Your middleware needs to be configurable for example via builder methods on
|
||||
your [`tower::Layer`] such as [`tower_http::trace::TraceLayer`].
|
||||
- You do intend to publish your middleware as a crate for others to use, perhaps
|
||||
as part of tower-http.
|
||||
- You're comfortable with implementing your own futures, or want to learn how
|
||||
the lower levels of async Rust works.
|
||||
|
||||
tower's ["Building a middleware from scratch"][tower-from-scratch-guide]
|
||||
guide is a good place to learn how to do this.
|
||||
|
||||
# Error handling for middleware
|
||||
|
||||
axum's error handling model requires handlers to always return a response.
|
||||
However middleware is one possible way to introduce errors into an application.
|
||||
If hyper receives an error the connection will be closed without sending a
|
||||
response. Thus axum requires those errors to be handled gracefully:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::get,
|
||||
error_handling::HandleErrorLayer,
|
||||
http::StatusCode,
|
||||
BoxError,
|
||||
Router,
|
||||
};
|
||||
use tower::{ServiceBuilder, timeout::TimeoutLayer};
|
||||
use std::time::Duration;
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { /* ... */ }))
|
||||
.layer(layer_fn(|inner| MyMiddleware { inner }));
|
||||
.route("/", get(handler))
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
// this middleware goes above `TimeoutLayer` because it will receive
|
||||
// errors returned by `TimeoutLayer`
|
||||
.layer(HandleErrorLayer::new(|_: BoxError| async {
|
||||
StatusCode::REQUEST_TIMEOUT
|
||||
}))
|
||||
.layer(TimeoutLayer::new(Duration::from_secs(10)))
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
See [`error_handling`](crate::error_handling) for more details on axum's error
|
||||
handling model.
|
||||
|
||||
# Routing to services/middleware and backpressure
|
||||
|
||||
Generally routing to one of multiple services and backpressure doesn't mix
|
||||
well. Ideally you would want ensure a service is ready to receive a request
|
||||
before calling it. However, in order to know which service to call, you need
|
||||
the request...
|
||||
|
||||
One approach is to not consider the router service itself ready until all
|
||||
destination services are ready. That is the approach used by
|
||||
[`tower::steer::Steer`].
|
||||
|
||||
Another approach is to always consider all services ready (always return
|
||||
`Poll::Ready(Ok(()))`) from `Service::poll_ready` and then actually drive
|
||||
readiness inside the response future returned by `Service::call`. This works
|
||||
well when your services don't care about backpressure and are always ready
|
||||
anyway.
|
||||
|
||||
axum expects that all services used in your app wont care about
|
||||
backpressure and so it uses the latter strategy. However that means you
|
||||
should avoid routing to a service (or using a middleware) that _does_ care
|
||||
about backpressure. At the very least you should [load shed] so requests are
|
||||
dropped quickly and don't keep piling up.
|
||||
|
||||
It also means that if `poll_ready` returns an error then that error will be
|
||||
returned in the response future from `call` and _not_ from `poll_ready`. In
|
||||
that case, the underlying service will _not_ be discarded and will continue
|
||||
to be used for future requests. Services that expect to be discarded if
|
||||
`poll_ready` fails should _not_ be used with axum.
|
||||
|
||||
One possible approach is to only apply backpressure sensitive middleware
|
||||
around your entire app. This is possible because axum applications are
|
||||
themselves services:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use tower::ServiceBuilder;
|
||||
# let some_backpressure_sensitive_middleware =
|
||||
# tower::layer::util::Identity::new();
|
||||
|
||||
async fn handler() { /* ... */ }
|
||||
|
||||
let app = Router::new().route("/", get(handler));
|
||||
|
||||
let app = ServiceBuilder::new()
|
||||
.layer(some_backpressure_sensitive_middleware)
|
||||
.service(app);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
However when applying middleware around your whole application in this way
|
||||
you have to take care that errors are still being handled with
|
||||
appropriately.
|
||||
|
||||
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.
|
||||
|
||||
[`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
|
||||
[`axum::middleware::from_fn`]: crate::middleware::from_fn
|
||||
[`middleware::from_fn`]: crate::middleware::from_fn
|
||||
[tower-from-scratch-guide]: https://github.com/tower-rs/tower/blob/master/guides/building-a-middleware-from-scratch.md
|
||||
[`ServiceBuilder::map_request`]: tower::ServiceBuilder::map_request
|
||||
[`ServiceBuilder::map_response`]: tower::ServiceBuilder::map_response
|
||||
[`ServiceBuilder::then`]: tower::ServiceBuilder::then
|
||||
[`ServiceBuilder::and_then`]: tower::ServiceBuilder::and_then
|
||||
[`axum::extract::extractor_middleware`]: crate::extract::extractor_middleware()
|
||||
[`Handler::layer`]: crate::handler::Handler::layer
|
||||
[`Router::layer`]: crate::routing::Router::layer
|
||||
[`MethodRouter::layer`]: crate::routing::MethodRouter::layer
|
||||
[`Router::route_layer`]: crate::routing::Router::route_layer
|
||||
[`MethodRouter::route_layer`]: crate::routing::MethodRouter::route_layer
|
||||
|
|
|
@ -6,7 +6,7 @@ corresponding middleware.
|
|||
This can be used to add additional processing to a request for a group
|
||||
of routes.
|
||||
|
||||
Note this differs from [`handler::Layered`](crate::handler::Layered)
|
||||
Note this differs from [`Handler::layer`](crate::handler::Handler::layer)
|
||||
which adds a middleware to a single handler.
|
||||
|
||||
# Example
|
||||
|
@ -66,73 +66,12 @@ let app = Router::new()
|
|||
# };
|
||||
```
|
||||
|
||||
# Applying multiple middleware
|
||||
# Multiple middleware
|
||||
|
||||
Its recommended to use [`tower::ServiceBuilder`] to apply multiple middleware at
|
||||
once, instead of calling `layer` repeatedly:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::get,
|
||||
AddExtensionLayer,
|
||||
Router,
|
||||
};
|
||||
use tower_http::{trace::TraceLayer};
|
||||
use tower::{ServiceBuilder, limit::ConcurrencyLimitLayer};
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State {}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(ConcurrencyLimitLayer::new(64))
|
||||
.layer(AddExtensionLayer::new(State {}))
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
It's recommended to use [`tower::ServiceBuilder`] when applying multiple
|
||||
middleware. See [`middleware`](crate::middleware) for more details.
|
||||
|
||||
# Error handling
|
||||
|
||||
axum's error handling model requires handlers to always return a response.
|
||||
However middleware is one possible way to introduce errors into an application.
|
||||
If hyper receives an error the connection will be closed without sending a
|
||||
response. Thus axum requires those errors to be handled gracefully:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::get,
|
||||
error_handling::HandleErrorLayer,
|
||||
http::StatusCode,
|
||||
BoxError,
|
||||
Router,
|
||||
};
|
||||
use tower::{ServiceBuilder, timeout::TimeoutLayer};
|
||||
use std::time::Duration;
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
// this middleware goes above `TimeoutLayer` because it will receive
|
||||
// errors returned by `TimeoutLayer`
|
||||
.layer(HandleErrorLayer::new(|_: BoxError| async {
|
||||
StatusCode::REQUEST_TIMEOUT
|
||||
}))
|
||||
.layer(TimeoutLayer::new(Duration::from_secs(10)))
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
See [`error_handling`](crate::error_handling) for more details on axum's error
|
||||
handling model.
|
||||
See [`middleware`](crate::middleware) for details on how error handling impacts
|
||||
middleware.
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
//! - [Responses](#responses)
|
||||
//! - [Error handling](#error-handling)
|
||||
//! - [Middleware](#middleware)
|
||||
//! - [Routing to services and backpressure](#routing-to-services-and-backpressure)
|
||||
//! - [Sharing state with handlers](#sharing-state-with-handlers)
|
||||
//! - [Building integrations for axum](#building-integrations-for-axum)
|
||||
//! - [Required dependencies](#required-dependencies)
|
||||
|
@ -160,69 +159,8 @@
|
|||
//!
|
||||
//! # Middleware
|
||||
//!
|
||||
#![doc = include_str!("docs/middleware.md")]
|
||||
//!
|
||||
//! # Routing to services and backpressure
|
||||
//!
|
||||
//! Generally routing to one of multiple services and backpressure doesn't mix
|
||||
//! well. Ideally you would want ensure a service is ready to receive a request
|
||||
//! before calling it. However, in order to know which service to call, you need
|
||||
//! the request...
|
||||
//!
|
||||
//! One approach is to not consider the router service itself ready until all
|
||||
//! destination services are ready. That is the approach used by
|
||||
//! [`tower::steer::Steer`].
|
||||
//!
|
||||
//! Another approach is to always consider all services ready (always return
|
||||
//! `Poll::Ready(Ok(()))`) from `Service::poll_ready` and then actually drive
|
||||
//! readiness inside the response future returned by `Service::call`. This works
|
||||
//! well when your services don't care about backpressure and are always ready
|
||||
//! anyway.
|
||||
//!
|
||||
//! axum expects that all services used in your app wont care about
|
||||
//! backpressure and so it uses the latter strategy. However that means you
|
||||
//! should avoid routing to a service (or using a middleware) that _does_ care
|
||||
//! about backpressure. At the very least you should [load shed] so requests are
|
||||
//! dropped quickly and don't keep piling up.
|
||||
//!
|
||||
//! It also means that if `poll_ready` returns an error then that error will be
|
||||
//! returned in the response future from `call` and _not_ from `poll_ready`. In
|
||||
//! that case, the underlying service will _not_ be discarded and will continue
|
||||
//! to be used for future requests. Services that expect to be discarded if
|
||||
//! `poll_ready` fails should _not_ be used with axum.
|
||||
//!
|
||||
//! One possible approach is to only apply backpressure sensitive middleware
|
||||
//! around your entire app. This is possible because axum applications are
|
||||
//! themselves services:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use axum::{
|
||||
//! routing::get,
|
||||
//! Router,
|
||||
//! };
|
||||
//! use tower::ServiceBuilder;
|
||||
//! # let some_backpressure_sensitive_middleware =
|
||||
//! # tower::layer::util::Identity::new();
|
||||
//!
|
||||
//! async fn handler() { /* ... */ }
|
||||
//!
|
||||
//! let app = Router::new().route("/", get(handler));
|
||||
//!
|
||||
//! let app = ServiceBuilder::new()
|
||||
//! .layer(some_backpressure_sensitive_middleware)
|
||||
//! .service(app);
|
||||
//! # async {
|
||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! However when applying middleware around your whole application in this way
|
||||
//! you have to take care that errors are still being handled with
|
||||
//! appropriately.
|
||||
//!
|
||||
//! 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.
|
||||
//! There are several different ways to write middleware for axum. See
|
||||
//! [`middleware`](crate::middleware) for more details.
|
||||
//!
|
||||
//! # Sharing state with handlers
|
||||
//!
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
//! Utilities for writing middleware
|
||||
//!
|
||||
#![doc = include_str!("../docs/middleware.md")]
|
||||
|
||||
mod from_fn;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Example of application using https://github.com/launchbadge/sqlx
|
||||
//! Example of application using <https://github.com/launchbadge/sqlx>
|
||||
//!
|
||||
//! Run with
|
||||
//!
|
||||
|
|
Loading…
Add table
Reference in a new issue