mirror of
https://github.com/tokio-rs/axum.git
synced 2026-09-09 00:00:12 +02:00
chore: move docs from markdown files to rust source
This commit is contained in:
@@ -1,39 +0,0 @@
|
|||||||
## Debugging handler type errors
|
|
||||||
|
|
||||||
For a function to be used as a handler it must implement the [`Handler`] trait.
|
|
||||||
axum provides blanket implementations for functions that:
|
|
||||||
|
|
||||||
- Are `async fn`s.
|
|
||||||
- Take no more than 16 arguments that all implement `Send`.
|
|
||||||
- All except the last argument implement [`FromRequestParts`].
|
|
||||||
- The last argument implements [`FromRequest`].
|
|
||||||
- Returns something that implements [`IntoResponse`].
|
|
||||||
- If a closure is used it must implement `Clone + Send` and be
|
|
||||||
`'static`.
|
|
||||||
- Returns a future that is `Send`. The most common way to accidentally make a
|
|
||||||
future `!Send` is to hold a `!Send` type across an await.
|
|
||||||
|
|
||||||
Unfortunately Rust gives poor error messages if you try to use a function
|
|
||||||
that doesn't quite match what's required by [`Handler`].
|
|
||||||
|
|
||||||
You might get an error like this:
|
|
||||||
|
|
||||||
```not_rust
|
|
||||||
error[E0277]: the trait bound `fn(bool) -> impl Future {handler}: Handler<_, _>` is not satisfied
|
|
||||||
--> src/main.rs:13:44
|
|
||||||
|
|
|
||||||
13 | let app = Router::new().route("/", get(handler));
|
|
||||||
| ^^^^^^^ the trait `Handler<_, _>` is not implemented for `fn(bool) -> impl Future {handler}`
|
|
||||||
|
|
|
||||||
::: axum/src/handler/mod.rs:116:8
|
|
||||||
|
|
|
||||||
116 | H: Handler<T, B>,
|
|
||||||
| ------------- required by this bound in `axum::routing::get`
|
|
||||||
```
|
|
||||||
|
|
||||||
This error doesn't tell you _why_ your function doesn't implement
|
|
||||||
[`Handler`]. It's possible to improve the error with the [`debug_handler`]
|
|
||||||
proc-macro from the [axum-macros] crate.
|
|
||||||
|
|
||||||
[axum-macros]: https://docs.rs/axum-macros
|
|
||||||
[`debug_handler`]: https://docs.rs/axum-macros/latest/axum_macros/attr.debug_handler.html
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
Error handling model and utilities
|
|
||||||
|
|
||||||
# axum's error handling model
|
|
||||||
|
|
||||||
axum is based on [`tower::Service`] which bundles errors through its associated
|
|
||||||
`Error` type. If you have a [`Service`] that produces an error and that error
|
|
||||||
makes it all the way up to hyper, the connection will be terminated _without_
|
|
||||||
sending a response. This is generally not desirable so axum makes sure you
|
|
||||||
always produce a response by relying on the type system.
|
|
||||||
|
|
||||||
axum does this by requiring all services have [`Infallible`] as their error
|
|
||||||
type. `Infallible` is the error type for errors that can never happen.
|
|
||||||
|
|
||||||
This means if you define a handler like:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::http::StatusCode;
|
|
||||||
|
|
||||||
async fn handler() -> Result<String, StatusCode> {
|
|
||||||
# todo!()
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
While it looks like it might fail with a `StatusCode` this actually isn't an
|
|
||||||
"error". If this handler returns `Err(some_status_code)` that will still be
|
|
||||||
converted into a [`Response`] and sent back to the client. This is done
|
|
||||||
through `StatusCode`'s [`IntoResponse`] implementation.
|
|
||||||
|
|
||||||
It doesn't matter whether you return `Err(StatusCode::NOT_FOUND)` or
|
|
||||||
`Err(StatusCode::INTERNAL_SERVER_ERROR)`. These are not considered errors in
|
|
||||||
axum.
|
|
||||||
|
|
||||||
Instead of a direct `StatusCode`, it makes sense to use intermediate error type
|
|
||||||
that can ultimately be converted to `Response`. This allows using `?` operator
|
|
||||||
in handlers. See those examples:
|
|
||||||
|
|
||||||
* [`anyhow-error-response`][anyhow] for generic boxed errors
|
|
||||||
* [`error-handling`][error-handling] for application-specific detailed errors
|
|
||||||
|
|
||||||
[anyhow]: https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs
|
|
||||||
[error-handling]: https://github.com/tokio-rs/axum/blob/main/examples/error-handling/src/main.rs
|
|
||||||
|
|
||||||
This also applies to extractors. If an extractor doesn't match the request the
|
|
||||||
request will be rejected and a response will be returned without calling your
|
|
||||||
handler. See [`extract`](crate::extract) to learn more about handling extractor
|
|
||||||
failures.
|
|
||||||
|
|
||||||
# Routing to fallible services
|
|
||||||
|
|
||||||
You generally don't have to think about errors if you're only using async
|
|
||||||
functions as handlers. However if you're embedding general `Service`s or
|
|
||||||
applying middleware, which might produce errors you have to tell axum how to
|
|
||||||
convert those errors into responses.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
body::Body,
|
|
||||||
http::{Request, Response, StatusCode},
|
|
||||||
error_handling::HandleError,
|
|
||||||
};
|
|
||||||
|
|
||||||
async fn thing_that_might_fail() -> Result<(), anyhow::Error> {
|
|
||||||
# Ok(())
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// this service might fail with `anyhow::Error`
|
|
||||||
let some_fallible_service = tower::service_fn(|_req| async {
|
|
||||||
thing_that_might_fail().await?;
|
|
||||||
Ok::<_, anyhow::Error>(Response::new(Body::empty()))
|
|
||||||
});
|
|
||||||
|
|
||||||
let app = Router::new().route_service(
|
|
||||||
"/",
|
|
||||||
// we cannot route to `some_fallible_service` directly since it might fail.
|
|
||||||
// we have to use `handle_error` which converts its errors into responses
|
|
||||||
// and changes its error type from `anyhow::Error` to `Infallible`.
|
|
||||||
HandleError::new(some_fallible_service, handle_anyhow_error),
|
|
||||||
);
|
|
||||||
|
|
||||||
// handle errors by converting them into something that implements
|
|
||||||
// `IntoResponse`
|
|
||||||
async fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!("Something went wrong: {err}"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Applying fallible middleware
|
|
||||||
|
|
||||||
Similarly axum requires you to handle errors from middleware. That is done with
|
|
||||||
[`HandleErrorLayer`]:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
BoxError,
|
|
||||||
routing::get,
|
|
||||||
http::StatusCode,
|
|
||||||
error_handling::HandleErrorLayer,
|
|
||||||
};
|
|
||||||
use std::time::Duration;
|
|
||||||
use tower::ServiceBuilder;
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(|| async {}))
|
|
||||||
.layer(
|
|
||||||
ServiceBuilder::new()
|
|
||||||
// `timeout` will produce an error if the handler takes
|
|
||||||
// too long so we must handle those
|
|
||||||
.layer(HandleErrorLayer::new(handle_timeout_error))
|
|
||||||
.timeout(Duration::from_secs(30))
|
|
||||||
);
|
|
||||||
|
|
||||||
async fn handle_timeout_error(err: BoxError) -> (StatusCode, String) {
|
|
||||||
if err.is::<tower::timeout::error::Elapsed>() {
|
|
||||||
(
|
|
||||||
StatusCode::REQUEST_TIMEOUT,
|
|
||||||
"Request took too long".to_string(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!("Unhandled internal error: {err}"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Running extractors for error handling
|
|
||||||
|
|
||||||
`HandleErrorLayer` also supports running extractors:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
BoxError,
|
|
||||||
routing::get,
|
|
||||||
http::{StatusCode, Method, Uri},
|
|
||||||
error_handling::HandleErrorLayer,
|
|
||||||
};
|
|
||||||
use std::time::Duration;
|
|
||||||
use tower::ServiceBuilder;
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(|| async {}))
|
|
||||||
.layer(
|
|
||||||
ServiceBuilder::new()
|
|
||||||
// `timeout` will produce an error if the handler takes
|
|
||||||
// too long so we must handle those
|
|
||||||
.layer(HandleErrorLayer::new(handle_timeout_error))
|
|
||||||
.timeout(Duration::from_secs(30))
|
|
||||||
);
|
|
||||||
|
|
||||||
async fn handle_timeout_error(
|
|
||||||
// `Method` and `Uri` are extractors so they can be used here
|
|
||||||
method: Method,
|
|
||||||
uri: Uri,
|
|
||||||
// the last argument must be the error itself
|
|
||||||
err: BoxError,
|
|
||||||
) -> (StatusCode, String) {
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!("`{method} {uri}` failed with {err}"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
[`tower::Service`]: `tower::Service`
|
|
||||||
[`Infallible`]: std::convert::Infallible
|
|
||||||
[`Response`]: crate::response::Response
|
|
||||||
[`IntoResponse`]: crate::response::IntoResponse
|
|
||||||
@@ -1,689 +0,0 @@
|
|||||||
Types and traits for extracting data from requests.
|
|
||||||
|
|
||||||
# Intro
|
|
||||||
|
|
||||||
A handler function is an async function that takes any number of
|
|
||||||
"extractors" as arguments. An extractor is a type that implements
|
|
||||||
[`FromRequest`] or [`FromRequestParts`].
|
|
||||||
|
|
||||||
For example, [`Json`] is an extractor that consumes the request body and
|
|
||||||
deserializes it as JSON into some target type:
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{
|
|
||||||
extract::Json,
|
|
||||||
routing::post,
|
|
||||||
handler::Handler,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateUser {
|
|
||||||
email: String,
|
|
||||||
password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_user(Json(payload): Json<CreateUser>) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = Router::new().route("/users", post(create_user));
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Common extractors
|
|
||||||
|
|
||||||
Some commonly used extractors are:
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{
|
|
||||||
extract::{Request, Json, Path, Extension, Query},
|
|
||||||
routing::post,
|
|
||||||
http::header::HeaderMap,
|
|
||||||
body::{Bytes, Body},
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use serde_json::Value;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
// `Path` gives you the path parameters and deserializes them. See its docs for
|
|
||||||
// more details
|
|
||||||
async fn path(Path(user_id): Path<u32>) {}
|
|
||||||
|
|
||||||
// `Query` gives you the query parameters and deserializes them.
|
|
||||||
async fn query(Query(params): Query<HashMap<String, String>>) {}
|
|
||||||
|
|
||||||
// `HeaderMap` gives you all the headers
|
|
||||||
async fn headers(headers: HeaderMap) {}
|
|
||||||
|
|
||||||
// `String` consumes the request body and ensures it is valid utf-8
|
|
||||||
async fn string(body: String) {}
|
|
||||||
|
|
||||||
// `Bytes` gives you the raw request body
|
|
||||||
async fn bytes(body: Bytes) {}
|
|
||||||
|
|
||||||
// We've already seen `Json` for parsing the request body as json
|
|
||||||
async fn json(Json(payload): Json<Value>) {}
|
|
||||||
|
|
||||||
// `Request` gives you the whole request for maximum control
|
|
||||||
async fn request(request: Request) {}
|
|
||||||
|
|
||||||
// `Extension` extracts data from "request extensions"
|
|
||||||
// This is commonly used to share state with handlers
|
|
||||||
async fn extension(Extension(state): Extension<State>) {}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct State { /* ... */ }
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/path/{user_id}", post(path))
|
|
||||||
.route("/query", post(query))
|
|
||||||
.route("/string", post(string))
|
|
||||||
.route("/bytes", post(bytes))
|
|
||||||
.route("/json", post(json))
|
|
||||||
.route("/request", post(request))
|
|
||||||
.route("/extension", post(extension));
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Applying multiple extractors
|
|
||||||
|
|
||||||
You can also apply multiple extractors:
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{
|
|
||||||
extract::{Path, Query},
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
let app = Router::new().route("/users/{id}/things", get(get_user_things));
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Pagination {
|
|
||||||
page: usize,
|
|
||||||
per_page: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_user_things(
|
|
||||||
Path(user_id): Path<Uuid>,
|
|
||||||
Query(pagination): Query<Pagination>,
|
|
||||||
) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# The order of extractors
|
|
||||||
|
|
||||||
Extractors always run in the order of the function parameters that is from
|
|
||||||
left to right.
|
|
||||||
|
|
||||||
The request body is an asynchronous stream that can only be consumed once.
|
|
||||||
Therefore you can only have one extractor that consumes the request body. axum
|
|
||||||
enforces this by requiring such extractors to be the _last_ argument your
|
|
||||||
handler takes.
|
|
||||||
|
|
||||||
For example
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{extract::State, http::{Method, HeaderMap}};
|
|
||||||
#
|
|
||||||
# #[derive(Clone)]
|
|
||||||
# struct AppState {
|
|
||||||
# }
|
|
||||||
|
|
||||||
async fn handler(
|
|
||||||
// `Method` and `HeaderMap` don't consume the request body so they can
|
|
||||||
// put anywhere in the argument list (but before `body`)
|
|
||||||
method: Method,
|
|
||||||
headers: HeaderMap,
|
|
||||||
// `State` is also an extractor so it needs to be before `body`
|
|
||||||
State(state): State<AppState>,
|
|
||||||
// `String` consumes the request body and thus must be the last extractor
|
|
||||||
body: String,
|
|
||||||
) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let _: axum::routing::MethodRouter<AppState> = axum::routing::get(handler);
|
|
||||||
```
|
|
||||||
|
|
||||||
We get a compile error if `String` isn't the last extractor:
|
|
||||||
|
|
||||||
```rust,compile_fail
|
|
||||||
use axum::http::Method;
|
|
||||||
|
|
||||||
async fn handler(
|
|
||||||
// this doesn't work since `String` must be the last argument
|
|
||||||
body: String,
|
|
||||||
method: Method,
|
|
||||||
) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let _: axum::routing::MethodRouter = axum::routing::get(handler);
|
|
||||||
```
|
|
||||||
|
|
||||||
This also means you cannot consume the request body twice:
|
|
||||||
|
|
||||||
```rust,compile_fail
|
|
||||||
use axum::Json;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Payload {}
|
|
||||||
|
|
||||||
async fn handler(
|
|
||||||
// `String` and `Json` both consume the request body
|
|
||||||
// so they cannot both be used
|
|
||||||
string_body: String,
|
|
||||||
json_body: Json<Payload>,
|
|
||||||
) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let _: axum::routing::MethodRouter = axum::routing::get(handler);
|
|
||||||
```
|
|
||||||
|
|
||||||
axum enforces this by requiring the last extractor implements [`FromRequest`]
|
|
||||||
and all others implement [`FromRequestParts`].
|
|
||||||
|
|
||||||
# Handling extractor rejections
|
|
||||||
|
|
||||||
If you want to handle the case of an extractor failing within a specific
|
|
||||||
handler, you can wrap it in `Result`, with the error being the rejection type
|
|
||||||
of the extractor:
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{
|
|
||||||
extract::{Json, rejection::JsonRejection},
|
|
||||||
routing::post,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
async fn create_user(payload: Result<Json<Value>, JsonRejection>) {
|
|
||||||
match payload {
|
|
||||||
Ok(payload) => {
|
|
||||||
// We got a valid JSON payload
|
|
||||||
}
|
|
||||||
Err(JsonRejection::MissingJsonContentType(_)) => {
|
|
||||||
// Request didn't have `Content-Type: application/json`
|
|
||||||
// header
|
|
||||||
}
|
|
||||||
Err(JsonRejection::JsonDataError(_)) => {
|
|
||||||
// Couldn't deserialize the body into the target type
|
|
||||||
}
|
|
||||||
Err(JsonRejection::JsonSyntaxError(_)) => {
|
|
||||||
// Syntax error in the body
|
|
||||||
}
|
|
||||||
Err(JsonRejection::BytesRejection(_)) => {
|
|
||||||
// Failed to extract the request body
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
// `JsonRejection` is marked `#[non_exhaustive]` so match must
|
|
||||||
// include a catch-all case.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = Router::new().route("/users", post(create_user));
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Optional extractors
|
|
||||||
|
|
||||||
Some extractors implement [`OptionalFromRequestParts`] in addition to
|
|
||||||
[`FromRequestParts`], or [`OptionalFromRequest`] in addition to [`FromRequest`].
|
|
||||||
|
|
||||||
These extractors can be used inside of `Option`. It depends on the particular
|
|
||||||
`OptionalFromRequestParts` or `OptionalFromRequest` implementation what this
|
|
||||||
does: For example for `TypedHeader` from axum-extra, you get `None` if the
|
|
||||||
header you're trying to extract is not part of the request, but if the header
|
|
||||||
is present and fails to parse, the request is rejected.
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{routing::post, Router};
|
|
||||||
use axum_extra::{headers::UserAgent, TypedHeader};
|
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
async fn foo(user_agent: Option<TypedHeader<UserAgent>>) {
|
|
||||||
if let Some(TypedHeader(user_agent)) = user_agent {
|
|
||||||
// The client sent a user agent
|
|
||||||
} else {
|
|
||||||
// No user agent header
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = Router::new().route("/foo", post(foo));
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Customizing extractor responses
|
|
||||||
|
|
||||||
If an extractor fails it will return a response with the error and your
|
|
||||||
handler will not be called. To customize the error response you have two
|
|
||||||
options:
|
|
||||||
|
|
||||||
1. Use `Result<T, T::Rejection>` as your extractor like shown in
|
|
||||||
["Handling extractor rejections"](#handling-extractor-rejections).
|
|
||||||
This works well if you're only using the extractor in a single handler.
|
|
||||||
2. Create your own extractor that in its [`FromRequest`] implementation calls
|
|
||||||
one of axum's built in extractors but returns a different response for
|
|
||||||
rejections. See the [customize-extractor-error] example for more details.
|
|
||||||
|
|
||||||
# Accessing inner errors
|
|
||||||
|
|
||||||
axum's built-in extractors don't directly expose the inner error. This gives us
|
|
||||||
more flexibility and allows us to change internal implementations without
|
|
||||||
breaking the public API.
|
|
||||||
|
|
||||||
For example that means while [`Json`] is implemented using [`serde_json`] it
|
|
||||||
doesn't directly expose the [`serde_json::Error`] that's contained in
|
|
||||||
[`JsonRejection::JsonDataError`]. However it is still possible to access via
|
|
||||||
methods from [`std::error::Error`]:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use std::error::Error;
|
|
||||||
use axum::{
|
|
||||||
extract::{Json, rejection::JsonRejection},
|
|
||||||
response::IntoResponse,
|
|
||||||
http::StatusCode,
|
|
||||||
};
|
|
||||||
use serde_json::{json, Value};
|
|
||||||
|
|
||||||
async fn handler(
|
|
||||||
result: Result<Json<Value>, JsonRejection>,
|
|
||||||
) -> Result<Json<Value>, (StatusCode, String)> {
|
|
||||||
match result {
|
|
||||||
// if the client sent valid JSON then we're good
|
|
||||||
Ok(Json(payload)) => Ok(Json(json!({ "payload": payload }))),
|
|
||||||
|
|
||||||
Err(err) => match err {
|
|
||||||
JsonRejection::JsonDataError(err) => {
|
|
||||||
Err(serde_json_error_response(err))
|
|
||||||
}
|
|
||||||
JsonRejection::JsonSyntaxError(err) => {
|
|
||||||
Err(serde_json_error_response(err))
|
|
||||||
}
|
|
||||||
// handle other rejections from the `Json` extractor
|
|
||||||
JsonRejection::MissingJsonContentType(_) => Err((
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
"Missing `Content-Type: application/json` header".to_string(),
|
|
||||||
)),
|
|
||||||
JsonRejection::BytesRejection(_) => Err((
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"Failed to buffer request body".to_string(),
|
|
||||||
)),
|
|
||||||
// we must provide a catch-all case since `JsonRejection` is marked
|
|
||||||
// `#[non_exhaustive]`
|
|
||||||
_ => Err((
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"Unknown error".to_string(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// attempt to extract the inner `serde_path_to_error::Error<serde_json::Error>`,
|
|
||||||
// if that succeeds we can provide a more specific error.
|
|
||||||
//
|
|
||||||
// `Json` uses `serde_path_to_error` so the error will be wrapped in `serde_path_to_error::Error`.
|
|
||||||
fn serde_json_error_response<E>(err: E) -> (StatusCode, String)
|
|
||||||
where
|
|
||||||
E: Error + 'static,
|
|
||||||
{
|
|
||||||
if let Some(err) = find_error_source::<serde_path_to_error::Error<serde_json::Error>>(&err) {
|
|
||||||
let serde_json_err = err.inner();
|
|
||||||
(
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
format!(
|
|
||||||
"Invalid JSON at line {} column {}",
|
|
||||||
serde_json_err.line(),
|
|
||||||
serde_json_err.column()
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(StatusCode::BAD_REQUEST, "Unknown error".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// attempt to downcast `err` into a `T` and if that fails recursively try and
|
|
||||||
// downcast `err`'s source
|
|
||||||
fn find_error_source<'a, T>(err: &'a (dyn Error + 'static)) -> Option<&'a T>
|
|
||||||
where
|
|
||||||
T: Error + 'static,
|
|
||||||
{
|
|
||||||
if let Some(err) = err.downcast_ref::<T>() {
|
|
||||||
Some(err)
|
|
||||||
} else if let Some(source) = err.source() {
|
|
||||||
find_error_source(source)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# #[tokio::main]
|
|
||||||
# async fn main() {
|
|
||||||
# use axum::extract::FromRequest;
|
|
||||||
#
|
|
||||||
# let req = axum::http::Request::builder()
|
|
||||||
# .header("content-type", "application/json")
|
|
||||||
# .body(axum::body::Body::from("{"))
|
|
||||||
# .unwrap();
|
|
||||||
#
|
|
||||||
# let err = match Json::<serde_json::Value>::from_request(req, &()).await.unwrap_err() {
|
|
||||||
# JsonRejection::JsonSyntaxError(err) => err,
|
|
||||||
# _ => panic!(),
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# let (_, body) = serde_json_error_response(err);
|
|
||||||
# assert_eq!(body, "Invalid JSON at line 1 column 1");
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that while this approach works it might break in the future if axum changes
|
|
||||||
its implementation to use a different error type internally. Such changes might
|
|
||||||
happen without major breaking versions.
|
|
||||||
|
|
||||||
# Defining custom extractors
|
|
||||||
|
|
||||||
You can also define your own extractors by implementing either
|
|
||||||
[`FromRequestParts`] or [`FromRequest`].
|
|
||||||
|
|
||||||
## Implementing `FromRequestParts`
|
|
||||||
|
|
||||||
Implement `FromRequestParts` if your extractor doesn't need access to the
|
|
||||||
request body:
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{
|
|
||||||
extract::FromRequestParts,
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
http::{
|
|
||||||
StatusCode,
|
|
||||||
header::{HeaderValue, USER_AGENT},
|
|
||||||
request::Parts,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ExtractUserAgent(HeaderValue);
|
|
||||||
|
|
||||||
impl<S> FromRequestParts<S> for ExtractUserAgent
|
|
||||||
where
|
|
||||||
S: Send + Sync,
|
|
||||||
{
|
|
||||||
type Rejection = (StatusCode, &'static str);
|
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
|
||||||
if let Some(user_agent) = parts.headers.get(USER_AGENT) {
|
|
||||||
Ok(ExtractUserAgent(user_agent.clone()))
|
|
||||||
} else {
|
|
||||||
Err((StatusCode::BAD_REQUEST, "`User-Agent` header is missing"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handler(ExtractUserAgent(user_agent): ExtractUserAgent) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = Router::new().route("/foo", get(handler));
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementing `FromRequest`
|
|
||||||
|
|
||||||
If your extractor needs to consume the request body you must implement [`FromRequest`]
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{
|
|
||||||
extract::{Request, FromRequest},
|
|
||||||
response::{Response, IntoResponse},
|
|
||||||
body::{Bytes, Body},
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
http::{
|
|
||||||
StatusCode,
|
|
||||||
header::{HeaderValue, USER_AGENT},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ValidatedBody(Bytes);
|
|
||||||
|
|
||||||
impl<S> FromRequest<S> for ValidatedBody
|
|
||||||
where
|
|
||||||
Bytes: FromRequest<S>,
|
|
||||||
S: Send + Sync,
|
|
||||||
{
|
|
||||||
type Rejection = Response;
|
|
||||||
|
|
||||||
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
|
|
||||||
let body = Bytes::from_request(req, state)
|
|
||||||
.await
|
|
||||||
.map_err(IntoResponse::into_response)?;
|
|
||||||
|
|
||||||
// do validation...
|
|
||||||
|
|
||||||
Ok(Self(body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handler(ValidatedBody(body): ValidatedBody) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = Router::new().route("/foo", get(handler));
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cannot implement both `FromRequest` and `FromRequestParts`
|
|
||||||
|
|
||||||
Note that you will make your extractor unusable by implementing both
|
|
||||||
`FromRequest` and `FromRequestParts` directly for the same type, unless it is
|
|
||||||
wrapping another extractor:
|
|
||||||
|
|
||||||
```rust,compile_fail
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
routing::get,
|
|
||||||
extract::{FromRequest, Request, FromRequestParts},
|
|
||||||
http::request::Parts,
|
|
||||||
body::Body,
|
|
||||||
};
|
|
||||||
use std::convert::Infallible;
|
|
||||||
|
|
||||||
// Some extractor that doesn't wrap another extractor
|
|
||||||
struct MyExtractor;
|
|
||||||
|
|
||||||
// `MyExtractor` implements both `FromRequest`
|
|
||||||
impl<S> FromRequest<S> for MyExtractor
|
|
||||||
where
|
|
||||||
S: Send + Sync,
|
|
||||||
{
|
|
||||||
type Rejection = Infallible;
|
|
||||||
|
|
||||||
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
|
|
||||||
// ...
|
|
||||||
# todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// and `FromRequestParts`
|
|
||||||
impl<S> FromRequestParts<S> for MyExtractor
|
|
||||||
where
|
|
||||||
S: Send + Sync,
|
|
||||||
{
|
|
||||||
type Rejection = Infallible;
|
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
|
||||||
// ...
|
|
||||||
# todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = Router::new().route(
|
|
||||||
"/",
|
|
||||||
// This fails when we go to actually use `MyExtractor` in a handler function.
|
|
||||||
// This is due to a limit in Rust's type system.
|
|
||||||
//
|
|
||||||
// The workaround is to implement either `FromRequest` or `FromRequestParts`
|
|
||||||
// but not both, if your extractor doesn't wrap another extractor.
|
|
||||||
//
|
|
||||||
// See "Wrapping extractors" for how to wrap other extractors.
|
|
||||||
get(|_: MyExtractor| async {}),
|
|
||||||
);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Accessing other extractors in `FromRequest` or `FromRequestParts` implementations
|
|
||||||
|
|
||||||
When defining custom extractors you often need to access another extractor
|
|
||||||
in your implementation.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
extract::{Extension, FromRequestParts},
|
|
||||||
http::{StatusCode, HeaderMap, request::Parts},
|
|
||||||
response::{IntoResponse, Response},
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct State {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AuthenticatedUser {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> FromRequestParts<S> for AuthenticatedUser
|
|
||||||
where
|
|
||||||
S: Send + Sync,
|
|
||||||
{
|
|
||||||
type Rejection = Response;
|
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
|
||||||
// You can either call them directly...
|
|
||||||
let headers = HeaderMap::from_request_parts(parts, state)
|
|
||||||
.await
|
|
||||||
.map_err(|err| match err {})?;
|
|
||||||
|
|
||||||
// ... or use `extract` / `extract_with_state` from `RequestExt` / `RequestPartsExt`
|
|
||||||
use axum::RequestPartsExt;
|
|
||||||
let Extension(state) = parts.extract::<Extension<State>>()
|
|
||||||
.await
|
|
||||||
.map_err(|err| err.into_response())?;
|
|
||||||
|
|
||||||
unimplemented!("actually perform the authorization")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handler(user: AuthenticatedUser) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = State { /* ... */ };
|
|
||||||
|
|
||||||
let app = Router::new().route("/", get(handler)).layer(Extension(state));
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Request body limits
|
|
||||||
|
|
||||||
For security reasons, [`Bytes`] will, by default, not accept bodies larger than
|
|
||||||
2MB. This also applies to extractors that uses [`Bytes`] internally such as
|
|
||||||
`String`, [`Json`], and [`Form`].
|
|
||||||
|
|
||||||
For more details, including how to disable this limit, see [`DefaultBodyLimit`].
|
|
||||||
|
|
||||||
# Wrapping extractors
|
|
||||||
|
|
||||||
If you want write an extractor that generically wraps another extractor (that
|
|
||||||
may or may not consume the request body) you should implement both
|
|
||||||
[`FromRequest`] and [`FromRequestParts`]:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
body::Body,
|
|
||||||
routing::get,
|
|
||||||
extract::{Request, FromRequest, FromRequestParts},
|
|
||||||
http::{HeaderMap, request::Parts},
|
|
||||||
};
|
|
||||||
use std::time::{Instant, Duration};
|
|
||||||
|
|
||||||
// an extractor that wraps another and measures how long time it takes to run
|
|
||||||
struct Timing<E> {
|
|
||||||
extractor: E,
|
|
||||||
duration: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
// we must implement both `FromRequestParts`
|
|
||||||
impl<S, T> FromRequestParts<S> for Timing<T>
|
|
||||||
where
|
|
||||||
S: Send + Sync,
|
|
||||||
T: FromRequestParts<S>,
|
|
||||||
{
|
|
||||||
type Rejection = T::Rejection;
|
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
|
||||||
let start = Instant::now();
|
|
||||||
let extractor = T::from_request_parts(parts, state).await?;
|
|
||||||
let duration = start.elapsed();
|
|
||||||
Ok(Timing {
|
|
||||||
extractor,
|
|
||||||
duration,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// and `FromRequest`
|
|
||||||
impl<S, T> FromRequest<S> for Timing<T>
|
|
||||||
where
|
|
||||||
S: Send + Sync,
|
|
||||||
T: FromRequest<S>,
|
|
||||||
{
|
|
||||||
type Rejection = T::Rejection;
|
|
||||||
|
|
||||||
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
|
|
||||||
let start = Instant::now();
|
|
||||||
let extractor = T::from_request(req, state).await?;
|
|
||||||
let duration = start.elapsed();
|
|
||||||
Ok(Timing {
|
|
||||||
extractor,
|
|
||||||
duration,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handler(
|
|
||||||
// this uses the `FromRequestParts` impl
|
|
||||||
_: Timing<HeaderMap>,
|
|
||||||
// this uses the `FromRequest` impl
|
|
||||||
_: Timing<String>,
|
|
||||||
) {}
|
|
||||||
# let _: axum::routing::MethodRouter = axum::routing::get(handler);
|
|
||||||
```
|
|
||||||
|
|
||||||
# Logging rejections
|
|
||||||
|
|
||||||
All built-in extractors will log rejections for easier debugging. To see the
|
|
||||||
logs, enable the `tracing` feature for axum (enabled by default) and the
|
|
||||||
`axum::rejection=trace` tracing target, for example with
|
|
||||||
`RUST_LOG=info,axum::rejection=trace cargo run`.
|
|
||||||
|
|
||||||
[axum-extra]: https://docs.rs/axum-extra/latest/axum_extra/extract/index.html
|
|
||||||
[`body::Body`]: crate::body::Body
|
|
||||||
[`Bytes`]: crate::body::Bytes
|
|
||||||
[customize-extractor-error]: https://github.com/tokio-rs/axum/blob/main/examples/customize-extractor-error/src/main.rs
|
|
||||||
[`HeaderMap`]: https://docs.rs/http/latest/http/header/struct.HeaderMap.html
|
|
||||||
[`Request`]: https://docs.rs/http/latest/http/struct.Request.html
|
|
||||||
[`JsonRejection::JsonDataError`]: rejection::JsonRejection::JsonDataError
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
In axum a "handler" is an async function that accepts zero or more
|
|
||||||
["extractors"](crate::extract) as arguments and returns something that
|
|
||||||
can be converted [into a response](crate::response).
|
|
||||||
|
|
||||||
Handlers are where your application logic lives and axum applications are built
|
|
||||||
by routing between handlers.
|
|
||||||
|
|
||||||
[`debug_handler`]: https://docs.rs/axum-macros/latest/axum_macros/attr.debug_handler.html
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
Add a fallback service to the router.
|
|
||||||
|
|
||||||
This service will be called if no routes matches the incoming request.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
routing::get,
|
|
||||||
handler::Handler,
|
|
||||||
response::IntoResponse,
|
|
||||||
http::{StatusCode, Method, Uri},
|
|
||||||
};
|
|
||||||
|
|
||||||
let handler = get(|| async {}).fallback(fallback);
|
|
||||||
|
|
||||||
let app = Router::new().route("/", handler);
|
|
||||||
|
|
||||||
async fn fallback(method: Method, uri: Uri) -> (StatusCode, String) {
|
|
||||||
(StatusCode::NOT_FOUND, format!("`{method}` not allowed for {uri}"))
|
|
||||||
}
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
## When used with `MethodRouter::merge`
|
|
||||||
|
|
||||||
Two routers that both have a fallback cannot be merged. Doing so results in a
|
|
||||||
panic:
|
|
||||||
|
|
||||||
```rust,should_panic
|
|
||||||
use axum::{
|
|
||||||
routing::{get, post},
|
|
||||||
handler::Handler,
|
|
||||||
response::IntoResponse,
|
|
||||||
http::{StatusCode, Uri},
|
|
||||||
};
|
|
||||||
|
|
||||||
let one = get(|| async {}).fallback(fallback_one);
|
|
||||||
|
|
||||||
let two = post(|| async {}).fallback(fallback_two);
|
|
||||||
|
|
||||||
let method_route = one.merge(two);
|
|
||||||
|
|
||||||
async fn fallback_one() -> impl IntoResponse { /* ... */ }
|
|
||||||
async fn fallback_two() -> impl IntoResponse { /* ... */ }
|
|
||||||
# let app: axum::Router = axum::Router::new().route("/", method_route);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setting the `Allow` header
|
|
||||||
|
|
||||||
By default `MethodRouter` will set the `Allow` header when returning `405 Method
|
|
||||||
Not Allowed`. This is also done when the fallback is used unless the response
|
|
||||||
generated by the fallback already sets the `Allow` header.
|
|
||||||
|
|
||||||
This means if you use `fallback` to accept additional methods, you should make
|
|
||||||
sure you set the `Allow` header correctly.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
Apply a [`tower::Layer`] to all routes in the router.
|
|
||||||
|
|
||||||
This can be used to add additional processing to a request for a group
|
|
||||||
of routes.
|
|
||||||
|
|
||||||
Note that the middleware is only applied to existing routes. So you have to
|
|
||||||
first add your routes (and / or fallback) and then call `layer` afterwards. Additional
|
|
||||||
routes added after `layer` is called will not have the middleware added.
|
|
||||||
|
|
||||||
Works similarly to [`Router::layer`](super::Router::layer). See that method for
|
|
||||||
more details.
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{routing::get, Router};
|
|
||||||
use tower::limit::ConcurrencyLimitLayer;
|
|
||||||
|
|
||||||
async fn handler() {}
|
|
||||||
|
|
||||||
let app = Router::new().route(
|
|
||||||
"/",
|
|
||||||
// All requests to `GET /` will be sent through `ConcurrencyLimitLayer`
|
|
||||||
get(handler).layer(ConcurrencyLimitLayer::new(64)),
|
|
||||||
);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
Merge two routers into one.
|
|
||||||
|
|
||||||
This is useful for breaking routers into smaller pieces and combining them
|
|
||||||
into one.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
routing::{get, post},
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
|
|
||||||
let get = get(|| async {});
|
|
||||||
let post = post(|| async {});
|
|
||||||
|
|
||||||
let merged = get.merge(post);
|
|
||||||
|
|
||||||
let app = Router::new().route("/", merged);
|
|
||||||
|
|
||||||
// Our app now accepts
|
|
||||||
// - GET /
|
|
||||||
// - POST /
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
Apply a [`tower::Layer`] to the router that will only run if the request matches
|
|
||||||
a route.
|
|
||||||
|
|
||||||
Note that the middleware is only applied to existing routes. So you have to
|
|
||||||
first add your routes (and / or fallback) and then call `route_layer`
|
|
||||||
afterwards. Additional routes added after `route_layer` is called will not have
|
|
||||||
the middleware added.
|
|
||||||
|
|
||||||
This works similarly to [`MethodRouter::layer`] except the middleware will only run if
|
|
||||||
the request matches a route. This is useful for middleware that return early
|
|
||||||
(such as authorization) which might otherwise convert a `405 Method Not Allowed` into a
|
|
||||||
`401 Unauthorized`.
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use tower_http::validate_request::ValidateRequestHeaderLayer;
|
|
||||||
|
|
||||||
let app = Router::new().route(
|
|
||||||
"/foo",
|
|
||||||
get(|| async {})
|
|
||||||
.route_layer(ValidateRequestHeaderLayer::bearer("password"))
|
|
||||||
);
|
|
||||||
|
|
||||||
// `GET /foo` with a valid token will receive `200 OK`
|
|
||||||
// `GET /foo` with a invalid token will receive `401 Unauthorized`
|
|
||||||
// `POST /FOO` with a invalid token will receive `405 Method Not Allowed`
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
@@ -1,583 +0,0 @@
|
|||||||
# Intro
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
While it's 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.
|
|
||||||
|
|
||||||
# Applying middleware
|
|
||||||
|
|
||||||
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 `layer` (or `route_layer`) repeatedly:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
routing::get,
|
|
||||||
Extension,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use tower_http::{trace::TraceLayer};
|
|
||||||
use tower::ServiceBuilder;
|
|
||||||
|
|
||||||
async fn handler() {}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct State {}
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(handler))
|
|
||||||
.layer(
|
|
||||||
ServiceBuilder::new()
|
|
||||||
.layer(TraceLayer::new_for_http())
|
|
||||||
.layer(Extension(State {}))
|
|
||||||
);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Commonly used middleware
|
|
||||||
|
|
||||||
Some commonly used middleware are:
|
|
||||||
|
|
||||||
- [`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_http::timeout::TimeoutLayer) for timeouts.
|
|
||||||
|
|
||||||
# Ordering
|
|
||||||
|
|
||||||
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::Extension(());
|
|
||||||
# let layer_two = axum::Extension(());
|
|
||||||
# let layer_three = axum::Extension(());
|
|
||||||
#
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(handler))
|
|
||||||
.layer(layer_one)
|
|
||||||
.layer(layer_two)
|
|
||||||
.layer(layer_three);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
Think of the middleware as being layered like an onion where each new layer
|
|
||||||
wraps all previous layers:
|
|
||||||
|
|
||||||
```not_rust
|
|
||||||
requests
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+----- layer_three -----+
|
|
||||||
| +---- layer_two ----+ |
|
|
||||||
| | +-- layer_one --+ | |
|
|
||||||
| | | | | |
|
|
||||||
| | | handler | | |
|
|
||||||
| | | | | |
|
|
||||||
| | +-- layer_one --+ | |
|
|
||||||
| +---- layer_two ----+ |
|
|
||||||
+----- layer_three -----+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
responses
|
|
||||||
```
|
|
||||||
|
|
||||||
That is:
|
|
||||||
|
|
||||||
- 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 passed to `layer_one`
|
|
||||||
- Then to `layer_two`
|
|
||||||
- And finally to `layer_three` where it's returned out of your app
|
|
||||||
|
|
||||||
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 it's a useful mental model to have.
|
|
||||||
|
|
||||||
As previously mentioned it's 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::Extension(());
|
|
||||||
# let layer_two = axum::Extension(());
|
|
||||||
# let layer_three = axum::Extension(());
|
|
||||||
#
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(handler))
|
|
||||||
.layer(
|
|
||||||
ServiceBuilder::new()
|
|
||||||
.layer(layer_one)
|
|
||||||
.layer(layer_two)
|
|
||||||
.layer(layer_three),
|
|
||||||
);
|
|
||||||
# let _: Router = 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::middleware::from_extractor`
|
|
||||||
|
|
||||||
Use [`axum::middleware::from_extractor`] 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 your 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,
|
|
||||||
body::Body,
|
|
||||||
extract::Request,
|
|
||||||
};
|
|
||||||
use futures_core::future::BoxFuture;
|
|
||||||
use tower::{Service, Layer};
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> Service<Request> for MyMiddleware<S>
|
|
||||||
where
|
|
||||||
S: Service<Request, 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, request: Request) -> Self::Future {
|
|
||||||
let future = self.inner.call(request);
|
|
||||||
Box::pin(async move {
|
|
||||||
let response: Response = future.await?;
|
|
||||||
Ok(response)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that your error type being defined as `S::Error` means that your middleware typically _returns no errors_. As a principle always try to return a response and try not to bail out with a custom error type. For example, if a 3rd party library you are using inside your new middleware returns its own specialized error type, try to convert it to some reasonable response and return `Ok` with that response.
|
|
||||||
|
|
||||||
If you choose to implement a custom error type such as `type Error = BoxError` (a boxed opaque error), or any other error type that is not `Infallible`, you must use a `HandleErrorLayer`, here is an example using a `ServiceBuilder`:
|
|
||||||
|
|
||||||
```ignore
|
|
||||||
ServiceBuilder::new()
|
|
||||||
.layer(HandleErrorLayer::new(|_: BoxError| async {
|
|
||||||
// because axum uses infallible errors, you must handle your custom error type from your middleware here
|
|
||||||
StatusCode::BAD_REQUEST
|
|
||||||
}))
|
|
||||||
.layer(
|
|
||||||
// <your actual layer which DOES return an error>
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## `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(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)))
|
|
||||||
);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
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 won't 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][tower::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);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
However when applying middleware around your whole application in this way
|
|
||||||
you have to take care that errors are still being handled 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.
|
|
||||||
|
|
||||||
# Accessing state in middleware
|
|
||||||
|
|
||||||
How to make state available to middleware depends on how the middleware is
|
|
||||||
written.
|
|
||||||
|
|
||||||
## Accessing state in `axum::middleware::from_fn`
|
|
||||||
|
|
||||||
Use [`axum::middleware::from_fn_with_state`](crate::middleware::from_fn_with_state).
|
|
||||||
|
|
||||||
## Accessing state in custom `tower::Layer`s
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
routing::get,
|
|
||||||
middleware::{self, Next},
|
|
||||||
response::Response,
|
|
||||||
extract::{State, Request},
|
|
||||||
};
|
|
||||||
use tower::{Layer, Service};
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct AppState {}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct MyLayer {
|
|
||||||
state: AppState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> Layer<S> for MyLayer {
|
|
||||||
type Service = MyService<S>;
|
|
||||||
|
|
||||||
fn layer(&self, inner: S) -> Self::Service {
|
|
||||||
MyService {
|
|
||||||
inner,
|
|
||||||
state: self.state.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct MyService<S> {
|
|
||||||
inner: S,
|
|
||||||
state: AppState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, B> Service<Request<B>> for MyService<S>
|
|
||||||
where
|
|
||||||
S: Service<Request<B>>,
|
|
||||||
{
|
|
||||||
type Response = S::Response;
|
|
||||||
type Error = S::Error;
|
|
||||||
type Future = S::Future;
|
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
self.inner.poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
|
||||||
// Do something with `self.state`.
|
|
||||||
//
|
|
||||||
// See `axum::RequestExt` for how to run extractors directly from
|
|
||||||
// a `Request`.
|
|
||||||
|
|
||||||
self.inner.call(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handler(_: State<AppState>) {}
|
|
||||||
|
|
||||||
let state = AppState {};
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(handler))
|
|
||||||
.layer(MyLayer { state: state.clone() })
|
|
||||||
.with_state(state);
|
|
||||||
# let _: axum::Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Passing state from middleware to handlers
|
|
||||||
|
|
||||||
State can be passed from middleware to handlers using [request extensions]:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
http::StatusCode,
|
|
||||||
routing::get,
|
|
||||||
response::{IntoResponse, Response},
|
|
||||||
middleware::{self, Next},
|
|
||||||
extract::{Request, Extension},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct CurrentUser { /* ... */ }
|
|
||||||
|
|
||||||
async fn auth(mut req: Request, next: Next) -> Result<Response, StatusCode> {
|
|
||||||
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 {
|
|
||||||
// insert the current user into a request extension so the handler can
|
|
||||||
// extract it
|
|
||||||
req.extensions_mut().insert(current_user);
|
|
||||||
Ok(next.run(req).await)
|
|
||||||
} else {
|
|
||||||
Err(StatusCode::UNAUTHORIZED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn authorize_current_user(auth_token: &str) -> Option<CurrentUser> {
|
|
||||||
// ...
|
|
||||||
# unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handler(
|
|
||||||
// extract the current user, set by the middleware
|
|
||||||
Extension(current_user): Extension<CurrentUser>,
|
|
||||||
) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(handler))
|
|
||||||
.route_layer(middleware::from_fn(auth));
|
|
||||||
# let _: 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.
|
|
||||||
|
|
||||||
# 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,
|
|
||||||
extract::Request,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn rewrite_request_uri<B>(req: Request<B>) -> Request<B> {
|
|
||||||
// ...
|
|
||||||
# req
|
|
||||||
}
|
|
||||||
|
|
||||||
// this can be any `tower::Layer`
|
|
||||||
let middleware = tower::util::MapRequestLayer::new(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 {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, 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
|
|
||||||
[`axum::middleware::from_fn`]: fn@crate::middleware::from_fn
|
|
||||||
[`middleware::from_fn`]: 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::middleware::from_extractor`]: fn@crate::middleware::from_extractor
|
|
||||||
[`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
|
|
||||||
[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
|
|
||||||
@@ -1,321 +0,0 @@
|
|||||||
Types and traits for generating responses.
|
|
||||||
|
|
||||||
# Building responses
|
|
||||||
|
|
||||||
Anything that implements [`IntoResponse`] can be returned from a handler. axum
|
|
||||||
provides implementations for common types:
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{
|
|
||||||
Json,
|
|
||||||
response::{Html, IntoResponse},
|
|
||||||
http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}},
|
|
||||||
};
|
|
||||||
|
|
||||||
// `()` gives an empty response
|
|
||||||
async fn empty() {}
|
|
||||||
|
|
||||||
// String will get a `text/plain; charset=utf-8` content-type
|
|
||||||
async fn plain_text(uri: Uri) -> String {
|
|
||||||
format!("Hi from {}", uri.path())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes will get a `application/octet-stream` content-type
|
|
||||||
async fn bytes() -> Vec<u8> {
|
|
||||||
vec![1, 2, 3, 4]
|
|
||||||
}
|
|
||||||
|
|
||||||
// `Json` will get a `application/json` content-type and work with anything that
|
|
||||||
// implements `serde::Serialize`
|
|
||||||
async fn json() -> Json<Vec<String>> {
|
|
||||||
Json(vec!["foo".to_owned(), "bar".to_owned()])
|
|
||||||
}
|
|
||||||
|
|
||||||
// `Html` will get a `text/html` content-type
|
|
||||||
async fn html() -> Html<&'static str> {
|
|
||||||
Html("<p>Hello, World!</p>")
|
|
||||||
}
|
|
||||||
|
|
||||||
// `StatusCode` gives an empty response with that status code
|
|
||||||
async fn status() -> StatusCode {
|
|
||||||
StatusCode::NOT_FOUND
|
|
||||||
}
|
|
||||||
|
|
||||||
// `HeaderMap` gives an empty response with some headers
|
|
||||||
async fn headers() -> HeaderMap {
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(header::SERVER, "axum".parse().unwrap());
|
|
||||||
headers
|
|
||||||
}
|
|
||||||
|
|
||||||
// An array of tuples also gives headers
|
|
||||||
async fn array_headers() -> [(HeaderName, &'static str); 2] {
|
|
||||||
[
|
|
||||||
(header::SERVER, "axum"),
|
|
||||||
(header::CONTENT_TYPE, "text/plain")
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use `impl IntoResponse` to avoid writing the whole type
|
|
||||||
async fn impl_trait() -> impl IntoResponse {
|
|
||||||
[
|
|
||||||
(header::SERVER, "axum"),
|
|
||||||
(header::CONTENT_TYPE, "text/plain")
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Additionally you can return tuples to build more complex responses from
|
|
||||||
individual parts.
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{
|
|
||||||
Json,
|
|
||||||
response::IntoResponse,
|
|
||||||
http::{StatusCode, HeaderMap, Uri, header},
|
|
||||||
extract::Extension,
|
|
||||||
};
|
|
||||||
|
|
||||||
// `(StatusCode, impl IntoResponse)` will override the status code of the response
|
|
||||||
async fn with_status(uri: Uri) -> (StatusCode, String) {
|
|
||||||
(StatusCode::NOT_FOUND, format!("Not Found: {}", uri.path()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use `impl IntoResponse` to avoid having to type the whole type
|
|
||||||
async fn impl_trait(uri: Uri) -> impl IntoResponse {
|
|
||||||
(StatusCode::NOT_FOUND, format!("Not Found: {}", uri.path()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// `(HeaderMap, impl IntoResponse)` to add additional headers
|
|
||||||
async fn with_headers() -> impl IntoResponse {
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
|
|
||||||
(headers, "foo")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or an array of tuples to more easily build the headers
|
|
||||||
async fn with_array_headers() -> impl IntoResponse {
|
|
||||||
([(header::CONTENT_TYPE, "text/plain")], "foo")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use string keys for custom headers
|
|
||||||
async fn with_array_headers_custom() -> impl IntoResponse {
|
|
||||||
([("x-custom", "custom")], "foo")
|
|
||||||
}
|
|
||||||
|
|
||||||
// `(StatusCode, headers, impl IntoResponse)` to set status and add headers
|
|
||||||
// `headers` can be either a `HeaderMap` or an array of tuples
|
|
||||||
async fn with_status_and_array_headers() -> impl IntoResponse {
|
|
||||||
(
|
|
||||||
StatusCode::NOT_FOUND,
|
|
||||||
[(header::CONTENT_TYPE, "text/plain")],
|
|
||||||
"foo",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// `(Extension<_>, impl IntoResponse)` to set response extensions
|
|
||||||
async fn with_status_extensions() -> impl IntoResponse {
|
|
||||||
(
|
|
||||||
Extension(Foo("foo")),
|
|
||||||
"foo",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Foo(&'static str);
|
|
||||||
|
|
||||||
// Or mix and match all the things
|
|
||||||
async fn all_the_things(uri: Uri) -> impl IntoResponse {
|
|
||||||
let mut header_map = HeaderMap::new();
|
|
||||||
if uri.path() == "/" {
|
|
||||||
header_map.insert(header::SERVER, "axum".parse().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
// set status code
|
|
||||||
StatusCode::NOT_FOUND,
|
|
||||||
// headers with an array
|
|
||||||
[("x-custom", "custom")],
|
|
||||||
// some extensions
|
|
||||||
Extension(Foo("foo")),
|
|
||||||
Extension(Foo("bar")),
|
|
||||||
// more headers, built dynamically
|
|
||||||
header_map,
|
|
||||||
// and finally the body
|
|
||||||
"foo",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In general you can return tuples like:
|
|
||||||
|
|
||||||
- `(StatusCode, impl IntoResponse)`
|
|
||||||
- `(Parts, impl IntoResponse)`
|
|
||||||
- `(Response<()>, impl IntoResponse)`
|
|
||||||
- `(T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
|
||||||
- `(StatusCode, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
|
||||||
- `(Parts, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
|
||||||
- `(Response<()>, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
|
||||||
|
|
||||||
This means you cannot accidentally override the status or body as [`IntoResponseParts`] only allows
|
|
||||||
setting headers and extensions.
|
|
||||||
|
|
||||||
Use [`Response`] for more low level control:
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{
|
|
||||||
Json,
|
|
||||||
response::{IntoResponse, Response},
|
|
||||||
body::Body,
|
|
||||||
http::StatusCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
async fn response() -> Response {
|
|
||||||
Response::builder()
|
|
||||||
.status(StatusCode::NOT_FOUND)
|
|
||||||
.header("x-foo", "custom header")
|
|
||||||
.body(Body::from("not found"))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Returning different response types
|
|
||||||
|
|
||||||
If you need to return multiple response types, and `Result<T, E>` isn't appropriate, you can call
|
|
||||||
`.into_response()` to turn things into `axum::response::Response`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
response::{IntoResponse, Redirect, Response},
|
|
||||||
http::StatusCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
async fn handle() -> Response {
|
|
||||||
if something() {
|
|
||||||
"All good!".into_response()
|
|
||||||
} else if something_else() {
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"Something went wrong...",
|
|
||||||
).into_response()
|
|
||||||
} else {
|
|
||||||
Redirect::to("/").into_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn something() -> bool {
|
|
||||||
// ...
|
|
||||||
# true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn something_else() -> bool {
|
|
||||||
// ...
|
|
||||||
# true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Regarding `impl IntoResponse`
|
|
||||||
|
|
||||||
You can use `impl IntoResponse` as the return type from handlers to avoid
|
|
||||||
typing large types. For example
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::http::StatusCode;
|
|
||||||
|
|
||||||
async fn handler() -> (StatusCode, [(&'static str, &'static str); 1], &'static str) {
|
|
||||||
(StatusCode::OK, [("x-foo", "bar")], "Hello, World!")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Becomes easier using `impl IntoResponse`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{http::StatusCode, response::IntoResponse};
|
|
||||||
|
|
||||||
async fn impl_into_response() -> impl IntoResponse {
|
|
||||||
(StatusCode::OK, [("x-foo", "bar")], "Hello, World!")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
However `impl IntoResponse` has a few limitations. Firstly it can only be used
|
|
||||||
to return a single type:
|
|
||||||
|
|
||||||
```rust,compile_fail
|
|
||||||
use axum::{http::StatusCode, response::IntoResponse};
|
|
||||||
|
|
||||||
async fn handler() -> impl IntoResponse {
|
|
||||||
if check_something() {
|
|
||||||
StatusCode::NOT_FOUND
|
|
||||||
} else {
|
|
||||||
"Hello, World!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_something() -> bool {
|
|
||||||
# false
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This function returns either a `StatusCode` or a `&'static str` which `impl
|
|
||||||
Trait` doesn't allow.
|
|
||||||
|
|
||||||
Secondly `impl IntoResponse` can lead to type inference issues when used with
|
|
||||||
`Result` and `?`:
|
|
||||||
|
|
||||||
```rust,compile_fail
|
|
||||||
use axum::{http::StatusCode, response::IntoResponse};
|
|
||||||
|
|
||||||
async fn handler() -> impl IntoResponse {
|
|
||||||
create_thing()?;
|
|
||||||
Ok(StatusCode::CREATED)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_thing() -> Result<(), StatusCode> {
|
|
||||||
# Ok(())
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is because `?` supports using the [`From`] trait to convert to a different
|
|
||||||
error type but it doesn't know which type to convert to, because we only
|
|
||||||
specified `impl IntoResponse` as the return type.
|
|
||||||
|
|
||||||
`Result<impl IntoResponse, impl IntoResponse>` doesn't always work either:
|
|
||||||
|
|
||||||
```rust,compile_fail
|
|
||||||
use axum::{http::StatusCode, response::IntoResponse};
|
|
||||||
|
|
||||||
async fn handler() -> Result<impl IntoResponse, impl IntoResponse> {
|
|
||||||
create_thing()?;
|
|
||||||
Ok(StatusCode::CREATED)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_thing() -> Result<(), StatusCode> {
|
|
||||||
# Ok(())
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The solution is to use a concrete error type, such as `Result<impl IntoResponse, StatusCode>`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{http::StatusCode, response::IntoResponse};
|
|
||||||
|
|
||||||
async fn handler() -> Result<impl IntoResponse, StatusCode> {
|
|
||||||
create_thing()?;
|
|
||||||
Ok(StatusCode::CREATED)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_thing() -> Result<(), StatusCode> {
|
|
||||||
# Ok(())
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Because of this it is generally not recommended to use `impl IntoResponse`
|
|
||||||
unless you're familiar with the details of how `impl Trait` works.
|
|
||||||
|
|
||||||
[`IntoResponse`]: crate::response::IntoResponse
|
|
||||||
[`IntoResponseParts`]: crate::response::IntoResponseParts
|
|
||||||
[`StatusCode`]: http::StatusCode
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
Add a fallback [`Handler`] to the router.
|
|
||||||
|
|
||||||
This service will be called if no routes matches the incoming request.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
routing::get,
|
|
||||||
handler::Handler,
|
|
||||||
response::IntoResponse,
|
|
||||||
http::{StatusCode, Uri},
|
|
||||||
};
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/foo", get(|| async { /* ... */ }))
|
|
||||||
.fallback(fallback);
|
|
||||||
|
|
||||||
async fn fallback(uri: Uri) -> (StatusCode, String) {
|
|
||||||
(StatusCode::NOT_FOUND, format!("No route for {uri}"))
|
|
||||||
}
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
Fallbacks only apply to routes that aren't matched by anything in the
|
|
||||||
router. If a handler is matched by a request but returns 404 the
|
|
||||||
fallback is not called. Note that this applies to [`MethodRouter`]s too: if the
|
|
||||||
request hits a valid path but the [`MethodRouter`] does not have an appropriate
|
|
||||||
method handler installed, the fallback is not called (use
|
|
||||||
[`MethodRouter::fallback`] for this purpose instead).
|
|
||||||
|
|
||||||
|
|
||||||
# Handling all requests without other routes
|
|
||||||
|
|
||||||
Using `Router::new().fallback(...)` to accept all request regardless of path or
|
|
||||||
method, if you don't have other routes, isn't optimal:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::Router;
|
|
||||||
|
|
||||||
async fn handler() {}
|
|
||||||
|
|
||||||
let app = Router::new().fallback(handler);
|
|
||||||
|
|
||||||
# async {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, app).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
Running the handler directly is faster since it avoids the overhead of routing:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::handler::HandlerWithoutStateExt;
|
|
||||||
|
|
||||||
async fn handler() {}
|
|
||||||
|
|
||||||
# async {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, handler.into_make_service()).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
Convert this router 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.
|
|
||||||
|
|
||||||
Extracting [`std::net::SocketAddr`] is supported out of the box:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
extract::ConnectInfo,
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
|
|
||||||
let app = Router::new().route("/", get(handler));
|
|
||||||
|
|
||||||
async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
|
|
||||||
format!("Hello {addr}")
|
|
||||||
}
|
|
||||||
|
|
||||||
# async {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
You can implement custom a [`Connected`] like so:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
extract::connect_info::{ConnectInfo, Connected},
|
|
||||||
routing::get,
|
|
||||||
serve::IncomingStream,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use tokio::net::TcpListener;
|
|
||||||
|
|
||||||
let app = Router::new().route("/", get(handler));
|
|
||||||
|
|
||||||
async fn handler(
|
|
||||||
ConnectInfo(my_connect_info): ConnectInfo<MyConnectInfo>,
|
|
||||||
) -> String {
|
|
||||||
format!("Hello {my_connect_info:?}")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct MyConnectInfo {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connected<IncomingStream<'_, TcpListener>> for MyConnectInfo {
|
|
||||||
fn connect_info(target: IncomingStream<'_, TcpListener>) -> Self {
|
|
||||||
MyConnectInfo {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# async {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, app.into_make_service_with_connect_info::<MyConnectInfo>()).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
See the [unix domain socket example][uds] for an example of how to use
|
|
||||||
this to collect UDS connection info.
|
|
||||||
|
|
||||||
[`MakeService`]: tower::make::MakeService
|
|
||||||
[`Connected`]: crate::extract::connect_info::Connected
|
|
||||||
[`ConnectInfo`]: crate::extract::connect_info::ConnectInfo
|
|
||||||
[uds]: https://github.com/tokio-rs/axum/blob/main/examples/unix-domain-socket/src/main.rs
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
Apply a [`tower::Layer`] to all routes in the router.
|
|
||||||
|
|
||||||
This can be used to add additional processing to a request for a group
|
|
||||||
of routes.
|
|
||||||
|
|
||||||
Note that the middleware is only applied to existing routes. So you have to
|
|
||||||
first add your routes (and / or fallback) and then call `layer` afterwards. Additional
|
|
||||||
routes added after `layer` is called will not have the middleware added.
|
|
||||||
|
|
||||||
If you want to add middleware to a single handler you can either use
|
|
||||||
[`MethodRouter::layer`] or [`Handler::layer`].
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
Adding the [`tower_http::trace::TraceLayer`]:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{routing::get, Router};
|
|
||||||
use tower_http::trace::TraceLayer;
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/foo", get(|| async {}))
|
|
||||||
.route("/bar", get(|| async {}))
|
|
||||||
.layer(TraceLayer::new_for_http());
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
If you need to write your own middleware see ["Writing
|
|
||||||
middleware"](crate::middleware#writing-middleware) for the different options.
|
|
||||||
|
|
||||||
If you only want middleware on some routes you can use [`Router::merge`]:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{routing::get, Router};
|
|
||||||
use tower_http::{trace::TraceLayer, compression::CompressionLayer};
|
|
||||||
|
|
||||||
let with_tracing = Router::new()
|
|
||||||
.route("/foo", get(|| async {}))
|
|
||||||
.layer(TraceLayer::new_for_http());
|
|
||||||
|
|
||||||
let with_compression = Router::new()
|
|
||||||
.route("/bar", get(|| async {}))
|
|
||||||
.layer(CompressionLayer::new());
|
|
||||||
|
|
||||||
// Merge everything into one `Router`
|
|
||||||
let app = Router::new()
|
|
||||||
.merge(with_tracing)
|
|
||||||
.merge(with_compression);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Multiple middleware
|
|
||||||
|
|
||||||
It's recommended to use [`tower::ServiceBuilder`] when applying multiple
|
|
||||||
middleware. See [`middleware`](crate::middleware) for more details.
|
|
||||||
|
|
||||||
# Runs after routing
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
# Error handling
|
|
||||||
|
|
||||||
See [`middleware`](crate::middleware) for details on how error handling impacts
|
|
||||||
middleware.
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
Merge the paths and fallbacks of two routers into a single [`Router`].
|
|
||||||
|
|
||||||
This is useful for breaking apps into smaller pieces and combining them
|
|
||||||
into one.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
#
|
|
||||||
# async fn users_list() {}
|
|
||||||
# async fn users_show() {}
|
|
||||||
# async fn teams_list() {}
|
|
||||||
|
|
||||||
// define some routes separately
|
|
||||||
let user_routes = Router::new()
|
|
||||||
.route("/users", get(users_list))
|
|
||||||
.route("/users/{id}", get(users_show));
|
|
||||||
|
|
||||||
let team_routes = Router::new()
|
|
||||||
.route("/teams", get(teams_list));
|
|
||||||
|
|
||||||
// combine them into one
|
|
||||||
let app = Router::new()
|
|
||||||
.merge(user_routes)
|
|
||||||
.merge(team_routes);
|
|
||||||
|
|
||||||
// could also do `user_routes.merge(team_routes)`
|
|
||||||
|
|
||||||
// Our app now accepts
|
|
||||||
// - GET /users
|
|
||||||
// - GET /users/{id}
|
|
||||||
// - GET /teams
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Merging routers with state
|
|
||||||
|
|
||||||
When combining [`Router`]s with this method, each [`Router`] must have the
|
|
||||||
same type of state. If your routers have different types you can use
|
|
||||||
[`Router::with_state`] to provide the state and make the types match:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
routing::get,
|
|
||||||
extract::State,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct InnerState {}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct OuterState {}
|
|
||||||
|
|
||||||
async fn inner_handler(state: State<InnerState>) {}
|
|
||||||
|
|
||||||
let inner_router = Router::new()
|
|
||||||
.route("/bar", get(inner_handler))
|
|
||||||
.with_state(InnerState {});
|
|
||||||
|
|
||||||
async fn outer_handler(state: State<OuterState>) {}
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(outer_handler))
|
|
||||||
.merge(inner_router)
|
|
||||||
.with_state(OuterState {});
|
|
||||||
# let _: axum::Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Merging routers with fallbacks
|
|
||||||
|
|
||||||
When combining [`Router`]s with this method, the [fallback](Router::fallback) is also merged.
|
|
||||||
However only one of the routers can have a fallback.
|
|
||||||
|
|
||||||
# Panics
|
|
||||||
|
|
||||||
- If two routers that each have a [fallback](Router::fallback) are merged. This
|
|
||||||
is because `Router` only allows a single fallback.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
Add a fallback [`Handler`] for the case where a route exists, but the method of the request is not supported.
|
|
||||||
|
|
||||||
Sets a fallback on all previously registered [`MethodRouter`]s,
|
|
||||||
to be called when no matching method handler is set.
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{response::IntoResponse, routing::get, Router};
|
|
||||||
|
|
||||||
async fn hello_world() -> impl IntoResponse {
|
|
||||||
"Hello, world!\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn default_fallback() -> impl IntoResponse {
|
|
||||||
"Default fallback\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_405() -> impl IntoResponse {
|
|
||||||
"Method not allowed fallback"
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let router = Router::new()
|
|
||||||
.route("/", get(hello_world))
|
|
||||||
.fallback(default_fallback)
|
|
||||||
.method_not_allowed_fallback(handle_405);
|
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
|
|
||||||
axum::serve(listener, router).await.unwrap();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The fallback only applies if there is a `MethodRouter` registered for a given path,
|
|
||||||
but the method used in the request is not specified. In the example, a `GET` on
|
|
||||||
`http://localhost:3000` causes the `hello_world` handler to react, while issuing a
|
|
||||||
`POST` triggers `handle_405`. Calling an entirely different route, like `http://localhost:3000/hello`
|
|
||||||
causes `default_fallback` to run.
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
Nest a [`Router`] at some path.
|
|
||||||
|
|
||||||
This allows you to break your application into smaller pieces and compose
|
|
||||||
them together.
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
routing::{get, post},
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
|
|
||||||
let user_routes = Router::new().route("/{id}", get(|| async {}));
|
|
||||||
|
|
||||||
let team_routes = Router::new().route("/", post(|| async {}));
|
|
||||||
|
|
||||||
let api_routes = Router::new()
|
|
||||||
.nest("/users", user_routes)
|
|
||||||
.nest("/teams", team_routes);
|
|
||||||
|
|
||||||
let app = Router::new().nest("/api", api_routes);
|
|
||||||
|
|
||||||
// Our app now accepts
|
|
||||||
// - GET /api/users/{id}
|
|
||||||
// - POST /api/teams
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# How the URI changes
|
|
||||||
|
|
||||||
Note that nested routes will not see the original request URI but instead
|
|
||||||
have the matched prefix stripped. This is necessary for services like static
|
|
||||||
file serving to work. Use [`OriginalUri`] if you need the original request
|
|
||||||
URI.
|
|
||||||
|
|
||||||
# Captures from outer routes
|
|
||||||
|
|
||||||
Take care when using `nest` together with dynamic routes as nesting also
|
|
||||||
captures from the outer routes:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
extract::Path,
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
async fn users_get(Path(params): Path<HashMap<String, String>>) {
|
|
||||||
// Both `version` and `id` were captured even though `users_api` only
|
|
||||||
// explicitly captures `id`.
|
|
||||||
let version = params.get("version");
|
|
||||||
let id = params.get("id");
|
|
||||||
}
|
|
||||||
|
|
||||||
let users_api = Router::new().route("/users/{id}", get(users_get));
|
|
||||||
|
|
||||||
let app = Router::new().nest("/{version}/api", users_api);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Differences from wildcard routes
|
|
||||||
|
|
||||||
Nested routes are similar to wildcard routes. The difference is that
|
|
||||||
wildcard routes still see the whole URI whereas nested routes will have
|
|
||||||
the prefix stripped:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{routing::get, http::Uri, Router};
|
|
||||||
|
|
||||||
let nested_router = Router::new()
|
|
||||||
.route("/", get(|uri: Uri| async {
|
|
||||||
// `uri` will _not_ contain `/bar`
|
|
||||||
}));
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/foo/{*rest}", get(|uri: Uri| async {
|
|
||||||
// `uri` will contain `/foo`
|
|
||||||
}))
|
|
||||||
.nest("/bar", nested_router);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
Additionally, while the wildcard route `/foo/*rest` will not match the
|
|
||||||
paths `/foo` or `/foo/`, a nested router at `/foo` will match the path `/foo`
|
|
||||||
(but not `/foo/`), and a nested router at `/foo/` will match the path `/foo/`
|
|
||||||
(but not `/foo`).
|
|
||||||
|
|
||||||
# Fallbacks
|
|
||||||
|
|
||||||
If a nested router doesn't have its own fallback then it will inherit the
|
|
||||||
fallback from the outer router:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{routing::get, http::StatusCode, handler::Handler, Router};
|
|
||||||
|
|
||||||
async fn fallback() -> (StatusCode, &'static str) {
|
|
||||||
(StatusCode::NOT_FOUND, "Not Found")
|
|
||||||
}
|
|
||||||
|
|
||||||
let api_routes = Router::new().route("/users", get(|| async {}));
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.nest("/api", api_routes)
|
|
||||||
.fallback(fallback);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
Here requests like `GET /api/not-found` will go into `api_routes` but because
|
|
||||||
it doesn't have a matching route and doesn't have its own fallback it will call
|
|
||||||
the fallback from the outer router, i.e. the `fallback` function.
|
|
||||||
|
|
||||||
If the nested router has its own fallback then the outer fallback will not be
|
|
||||||
inherited:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
routing::get,
|
|
||||||
http::StatusCode,
|
|
||||||
handler::Handler,
|
|
||||||
Json,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
|
|
||||||
async fn fallback() -> (StatusCode, &'static str) {
|
|
||||||
(StatusCode::NOT_FOUND, "Not Found")
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn api_fallback() -> (StatusCode, Json<serde_json::Value>) {
|
|
||||||
(
|
|
||||||
StatusCode::NOT_FOUND,
|
|
||||||
Json(serde_json::json!({ "status": "Not Found" })),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let api_routes = Router::new()
|
|
||||||
.route("/users", get(|| async {}))
|
|
||||||
.fallback(api_fallback);
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.nest("/api", api_routes)
|
|
||||||
.fallback(fallback);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
Here requests like `GET /api/not-found` will go to `api_fallback`.
|
|
||||||
|
|
||||||
# Nesting routers with state
|
|
||||||
|
|
||||||
When combining [`Router`]s with this method, each [`Router`] must have the
|
|
||||||
same type of state. If your routers have different types you can use
|
|
||||||
[`Router::with_state`] to provide the state and make the types match:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
routing::get,
|
|
||||||
extract::State,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct InnerState {}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct OuterState {}
|
|
||||||
|
|
||||||
async fn inner_handler(state: State<InnerState>) {}
|
|
||||||
|
|
||||||
let inner_router = Router::new()
|
|
||||||
.route("/bar", get(inner_handler))
|
|
||||||
.with_state(InnerState {});
|
|
||||||
|
|
||||||
async fn outer_handler(state: State<OuterState>) {}
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(outer_handler))
|
|
||||||
.nest("/foo", inner_router)
|
|
||||||
.with_state(OuterState {});
|
|
||||||
# let _: axum::Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the inner router will still inherit the fallback from the outer
|
|
||||||
router.
|
|
||||||
|
|
||||||
# Panics
|
|
||||||
|
|
||||||
- If the route overlaps with another route. See [`Router::route`]
|
|
||||||
for more details.
|
|
||||||
- If the route contains a wildcard (`*`).
|
|
||||||
- If `path` is empty.
|
|
||||||
|
|
||||||
[`OriginalUri`]: crate::extract::OriginalUri
|
|
||||||
[fallbacks]: Router::fallback
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
Add another route to the router.
|
|
||||||
|
|
||||||
`path` is a string of path segments separated by `/`. Each segment
|
|
||||||
can be either static, a capture, or a wildcard.
|
|
||||||
|
|
||||||
`method_router` is the [`MethodRouter`] that should receive the request if the
|
|
||||||
path matches `path`. Usually, `method_router` will be a handler wrapped in a method
|
|
||||||
router like [`get`]. See [`handler`](crate::handler) for more details on handlers.
|
|
||||||
|
|
||||||
# Static paths
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- `/`
|
|
||||||
- `/foo`
|
|
||||||
- `/users/123`
|
|
||||||
|
|
||||||
If the incoming request matches the path exactly the corresponding service will
|
|
||||||
be called.
|
|
||||||
|
|
||||||
# Captures
|
|
||||||
|
|
||||||
Paths can contain segments like `/{key}` which matches any single segment and
|
|
||||||
will store the value captured at `key`. The value captured can be zero-length
|
|
||||||
except for in the invalid path `//`.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- `/{key}`
|
|
||||||
- `/users/{id}`
|
|
||||||
- `/users/{id}/tweets`
|
|
||||||
|
|
||||||
Captures can be extracted using [`Path`](crate::extract::Path). See its
|
|
||||||
documentation for more details.
|
|
||||||
|
|
||||||
It is not possible to create segments that only match some types like numbers or
|
|
||||||
regular expression. You must handle that manually in your handlers.
|
|
||||||
|
|
||||||
[`MatchedPath`] can be used to extract the matched path rather than the actual path.
|
|
||||||
|
|
||||||
# Wildcards
|
|
||||||
|
|
||||||
Paths can end in `/{*key}` which matches all segments and will store the segments
|
|
||||||
captured at `key`.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- `/{*key}`
|
|
||||||
- `/assets/{*path}`
|
|
||||||
- `/{id}/{repo}/{*tree}`
|
|
||||||
|
|
||||||
Note that `/{*key}` doesn't match empty segments. Thus:
|
|
||||||
|
|
||||||
- `/{*key}` doesn't match `/` but does match `/a`, `/a/`, etc.
|
|
||||||
- `/x/{*key}` doesn't match `/x` or `/x/` but does match `/x/a`, `/x/a/`, etc.
|
|
||||||
|
|
||||||
Wildcard captures can also be extracted using [`Path`](crate::extract::Path):
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
routing::get,
|
|
||||||
extract::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
let app: Router = Router::new().route("/{*key}", get(handler));
|
|
||||||
|
|
||||||
async fn handler(Path(path): Path<String>) -> String {
|
|
||||||
path
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the leading slash is not included, i.e. for the route `/foo/{*rest}` and
|
|
||||||
the path `/foo/bar/baz` the value of `rest` will be `bar/baz`.
|
|
||||||
|
|
||||||
# Accepting multiple methods
|
|
||||||
|
|
||||||
To accept multiple methods for the same route you can add all handlers at the
|
|
||||||
same time:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{Router, routing::{get, delete}, extract::Path};
|
|
||||||
|
|
||||||
let app = Router::new().route(
|
|
||||||
"/",
|
|
||||||
get(get_root).post(post_root).delete(delete_root),
|
|
||||||
);
|
|
||||||
|
|
||||||
async fn get_root() {}
|
|
||||||
|
|
||||||
async fn post_root() {}
|
|
||||||
|
|
||||||
async fn delete_root() {}
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
Or you can add them one by one:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# use axum::Router;
|
|
||||||
# use axum::routing::{get, post, delete};
|
|
||||||
#
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(get_root))
|
|
||||||
.route("/", post(post_root))
|
|
||||||
.route("/", delete(delete_root));
|
|
||||||
#
|
|
||||||
# let _: Router = app;
|
|
||||||
# async fn get_root() {}
|
|
||||||
# async fn post_root() {}
|
|
||||||
# async fn delete_root() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
# More examples
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{Router, routing::{get, delete}, extract::Path};
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(root))
|
|
||||||
.route("/users", get(list_users).post(create_user))
|
|
||||||
.route("/users/{id}", get(show_user))
|
|
||||||
.route("/api/{version}/users/{id}/action", delete(do_users_action))
|
|
||||||
.route("/assets/{*path}", get(serve_asset));
|
|
||||||
|
|
||||||
async fn root() {}
|
|
||||||
|
|
||||||
async fn list_users() {}
|
|
||||||
|
|
||||||
async fn create_user() {}
|
|
||||||
|
|
||||||
async fn show_user(Path(id): Path<u64>) {}
|
|
||||||
|
|
||||||
async fn do_users_action(Path((version, id)): Path<(String, u64)>) {}
|
|
||||||
|
|
||||||
async fn serve_asset(Path(path): Path<String>) {}
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
# Panics
|
|
||||||
|
|
||||||
Panics if the route overlaps with another route:
|
|
||||||
|
|
||||||
```rust,should_panic
|
|
||||||
use axum::{routing::get, Router};
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(|| async {}))
|
|
||||||
.route("/", get(|| async {}));
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
The static route `/foo` and the dynamic route `/{key}` are not considered to
|
|
||||||
overlap and `/foo` will take precedence.
|
|
||||||
|
|
||||||
Also panics if `path` is empty.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
Apply a [`tower::Layer`] to the router that will only run if the request matches
|
|
||||||
a route.
|
|
||||||
|
|
||||||
Note that the middleware is only applied to existing routes. So you have to
|
|
||||||
first add your routes (and / or fallback) and then call `route_layer`
|
|
||||||
afterwards. Additional routes added after `route_layer` is called will not have
|
|
||||||
the middleware added.
|
|
||||||
|
|
||||||
This works similarly to [`Router::layer`] except the middleware will only run if
|
|
||||||
the request matches a route. This is useful for middleware that return early
|
|
||||||
(such as authorization) which might otherwise convert a `404 Not Found` into a
|
|
||||||
`401 Unauthorized`.
|
|
||||||
|
|
||||||
This function will panic if no routes have been declared yet on the router,
|
|
||||||
since the new layer will have no effect, and this is typically a bug.
|
|
||||||
In generic code, you can test if that is the case first, by calling [`Router::has_routes`].
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use tower_http::validate_request::ValidateRequestHeaderLayer;
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/foo", get(|| async {}))
|
|
||||||
.route_layer(ValidateRequestHeaderLayer::bearer("password"));
|
|
||||||
|
|
||||||
// `GET /foo` with a valid token will receive `200 OK`
|
|
||||||
// `GET /foo` with a invalid token will receive `401 Unauthorized`
|
|
||||||
// `GET /not-found` with a invalid token will receive `404 Not Found`
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
Add another route to the router that calls a [`Service`].
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
```rust,no_run
|
|
||||||
use axum::{
|
|
||||||
Router,
|
|
||||||
body::Body,
|
|
||||||
routing::{any_service, get_service},
|
|
||||||
extract::Request,
|
|
||||||
http::StatusCode,
|
|
||||||
error_handling::HandleErrorLayer,
|
|
||||||
};
|
|
||||||
use tower_http::services::ServeFile;
|
|
||||||
use http::Response;
|
|
||||||
use std::{convert::Infallible, io};
|
|
||||||
use tower::service_fn;
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route(
|
|
||||||
// Any request to `/` goes to a service
|
|
||||||
"/",
|
|
||||||
// Services whose response body is not `axum::body::BoxBody`
|
|
||||||
// can be wrapped in `axum::routing::any_service` (or one of the other routing filters)
|
|
||||||
// to have the response body mapped
|
|
||||||
any_service(service_fn(|_: Request| async {
|
|
||||||
let res = Response::new(Body::from("Hi from `GET /`"));
|
|
||||||
Ok::<_, Infallible>(res)
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
.route_service(
|
|
||||||
"/foo",
|
|
||||||
// This service's response body is `axum::body::BoxBody` so
|
|
||||||
// it can be routed to directly.
|
|
||||||
service_fn(|req: Request| async move {
|
|
||||||
let body = Body::from(format!("Hi from `{} /foo`", req.method()));
|
|
||||||
let res = Response::new(body);
|
|
||||||
Ok::<_, Infallible>(res)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.route_service(
|
|
||||||
// GET `/static/Cargo.toml` goes to a service from tower-http
|
|
||||||
"/static/Cargo.toml",
|
|
||||||
ServeFile::new("Cargo.toml"),
|
|
||||||
);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
Routing to arbitrary services in this way has complications for backpressure
|
|
||||||
([`Service::poll_ready`]). See the [Routing to services and backpressure] module
|
|
||||||
for more details.
|
|
||||||
|
|
||||||
# Panics
|
|
||||||
|
|
||||||
Panics for the same reasons as [`Router::route`] or if you attempt to route to a
|
|
||||||
`Router`:
|
|
||||||
|
|
||||||
```rust,should_panic
|
|
||||||
use axum::{routing::get, Router};
|
|
||||||
|
|
||||||
let app = Router::new().route_service(
|
|
||||||
"/",
|
|
||||||
Router::new().route("/foo", get(|| async {})),
|
|
||||||
);
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
Use [`Router::nest`] instead.
|
|
||||||
|
|
||||||
[Routing to services and backpressure]: middleware/index.html#routing-to-servicesmiddleware-and-backpressure
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
Provide the state for the router. State passed to this method is global and will be used
|
|
||||||
for all requests this router receives. That means it is not suitable for holding state derived from a request, such as authorization data extracted in a middleware. Use [`Extension`] instead for such data.
|
|
||||||
|
|
||||||
```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 {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, routes).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
# Returning routers with states from functions
|
|
||||||
|
|
||||||
When returning `Router`s from functions, it is generally recommended not to 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 {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, routes).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, routes).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, routes).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
# 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 {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, router).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, final_router).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
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 won't 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 {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, app).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
||||||
axum::serve(listener, app).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
[`Extension`]: crate::Extension
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
Turn off checks for compatibility with route matching syntax from 0.7.
|
|
||||||
|
|
||||||
This allows usage of paths starting with a colon `:` or an asterisk `*` which are otherwise prohibited.
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use axum::{
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
|
|
||||||
let app = Router::<()>::new()
|
|
||||||
.without_v07_checks()
|
|
||||||
.route("/:colon", get(|| async {}))
|
|
||||||
.route("/*asterisk", get(|| async {}));
|
|
||||||
|
|
||||||
// Our app now accepts
|
|
||||||
// - GET /:colon
|
|
||||||
// - GET /*asterisk
|
|
||||||
# let _: Router = app;
|
|
||||||
```
|
|
||||||
|
|
||||||
Adding such routes without calling this method first will panic.
|
|
||||||
|
|
||||||
```rust,should_panic
|
|
||||||
use axum::{
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
|
|
||||||
// This panics...
|
|
||||||
let app = Router::<()>::new()
|
|
||||||
.route("/:colon", get(|| async {}));
|
|
||||||
```
|
|
||||||
|
|
||||||
# Merging
|
|
||||||
|
|
||||||
When two routers are merged, v0.7 checks are disabled for route registrations on the resulting router if both of the two routers had them also disabled.
|
|
||||||
|
|
||||||
# Nesting
|
|
||||||
|
|
||||||
Each router needs to have the checks explicitly disabled. Nesting a router with the checks either enabled or disabled has no effect on the outer router.
|
|
||||||
@@ -1,4 +1,182 @@
|
|||||||
#![doc = include_str!("../docs/error_handling.md")]
|
//! Error handling model and utilities
|
||||||
|
//!
|
||||||
|
//! # axum's error handling model
|
||||||
|
//!
|
||||||
|
//! axum is based on [`tower::Service`] which bundles errors through its associated
|
||||||
|
//! `Error` type. If you have a [`Service`] that produces an error and that error
|
||||||
|
//! makes it all the way up to hyper, the connection will be terminated _without_
|
||||||
|
//! sending a response. This is generally not desirable so axum makes sure you
|
||||||
|
//! always produce a response by relying on the type system.
|
||||||
|
//!
|
||||||
|
//! axum does this by requiring all services have [`Infallible`] as their error
|
||||||
|
//! type. `Infallible` is the error type for errors that can never happen.
|
||||||
|
//!
|
||||||
|
//! This means if you define a handler like:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::http::StatusCode;
|
||||||
|
//!
|
||||||
|
//! async fn handler() -> Result<String, StatusCode> {
|
||||||
|
//! # todo!()
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! While it looks like it might fail with a `StatusCode` this actually isn't an
|
||||||
|
//! "error". If this handler returns `Err(some_status_code)` that will still be
|
||||||
|
//! converted into a [`Response`] and sent back to the client. This is done
|
||||||
|
//! through `StatusCode`'s [`IntoResponse`] implementation.
|
||||||
|
//!
|
||||||
|
//! It doesn't matter whether you return `Err(StatusCode::NOT_FOUND)` or
|
||||||
|
//! `Err(StatusCode::INTERNAL_SERVER_ERROR)`. These are not considered errors in
|
||||||
|
//! axum.
|
||||||
|
//!
|
||||||
|
//! Instead of a direct `StatusCode`, it makes sense to use intermediate error type
|
||||||
|
//! that can ultimately be converted to `Response`. This allows using `?` operator
|
||||||
|
//! in handlers. See those examples:
|
||||||
|
//!
|
||||||
|
//! * [`anyhow-error-response`][anyhow] for generic boxed errors
|
||||||
|
//! * [`error-handling`][error-handling] for application-specific detailed errors
|
||||||
|
//!
|
||||||
|
//! [anyhow]: https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs
|
||||||
|
//! [error-handling]: https://github.com/tokio-rs/axum/blob/main/examples/error-handling/src/main.rs
|
||||||
|
//!
|
||||||
|
//! This also applies to extractors. If an extractor doesn't match the request the
|
||||||
|
//! request will be rejected and a response will be returned without calling your
|
||||||
|
//! handler. See [`extract`](crate::extract) to learn more about handling extractor
|
||||||
|
//! failures.
|
||||||
|
//!
|
||||||
|
//! # Routing to fallible services
|
||||||
|
//!
|
||||||
|
//! You generally don't have to think about errors if you're only using async
|
||||||
|
//! functions as handlers. However if you're embedding general `Service`s or
|
||||||
|
//! applying middleware, which might produce errors you have to tell axum how to
|
||||||
|
//! convert those errors into responses.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{
|
||||||
|
//! Router,
|
||||||
|
//! body::Body,
|
||||||
|
//! http::{Request, Response, StatusCode},
|
||||||
|
//! error_handling::HandleError,
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! async fn thing_that_might_fail() -> Result<(), anyhow::Error> {
|
||||||
|
//! # Ok(())
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // this service might fail with `anyhow::Error`
|
||||||
|
//! let some_fallible_service = tower::service_fn(|_req| async {
|
||||||
|
//! thing_that_might_fail().await?;
|
||||||
|
//! Ok::<_, anyhow::Error>(Response::new(Body::empty()))
|
||||||
|
//! });
|
||||||
|
//!
|
||||||
|
//! let app = Router::new().route_service(
|
||||||
|
//! "/",
|
||||||
|
//! // we cannot route to `some_fallible_service` directly since it might fail.
|
||||||
|
//! // we have to use `handle_error` which converts its errors into responses
|
||||||
|
//! // and changes its error type from `anyhow::Error` to `Infallible`.
|
||||||
|
//! HandleError::new(some_fallible_service, handle_anyhow_error),
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! // handle errors by converting them into something that implements
|
||||||
|
//! // `IntoResponse`
|
||||||
|
//! async fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
|
||||||
|
//! (
|
||||||
|
//! StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
//! format!("Something went wrong: {err}"),
|
||||||
|
//! )
|
||||||
|
//! }
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Applying fallible middleware
|
||||||
|
//!
|
||||||
|
//! Similarly axum requires you to handle errors from middleware. That is done with
|
||||||
|
//! [`HandleErrorLayer`]:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{
|
||||||
|
//! Router,
|
||||||
|
//! BoxError,
|
||||||
|
//! routing::get,
|
||||||
|
//! http::StatusCode,
|
||||||
|
//! error_handling::HandleErrorLayer,
|
||||||
|
//! };
|
||||||
|
//! use std::time::Duration;
|
||||||
|
//! use tower::ServiceBuilder;
|
||||||
|
//!
|
||||||
|
//! let app = Router::new()
|
||||||
|
//! .route("/", get(|| async {}))
|
||||||
|
//! .layer(
|
||||||
|
//! ServiceBuilder::new()
|
||||||
|
//! // `timeout` will produce an error if the handler takes
|
||||||
|
//! // too long so we must handle those
|
||||||
|
//! .layer(HandleErrorLayer::new(handle_timeout_error))
|
||||||
|
//! .timeout(Duration::from_secs(30))
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! async fn handle_timeout_error(err: BoxError) -> (StatusCode, String) {
|
||||||
|
//! if err.is::<tower::timeout::error::Elapsed>() {
|
||||||
|
//! (
|
||||||
|
//! StatusCode::REQUEST_TIMEOUT,
|
||||||
|
//! "Request took too long".to_string(),
|
||||||
|
//! )
|
||||||
|
//! } else {
|
||||||
|
//! (
|
||||||
|
//! StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
//! format!("Unhandled internal error: {err}"),
|
||||||
|
//! )
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Running extractors for error handling
|
||||||
|
//!
|
||||||
|
//! `HandleErrorLayer` also supports running extractors:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{
|
||||||
|
//! Router,
|
||||||
|
//! BoxError,
|
||||||
|
//! routing::get,
|
||||||
|
//! http::{StatusCode, Method, Uri},
|
||||||
|
//! error_handling::HandleErrorLayer,
|
||||||
|
//! };
|
||||||
|
//! use std::time::Duration;
|
||||||
|
//! use tower::ServiceBuilder;
|
||||||
|
//!
|
||||||
|
//! let app = Router::new()
|
||||||
|
//! .route("/", get(|| async {}))
|
||||||
|
//! .layer(
|
||||||
|
//! ServiceBuilder::new()
|
||||||
|
//! // `timeout` will produce an error if the handler takes
|
||||||
|
//! // too long so we must handle those
|
||||||
|
//! .layer(HandleErrorLayer::new(handle_timeout_error))
|
||||||
|
//! .timeout(Duration::from_secs(30))
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! async fn handle_timeout_error(
|
||||||
|
//! // `Method` and `Uri` are extractors so they can be used here
|
||||||
|
//! method: Method,
|
||||||
|
//! uri: Uri,
|
||||||
|
//! // the last argument must be the error itself
|
||||||
|
//! err: BoxError,
|
||||||
|
//! ) -> (StatusCode, String) {
|
||||||
|
//! (
|
||||||
|
//! StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
//! format!("`{method} {uri}` failed with {err}"),
|
||||||
|
//! )
|
||||||
|
//! }
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [`tower::Service`]: `tower::Service`
|
||||||
|
//! [`Infallible`]: std::convert::Infallible
|
||||||
|
//! [`Response`]: crate::response::Response
|
||||||
|
//! [`IntoResponse`]: crate::response::IntoResponse
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
extract::FromRequestParts,
|
extract::FromRequestParts,
|
||||||
|
|||||||
+689
-1
@@ -1,4 +1,692 @@
|
|||||||
#![doc = include_str!("../docs/extract.md")]
|
//! Types and traits for extracting data from requests.
|
||||||
|
//!
|
||||||
|
//! # Intro
|
||||||
|
//!
|
||||||
|
//! A handler function is an async function that takes any number of
|
||||||
|
//! "extractors" as arguments. An extractor is a type that implements
|
||||||
|
//! [`FromRequest`] or [`FromRequestParts`].
|
||||||
|
//!
|
||||||
|
//! For example, [`Json`] is an extractor that consumes the request body and
|
||||||
|
//! deserializes it as JSON into some target type:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use axum::{
|
||||||
|
//! extract::Json,
|
||||||
|
//! routing::post,
|
||||||
|
//! handler::Handler,
|
||||||
|
//! Router,
|
||||||
|
//! };
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//!
|
||||||
|
//! #[derive(Deserialize)]
|
||||||
|
//! struct CreateUser {
|
||||||
|
//! email: String,
|
||||||
|
//! password: String,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn create_user(Json(payload): Json<CreateUser>) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! let app = Router::new().route("/users", post(create_user));
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Common extractors
|
||||||
|
//!
|
||||||
|
//! Some commonly used extractors are:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use axum::{
|
||||||
|
//! extract::{Request, Json, Path, Extension, Query},
|
||||||
|
//! routing::post,
|
||||||
|
//! http::header::HeaderMap,
|
||||||
|
//! body::{Bytes, Body},
|
||||||
|
//! Router,
|
||||||
|
//! };
|
||||||
|
//! use serde_json::Value;
|
||||||
|
//! use std::collections::HashMap;
|
||||||
|
//!
|
||||||
|
//! // `Path` gives you the path parameters and deserializes them. See its docs for
|
||||||
|
//! // more details
|
||||||
|
//! async fn path(Path(user_id): Path<u32>) {}
|
||||||
|
//!
|
||||||
|
//! // `Query` gives you the query parameters and deserializes them.
|
||||||
|
//! async fn query(Query(params): Query<HashMap<String, String>>) {}
|
||||||
|
//!
|
||||||
|
//! // `HeaderMap` gives you all the headers
|
||||||
|
//! async fn headers(headers: HeaderMap) {}
|
||||||
|
//!
|
||||||
|
//! // `String` consumes the request body and ensures it is valid utf-8
|
||||||
|
//! async fn string(body: String) {}
|
||||||
|
//!
|
||||||
|
//! // `Bytes` gives you the raw request body
|
||||||
|
//! async fn bytes(body: Bytes) {}
|
||||||
|
//!
|
||||||
|
//! // We've already seen `Json` for parsing the request body as json
|
||||||
|
//! async fn json(Json(payload): Json<Value>) {}
|
||||||
|
//!
|
||||||
|
//! // `Request` gives you the whole request for maximum control
|
||||||
|
//! async fn request(request: Request) {}
|
||||||
|
//!
|
||||||
|
//! // `Extension` extracts data from "request extensions"
|
||||||
|
//! // This is commonly used to share state with handlers
|
||||||
|
//! async fn extension(Extension(state): Extension<State>) {}
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! struct State { /* ... */ }
|
||||||
|
//!
|
||||||
|
//! let app = Router::new()
|
||||||
|
//! .route("/path/{user_id}", post(path))
|
||||||
|
//! .route("/query", post(query))
|
||||||
|
//! .route("/string", post(string))
|
||||||
|
//! .route("/bytes", post(bytes))
|
||||||
|
//! .route("/json", post(json))
|
||||||
|
//! .route("/request", post(request))
|
||||||
|
//! .route("/extension", post(extension));
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Applying multiple extractors
|
||||||
|
//!
|
||||||
|
//! You can also apply multiple extractors:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use axum::{
|
||||||
|
//! extract::{Path, Query},
|
||||||
|
//! routing::get,
|
||||||
|
//! Router,
|
||||||
|
//! };
|
||||||
|
//! use uuid::Uuid;
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//!
|
||||||
|
//! let app = Router::new().route("/users/{id}/things", get(get_user_things));
|
||||||
|
//!
|
||||||
|
//! #[derive(Deserialize)]
|
||||||
|
//! struct Pagination {
|
||||||
|
//! page: usize,
|
||||||
|
//! per_page: usize,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn get_user_things(
|
||||||
|
//! Path(user_id): Path<Uuid>,
|
||||||
|
//! Query(pagination): Query<Pagination>,
|
||||||
|
//! ) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # The order of extractors
|
||||||
|
//!
|
||||||
|
//! Extractors always run in the order of the function parameters that is from
|
||||||
|
//! left to right.
|
||||||
|
//!
|
||||||
|
//! The request body is an asynchronous stream that can only be consumed once.
|
||||||
|
//! Therefore you can only have one extractor that consumes the request body. axum
|
||||||
|
//! enforces this by requiring such extractors to be the _last_ argument your
|
||||||
|
//! handler takes.
|
||||||
|
//!
|
||||||
|
//! For example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{extract::State, http::{Method, HeaderMap}};
|
||||||
|
//! #
|
||||||
|
//! # #[derive(Clone)]
|
||||||
|
//! # struct AppState {
|
||||||
|
//! # }
|
||||||
|
//!
|
||||||
|
//! async fn handler(
|
||||||
|
//! // `Method` and `HeaderMap` don't consume the request body so they can
|
||||||
|
//! // put anywhere in the argument list (but before `body`)
|
||||||
|
//! method: Method,
|
||||||
|
//! headers: HeaderMap,
|
||||||
|
//! // `State` is also an extractor so it needs to be before `body`
|
||||||
|
//! State(state): State<AppState>,
|
||||||
|
//! // `String` consumes the request body and thus must be the last extractor
|
||||||
|
//! body: String,
|
||||||
|
//! ) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! #
|
||||||
|
//! # let _: axum::routing::MethodRouter<AppState> = axum::routing::get(handler);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! We get a compile error if `String` isn't the last extractor:
|
||||||
|
//!
|
||||||
|
//! ```rust,compile_fail
|
||||||
|
//! use axum::http::Method;
|
||||||
|
//!
|
||||||
|
//! async fn handler(
|
||||||
|
//! // this doesn't work since `String` must be the last argument
|
||||||
|
//! body: String,
|
||||||
|
//! method: Method,
|
||||||
|
//! ) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! #
|
||||||
|
//! # let _: axum::routing::MethodRouter = axum::routing::get(handler);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! This also means you cannot consume the request body twice:
|
||||||
|
//!
|
||||||
|
//! ```rust,compile_fail
|
||||||
|
//! use axum::Json;
|
||||||
|
//! use serde::Deserialize;
|
||||||
|
//!
|
||||||
|
//! #[derive(Deserialize)]
|
||||||
|
//! struct Payload {}
|
||||||
|
//!
|
||||||
|
//! async fn handler(
|
||||||
|
//! // `String` and `Json` both consume the request body
|
||||||
|
//! // so they cannot both be used
|
||||||
|
//! string_body: String,
|
||||||
|
//! json_body: Json<Payload>,
|
||||||
|
//! ) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! #
|
||||||
|
//! # let _: axum::routing::MethodRouter = axum::routing::get(handler);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! axum enforces this by requiring the last extractor implements [`FromRequest`]
|
||||||
|
//! and all others implement [`FromRequestParts`].
|
||||||
|
//!
|
||||||
|
//! # Handling extractor rejections
|
||||||
|
//!
|
||||||
|
//! If you want to handle the case of an extractor failing within a specific
|
||||||
|
//! handler, you can wrap it in `Result`, with the error being the rejection type
|
||||||
|
//! of the extractor:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use axum::{
|
||||||
|
//! extract::{Json, rejection::JsonRejection},
|
||||||
|
//! routing::post,
|
||||||
|
//! Router,
|
||||||
|
//! };
|
||||||
|
//! use serde_json::Value;
|
||||||
|
//!
|
||||||
|
//! async fn create_user(payload: Result<Json<Value>, JsonRejection>) {
|
||||||
|
//! match payload {
|
||||||
|
//! Ok(payload) => {
|
||||||
|
//! // We got a valid JSON payload
|
||||||
|
//! }
|
||||||
|
//! Err(JsonRejection::MissingJsonContentType(_)) => {
|
||||||
|
//! // Request didn't have `Content-Type: application/json`
|
||||||
|
//! // header
|
||||||
|
//! }
|
||||||
|
//! Err(JsonRejection::JsonDataError(_)) => {
|
||||||
|
//! // Couldn't deserialize the body into the target type
|
||||||
|
//! }
|
||||||
|
//! Err(JsonRejection::JsonSyntaxError(_)) => {
|
||||||
|
//! // Syntax error in the body
|
||||||
|
//! }
|
||||||
|
//! Err(JsonRejection::BytesRejection(_)) => {
|
||||||
|
//! // Failed to extract the request body
|
||||||
|
//! }
|
||||||
|
//! Err(_) => {
|
||||||
|
//! // `JsonRejection` is marked `#[non_exhaustive]` so match must
|
||||||
|
//! // include a catch-all case.
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! let app = Router::new().route("/users", post(create_user));
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Optional extractors
|
||||||
|
//!
|
||||||
|
//! Some extractors implement [`OptionalFromRequestParts`] in addition to
|
||||||
|
//! [`FromRequestParts`], or [`OptionalFromRequest`] in addition to [`FromRequest`].
|
||||||
|
//!
|
||||||
|
//! These extractors can be used inside of `Option`. It depends on the particular
|
||||||
|
//! `OptionalFromRequestParts` or `OptionalFromRequest` implementation what this
|
||||||
|
//! does: For example for `TypedHeader` from axum-extra, you get `None` if the
|
||||||
|
//! header you're trying to extract is not part of the request, but if the header
|
||||||
|
//! is present and fails to parse, the request is rejected.
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use axum::{routing::post, Router};
|
||||||
|
//! use axum_extra::{headers::UserAgent, TypedHeader};
|
||||||
|
//! use serde_json::Value;
|
||||||
|
//!
|
||||||
|
//! async fn foo(user_agent: Option<TypedHeader<UserAgent>>) {
|
||||||
|
//! if let Some(TypedHeader(user_agent)) = user_agent {
|
||||||
|
//! // The client sent a user agent
|
||||||
|
//! } else {
|
||||||
|
//! // No user agent header
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! let app = Router::new().route("/foo", post(foo));
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Customizing extractor responses
|
||||||
|
//!
|
||||||
|
//! If an extractor fails it will return a response with the error and your
|
||||||
|
//! handler will not be called. To customize the error response you have two
|
||||||
|
//! options:
|
||||||
|
//!
|
||||||
|
//! 1. Use `Result<T, T::Rejection>` as your extractor like shown in
|
||||||
|
//! ["Handling extractor rejections"](#handling-extractor-rejections).
|
||||||
|
//! This works well if you're only using the extractor in a single handler.
|
||||||
|
//! 2. Create your own extractor that in its [`FromRequest`] implementation calls
|
||||||
|
//! one of axum's built in extractors but returns a different response for
|
||||||
|
//! rejections. See the [customize-extractor-error] example for more details.
|
||||||
|
//!
|
||||||
|
//! # Accessing inner errors
|
||||||
|
//!
|
||||||
|
//! axum's built-in extractors don't directly expose the inner error. This gives us
|
||||||
|
//! more flexibility and allows us to change internal implementations without
|
||||||
|
//! breaking the public API.
|
||||||
|
//!
|
||||||
|
//! For example that means while [`Json`] is implemented using [`serde_json`] it
|
||||||
|
//! doesn't directly expose the [`serde_json::Error`] that's contained in
|
||||||
|
//! [`JsonRejection::JsonDataError`]. However it is still possible to access via
|
||||||
|
//! methods from [`std::error::Error`]:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use std::error::Error;
|
||||||
|
//! use axum::{
|
||||||
|
//! extract::{Json, rejection::JsonRejection},
|
||||||
|
//! response::IntoResponse,
|
||||||
|
//! http::StatusCode,
|
||||||
|
//! };
|
||||||
|
//! use serde_json::{json, Value};
|
||||||
|
//!
|
||||||
|
//! async fn handler(
|
||||||
|
//! result: Result<Json<Value>, JsonRejection>,
|
||||||
|
//! ) -> Result<Json<Value>, (StatusCode, String)> {
|
||||||
|
//! match result {
|
||||||
|
//! // if the client sent valid JSON then we're good
|
||||||
|
//! Ok(Json(payload)) => Ok(Json(json!({ "payload": payload }))),
|
||||||
|
//!
|
||||||
|
//! Err(err) => match err {
|
||||||
|
//! JsonRejection::JsonDataError(err) => {
|
||||||
|
//! Err(serde_json_error_response(err))
|
||||||
|
//! }
|
||||||
|
//! JsonRejection::JsonSyntaxError(err) => {
|
||||||
|
//! Err(serde_json_error_response(err))
|
||||||
|
//! }
|
||||||
|
//! // handle other rejections from the `Json` extractor
|
||||||
|
//! JsonRejection::MissingJsonContentType(_) => Err((
|
||||||
|
//! StatusCode::BAD_REQUEST,
|
||||||
|
//! "Missing `Content-Type: application/json` header".to_string(),
|
||||||
|
//! )),
|
||||||
|
//! JsonRejection::BytesRejection(_) => Err((
|
||||||
|
//! StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
//! "Failed to buffer request body".to_string(),
|
||||||
|
//! )),
|
||||||
|
//! // we must provide a catch-all case since `JsonRejection` is marked
|
||||||
|
//! // `#[non_exhaustive]`
|
||||||
|
//! _ => Err((
|
||||||
|
//! StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
//! "Unknown error".to_string(),
|
||||||
|
//! )),
|
||||||
|
//! },
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // attempt to extract the inner `serde_path_to_error::Error<serde_json::Error>`,
|
||||||
|
//! // if that succeeds we can provide a more specific error.
|
||||||
|
//! //
|
||||||
|
//! // `Json` uses `serde_path_to_error` so the error will be wrapped in `serde_path_to_error::Error`.
|
||||||
|
//! fn serde_json_error_response<E>(err: E) -> (StatusCode, String)
|
||||||
|
//! where
|
||||||
|
//! E: Error + 'static,
|
||||||
|
//! {
|
||||||
|
//! if let Some(err) = find_error_source::<serde_path_to_error::Error<serde_json::Error>>(&err) {
|
||||||
|
//! let serde_json_err = err.inner();
|
||||||
|
//! (
|
||||||
|
//! StatusCode::BAD_REQUEST,
|
||||||
|
//! format!(
|
||||||
|
//! "Invalid JSON at line {} column {}",
|
||||||
|
//! serde_json_err.line(),
|
||||||
|
//! serde_json_err.column()
|
||||||
|
//! ),
|
||||||
|
//! )
|
||||||
|
//! } else {
|
||||||
|
//! (StatusCode::BAD_REQUEST, "Unknown error".to_string())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // attempt to downcast `err` into a `T` and if that fails recursively try and
|
||||||
|
//! // downcast `err`'s source
|
||||||
|
//! fn find_error_source<'a, T>(err: &'a (dyn Error + 'static)) -> Option<&'a T>
|
||||||
|
//! where
|
||||||
|
//! T: Error + 'static,
|
||||||
|
//! {
|
||||||
|
//! if let Some(err) = err.downcast_ref::<T>() {
|
||||||
|
//! Some(err)
|
||||||
|
//! } else if let Some(source) = err.source() {
|
||||||
|
//! find_error_source(source)
|
||||||
|
//! } else {
|
||||||
|
//! None
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! #
|
||||||
|
//! # #[tokio::main]
|
||||||
|
//! # async fn main() {
|
||||||
|
//! # use axum::extract::FromRequest;
|
||||||
|
//! #
|
||||||
|
//! # let req = axum::http::Request::builder()
|
||||||
|
//! # .header("content-type", "application/json")
|
||||||
|
//! # .body(axum::body::Body::from("{"))
|
||||||
|
//! # .unwrap();
|
||||||
|
//! #
|
||||||
|
//! # let err = match Json::<serde_json::Value>::from_request(req, &()).await.unwrap_err() {
|
||||||
|
//! # JsonRejection::JsonSyntaxError(err) => err,
|
||||||
|
//! # _ => panic!(),
|
||||||
|
//! # };
|
||||||
|
//! #
|
||||||
|
//! # let (_, body) = serde_json_error_response(err);
|
||||||
|
//! # assert_eq!(body, "Invalid JSON at line 1 column 1");
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Note that while this approach works it might break in the future if axum changes
|
||||||
|
//! its implementation to use a different error type internally. Such changes might
|
||||||
|
//! happen without major breaking versions.
|
||||||
|
//!
|
||||||
|
//! # Defining custom extractors
|
||||||
|
//!
|
||||||
|
//! You can also define your own extractors by implementing either
|
||||||
|
//! [`FromRequestParts`] or [`FromRequest`].
|
||||||
|
//!
|
||||||
|
//! ## Implementing `FromRequestParts`
|
||||||
|
//!
|
||||||
|
//! Implement `FromRequestParts` if your extractor doesn't need access to the
|
||||||
|
//! request body:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use axum::{
|
||||||
|
//! extract::FromRequestParts,
|
||||||
|
//! routing::get,
|
||||||
|
//! Router,
|
||||||
|
//! http::{
|
||||||
|
//! StatusCode,
|
||||||
|
//! header::{HeaderValue, USER_AGENT},
|
||||||
|
//! request::Parts,
|
||||||
|
//! },
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! struct ExtractUserAgent(HeaderValue);
|
||||||
|
//!
|
||||||
|
//! impl<S> FromRequestParts<S> for ExtractUserAgent
|
||||||
|
//! where
|
||||||
|
//! S: Send + Sync,
|
||||||
|
//! {
|
||||||
|
//! type Rejection = (StatusCode, &'static str);
|
||||||
|
//!
|
||||||
|
//! async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
//! if let Some(user_agent) = parts.headers.get(USER_AGENT) {
|
||||||
|
//! Ok(ExtractUserAgent(user_agent.clone()))
|
||||||
|
//! } else {
|
||||||
|
//! Err((StatusCode::BAD_REQUEST, "`User-Agent` header is missing"))
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn handler(ExtractUserAgent(user_agent): ExtractUserAgent) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! let app = Router::new().route("/foo", get(handler));
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Implementing `FromRequest`
|
||||||
|
//!
|
||||||
|
//! If your extractor needs to consume the request body you must implement [`FromRequest`]
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use axum::{
|
||||||
|
//! extract::{Request, FromRequest},
|
||||||
|
//! response::{Response, IntoResponse},
|
||||||
|
//! body::{Bytes, Body},
|
||||||
|
//! routing::get,
|
||||||
|
//! Router,
|
||||||
|
//! http::{
|
||||||
|
//! StatusCode,
|
||||||
|
//! header::{HeaderValue, USER_AGENT},
|
||||||
|
//! },
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! struct ValidatedBody(Bytes);
|
||||||
|
//!
|
||||||
|
//! impl<S> FromRequest<S> for ValidatedBody
|
||||||
|
//! where
|
||||||
|
//! Bytes: FromRequest<S>,
|
||||||
|
//! S: Send + Sync,
|
||||||
|
//! {
|
||||||
|
//! type Rejection = Response;
|
||||||
|
//!
|
||||||
|
//! async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
//! let body = Bytes::from_request(req, state)
|
||||||
|
//! .await
|
||||||
|
//! .map_err(IntoResponse::into_response)?;
|
||||||
|
//!
|
||||||
|
//! // do validation...
|
||||||
|
//!
|
||||||
|
//! Ok(Self(body))
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn handler(ValidatedBody(body): ValidatedBody) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! let app = Router::new().route("/foo", get(handler));
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Cannot implement both `FromRequest` and `FromRequestParts`
|
||||||
|
//!
|
||||||
|
//! Note that you will make your extractor unusable by implementing both
|
||||||
|
//! `FromRequest` and `FromRequestParts` directly for the same type, unless it is
|
||||||
|
//! wrapping another extractor:
|
||||||
|
//!
|
||||||
|
//! ```rust,compile_fail
|
||||||
|
//! use axum::{
|
||||||
|
//! Router,
|
||||||
|
//! routing::get,
|
||||||
|
//! extract::{FromRequest, Request, FromRequestParts},
|
||||||
|
//! http::request::Parts,
|
||||||
|
//! body::Body,
|
||||||
|
//! };
|
||||||
|
//! use std::convert::Infallible;
|
||||||
|
//!
|
||||||
|
//! // Some extractor that doesn't wrap another extractor
|
||||||
|
//! struct MyExtractor;
|
||||||
|
//!
|
||||||
|
//! // `MyExtractor` implements both `FromRequest`
|
||||||
|
//! impl<S> FromRequest<S> for MyExtractor
|
||||||
|
//! where
|
||||||
|
//! S: Send + Sync,
|
||||||
|
//! {
|
||||||
|
//! type Rejection = Infallible;
|
||||||
|
//!
|
||||||
|
//! async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
//! // ...
|
||||||
|
//! # todo!()
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // and `FromRequestParts`
|
||||||
|
//! impl<S> FromRequestParts<S> for MyExtractor
|
||||||
|
//! where
|
||||||
|
//! S: Send + Sync,
|
||||||
|
//! {
|
||||||
|
//! type Rejection = Infallible;
|
||||||
|
//!
|
||||||
|
//! async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
//! // ...
|
||||||
|
//! # todo!()
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! let app = Router::new().route(
|
||||||
|
//! "/",
|
||||||
|
//! // This fails when we go to actually use `MyExtractor` in a handler function.
|
||||||
|
//! // This is due to a limit in Rust's type system.
|
||||||
|
//! //
|
||||||
|
//! // The workaround is to implement either `FromRequest` or `FromRequestParts`
|
||||||
|
//! // but not both, if your extractor doesn't wrap another extractor.
|
||||||
|
//! //
|
||||||
|
//! // See "Wrapping extractors" for how to wrap other extractors.
|
||||||
|
//! get(|_: MyExtractor| async {}),
|
||||||
|
//! );
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Accessing other extractors in `FromRequest` or `FromRequestParts` implementations
|
||||||
|
//!
|
||||||
|
//! When defining custom extractors you often need to access another extractor
|
||||||
|
//! in your implementation.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{
|
||||||
|
//! extract::{Extension, FromRequestParts},
|
||||||
|
//! http::{StatusCode, HeaderMap, request::Parts},
|
||||||
|
//! response::{IntoResponse, Response},
|
||||||
|
//! routing::get,
|
||||||
|
//! Router,
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! struct State {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! struct AuthenticatedUser {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl<S> FromRequestParts<S> for AuthenticatedUser
|
||||||
|
//! where
|
||||||
|
//! S: Send + Sync,
|
||||||
|
//! {
|
||||||
|
//! type Rejection = Response;
|
||||||
|
//!
|
||||||
|
//! async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
//! // You can either call them directly...
|
||||||
|
//! let headers = HeaderMap::from_request_parts(parts, state)
|
||||||
|
//! .await
|
||||||
|
//! .map_err(|err| match err {})?;
|
||||||
|
//!
|
||||||
|
//! // ... or use `extract` / `extract_with_state` from `RequestExt` / `RequestPartsExt`
|
||||||
|
//! use axum::RequestPartsExt;
|
||||||
|
//! let Extension(state) = parts.extract::<Extension<State>>()
|
||||||
|
//! .await
|
||||||
|
//! .map_err(|err| err.into_response())?;
|
||||||
|
//!
|
||||||
|
//! unimplemented!("actually perform the authorization")
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn handler(user: AuthenticatedUser) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! let state = State { /* ... */ };
|
||||||
|
//!
|
||||||
|
//! let app = Router::new().route("/", get(handler)).layer(Extension(state));
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Request body limits
|
||||||
|
//!
|
||||||
|
//! For security reasons, [`Bytes`] will, by default, not accept bodies larger than
|
||||||
|
//! 2MB. This also applies to extractors that uses [`Bytes`] internally such as
|
||||||
|
//! `String`, [`Json`], and [`Form`].
|
||||||
|
//!
|
||||||
|
//! For more details, including how to disable this limit, see [`DefaultBodyLimit`].
|
||||||
|
//!
|
||||||
|
//! # Wrapping extractors
|
||||||
|
//!
|
||||||
|
//! If you want write an extractor that generically wraps another extractor (that
|
||||||
|
//! may or may not consume the request body) you should implement both
|
||||||
|
//! [`FromRequest`] and [`FromRequestParts`]:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{
|
||||||
|
//! Router,
|
||||||
|
//! body::Body,
|
||||||
|
//! routing::get,
|
||||||
|
//! extract::{Request, FromRequest, FromRequestParts},
|
||||||
|
//! http::{HeaderMap, request::Parts},
|
||||||
|
//! };
|
||||||
|
//! use std::time::{Instant, Duration};
|
||||||
|
//!
|
||||||
|
//! // an extractor that wraps another and measures how long time it takes to run
|
||||||
|
//! struct Timing<E> {
|
||||||
|
//! extractor: E,
|
||||||
|
//! duration: Duration,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // we must implement both `FromRequestParts`
|
||||||
|
//! impl<S, T> FromRequestParts<S> for Timing<T>
|
||||||
|
//! where
|
||||||
|
//! S: Send + Sync,
|
||||||
|
//! T: FromRequestParts<S>,
|
||||||
|
//! {
|
||||||
|
//! type Rejection = T::Rejection;
|
||||||
|
//!
|
||||||
|
//! async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
//! let start = Instant::now();
|
||||||
|
//! let extractor = T::from_request_parts(parts, state).await?;
|
||||||
|
//! let duration = start.elapsed();
|
||||||
|
//! Ok(Timing {
|
||||||
|
//! extractor,
|
||||||
|
//! duration,
|
||||||
|
//! })
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // and `FromRequest`
|
||||||
|
//! impl<S, T> FromRequest<S> for Timing<T>
|
||||||
|
//! where
|
||||||
|
//! S: Send + Sync,
|
||||||
|
//! T: FromRequest<S>,
|
||||||
|
//! {
|
||||||
|
//! type Rejection = T::Rejection;
|
||||||
|
//!
|
||||||
|
//! async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
//! let start = Instant::now();
|
||||||
|
//! let extractor = T::from_request(req, state).await?;
|
||||||
|
//! let duration = start.elapsed();
|
||||||
|
//! Ok(Timing {
|
||||||
|
//! extractor,
|
||||||
|
//! duration,
|
||||||
|
//! })
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn handler(
|
||||||
|
//! // this uses the `FromRequestParts` impl
|
||||||
|
//! _: Timing<HeaderMap>,
|
||||||
|
//! // this uses the `FromRequest` impl
|
||||||
|
//! _: Timing<String>,
|
||||||
|
//! ) {}
|
||||||
|
//! # let _: axum::routing::MethodRouter = axum::routing::get(handler);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Logging rejections
|
||||||
|
//!
|
||||||
|
//! All built-in extractors will log rejections for easier debugging. To see the
|
||||||
|
//! logs, enable the `tracing` feature for axum (enabled by default) and the
|
||||||
|
//! `axum::rejection=trace` tracing target, for example with
|
||||||
|
//! `RUST_LOG=info,axum::rejection=trace cargo run`.
|
||||||
|
//!
|
||||||
|
//! [axum-extra]: https://docs.rs/axum-extra/latest/axum_extra/extract/index.html
|
||||||
|
//! [`body::Body`]: crate::body::Body
|
||||||
|
//! [`Bytes`]: crate::body::Bytes
|
||||||
|
//! [customize-extractor-error]: https://github.com/tokio-rs/axum/blob/main/examples/customize-extractor-error/src/main.rs
|
||||||
|
//! [`HeaderMap`]: https://docs.rs/http/latest/http/header/struct.HeaderMap.html
|
||||||
|
//! [`Request`]: https://docs.rs/http/latest/http/struct.Request.html
|
||||||
|
//! [`JsonRejection::JsonDataError`]: rejection::JsonRejection::JsonDataError
|
||||||
|
|
||||||
use http::header::{self, HeaderMap};
|
use http::header::{self, HeaderMap};
|
||||||
|
|
||||||
|
|||||||
+85
-3
@@ -1,6 +1,11 @@
|
|||||||
//! Async functions that can be used to handle requests.
|
//! Async functions that can be used to handle requests.
|
||||||
//!
|
//!
|
||||||
#![doc = include_str!("../docs/handlers_intro.md")]
|
//! In axum a "handler" is an async function that accepts zero or more
|
||||||
|
//! ["extractors"](crate::extract) as arguments and returns something that
|
||||||
|
//! can be converted [into a response](crate::response).
|
||||||
|
//!
|
||||||
|
//! Handlers are where your application logic lives and axum applications are built
|
||||||
|
//! by routing between handlers.
|
||||||
//!
|
//!
|
||||||
//! Some examples of handlers:
|
//! Some examples of handlers:
|
||||||
//!
|
//!
|
||||||
@@ -42,7 +47,45 @@
|
|||||||
//! [anyhow]: https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs
|
//! [anyhow]: https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs
|
||||||
//! [error-handling]: https://github.com/tokio-rs/axum/blob/main/examples/error-handling/src/main.rs
|
//! [error-handling]: https://github.com/tokio-rs/axum/blob/main/examples/error-handling/src/main.rs
|
||||||
//!
|
//!
|
||||||
#![doc = include_str!("../docs/debugging_handler_type_errors.md")]
|
//! ## Debugging handler type errors
|
||||||
|
//!
|
||||||
|
//! For a function to be used as a handler it must implement the [`Handler`] trait.
|
||||||
|
//! axum provides blanket implementations for functions that:
|
||||||
|
//!
|
||||||
|
//! - Are `async fn`s.
|
||||||
|
//! - Take no more than 16 arguments that all implement `Send`.
|
||||||
|
//! - All except the last argument implement [`FromRequestParts`].
|
||||||
|
//! - The last argument implements [`FromRequest`].
|
||||||
|
//! - Returns something that implements [`IntoResponse`].
|
||||||
|
//! - If a closure is used it must implement `Clone + Send` and be
|
||||||
|
//! `'static`.
|
||||||
|
//! - Returns a future that is `Send`. The most common way to accidentally make a
|
||||||
|
//! future `!Send` is to hold a `!Send` type across an await.
|
||||||
|
//!
|
||||||
|
//! Unfortunately Rust gives poor error messages if you try to use a function
|
||||||
|
//! that doesn't quite match what's required by [`Handler`].
|
||||||
|
//!
|
||||||
|
//! You might get an error like this:
|
||||||
|
//!
|
||||||
|
//! ```not_rust
|
||||||
|
//! error[E0277]: the trait bound `fn(bool) -> impl Future {handler}: Handler<_, _>` is not satisfied
|
||||||
|
//! --> src/main.rs:13:44
|
||||||
|
//! |
|
||||||
|
//! 13 | let app = Router::new().route("/", get(handler));
|
||||||
|
//! | ^^^^^^^ the trait `Handler<_, _>` is not implemented for `fn(bool) -> impl Future {handler}`
|
||||||
|
//! |
|
||||||
|
//! ::: axum/src/handler/mod.rs:116:8
|
||||||
|
//! |
|
||||||
|
//! 116 | H: Handler<T, B>,
|
||||||
|
//! | ------------- required by this bound in `axum::routing::get`
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! This error doesn't tell you _why_ your function doesn't implement
|
||||||
|
//! [`Handler`]. It's possible to improve the error with the [`debug_handler`]
|
||||||
|
//! proc-macro from the [axum-macros] crate.
|
||||||
|
//!
|
||||||
|
//! [axum-macros]: https://docs.rs/axum-macros
|
||||||
|
//! [`debug_handler`]: https://docs.rs/axum-macros/latest/axum_macros/attr.debug_handler.html
|
||||||
|
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
|
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
|
||||||
@@ -99,7 +142,46 @@ pub use self::service::HandlerService;
|
|||||||
/// S: Service<Request>,
|
/// S: Service<Request>,
|
||||||
/// {}
|
/// {}
|
||||||
/// ```
|
/// ```
|
||||||
#[doc = include_str!("../docs/debugging_handler_type_errors.md")]
|
///
|
||||||
|
/// ## Debugging handler type errors
|
||||||
|
///
|
||||||
|
/// For a function to be used as a handler it must implement the [`Handler`] trait.
|
||||||
|
/// axum provides blanket implementations for functions that:
|
||||||
|
///
|
||||||
|
/// - Are `async fn`s.
|
||||||
|
/// - Take no more than 16 arguments that all implement `Send`.
|
||||||
|
/// - All except the last argument implement [`FromRequestParts`].
|
||||||
|
/// - The last argument implements [`FromRequest`].
|
||||||
|
/// - Returns something that implements [`IntoResponse`].
|
||||||
|
/// - If a closure is used it must implement `Clone + Send` and be
|
||||||
|
/// `'static`.
|
||||||
|
/// - Returns a future that is `Send`. The most common way to accidentally make a
|
||||||
|
/// future `!Send` is to hold a `!Send` type across an await.
|
||||||
|
///
|
||||||
|
/// Unfortunately Rust gives poor error messages if you try to use a function
|
||||||
|
/// that doesn't quite match what's required by [`Handler`].
|
||||||
|
///
|
||||||
|
/// You might get an error like this:
|
||||||
|
///
|
||||||
|
/// ```not_rust
|
||||||
|
/// error[E0277]: the trait bound `fn(bool) -> impl Future {handler}: Handler<_, _>` is not satisfied
|
||||||
|
/// --> src/main.rs:13:44
|
||||||
|
/// |
|
||||||
|
/// 13 | let app = Router::new().route("/", get(handler));
|
||||||
|
/// | ^^^^^^^ the trait `Handler<_, _>` is not implemented for `fn(bool) -> impl Future {handler}`
|
||||||
|
/// |
|
||||||
|
/// ::: axum/src/handler/mod.rs:116:8
|
||||||
|
/// |
|
||||||
|
/// 116 | H: Handler<T, B>,
|
||||||
|
/// | ------------- required by this bound in `axum::routing::get`
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This error doesn't tell you _why_ your function doesn't implement
|
||||||
|
/// [`Handler`]. It's possible to improve the error with the [`debug_handler`]
|
||||||
|
/// proc-macro from the [axum-macros] crate.
|
||||||
|
///
|
||||||
|
/// [axum-macros]: https://docs.rs/axum-macros
|
||||||
|
/// [`debug_handler`]: crate::debug_handler
|
||||||
///
|
///
|
||||||
/// # Handlers that aren't functions
|
/// # Handlers that aren't functions
|
||||||
///
|
///
|
||||||
|
|||||||
+9
-4
@@ -69,9 +69,14 @@
|
|||||||
//!
|
//!
|
||||||
//! # Handlers
|
//! # Handlers
|
||||||
//!
|
//!
|
||||||
#![doc = include_str!("docs/handlers_intro.md")]
|
//! In axum a "handler" is an async function that accepts zero or more
|
||||||
|
//! ["extractors"](crate::extract) as arguments and returns something that
|
||||||
|
//! can be converted [into a response](crate::response).
|
||||||
//!
|
//!
|
||||||
//! See [`handler`](crate::handler) for more details on handlers.
|
//! Handlers are where your application logic lives and axum applications are built
|
||||||
|
//! by routing between handlers.
|
||||||
|
//!
|
||||||
|
//! See [`handler`] for more details on handlers.
|
||||||
//!
|
//!
|
||||||
//! # Extractors
|
//! # Extractors
|
||||||
//!
|
//!
|
||||||
@@ -94,7 +99,7 @@
|
|||||||
//! async fn json(Json(payload): Json<serde_json::Value>) {}
|
//! async fn json(Json(payload): Json<serde_json::Value>) {}
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! See [`extract`](crate::extract) for more details on extractors.
|
//! See [`extract`] for more details on extractors.
|
||||||
//!
|
//!
|
||||||
//! # Responses
|
//! # Responses
|
||||||
//!
|
//!
|
||||||
@@ -126,7 +131,7 @@
|
|||||||
//! # let _: Router = app;
|
//! # let _: Router = app;
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! See [`response`](crate::response) for more details on building responses.
|
//! See [`response`] for more details on building responses.
|
||||||
//!
|
//!
|
||||||
//! # Error handling
|
//! # Error handling
|
||||||
//!
|
//!
|
||||||
|
|||||||
+583
-1
@@ -1,6 +1,588 @@
|
|||||||
//! Utilities for writing middleware
|
//! Utilities for writing middleware
|
||||||
//!
|
//!
|
||||||
#![doc = include_str!("../docs/middleware.md")]
|
//! # Intro
|
||||||
|
//!
|
||||||
|
//! 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.
|
||||||
|
//!
|
||||||
|
//! While it's 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.
|
||||||
|
//!
|
||||||
|
//! # Applying middleware
|
||||||
|
//!
|
||||||
|
//! 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 `layer` (or `route_layer`) repeatedly:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{
|
||||||
|
//! routing::get,
|
||||||
|
//! Extension,
|
||||||
|
//! Router,
|
||||||
|
//! };
|
||||||
|
//! use tower_http::{trace::TraceLayer};
|
||||||
|
//! use tower::ServiceBuilder;
|
||||||
|
//!
|
||||||
|
//! async fn handler() {}
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! struct State {}
|
||||||
|
//!
|
||||||
|
//! let app = Router::new()
|
||||||
|
//! .route("/", get(handler))
|
||||||
|
//! .layer(
|
||||||
|
//! ServiceBuilder::new()
|
||||||
|
//! .layer(TraceLayer::new_for_http())
|
||||||
|
//! .layer(Extension(State {}))
|
||||||
|
//! );
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Commonly used middleware
|
||||||
|
//!
|
||||||
|
//! Some commonly used middleware are:
|
||||||
|
//!
|
||||||
|
//! - [`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_http::timeout::TimeoutLayer) for timeouts.
|
||||||
|
//!
|
||||||
|
//! # Ordering
|
||||||
|
//!
|
||||||
|
//! 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::Extension(());
|
||||||
|
//! # let layer_two = axum::Extension(());
|
||||||
|
//! # let layer_three = axum::Extension(());
|
||||||
|
//! #
|
||||||
|
//! let app = Router::new()
|
||||||
|
//! .route("/", get(handler))
|
||||||
|
//! .layer(layer_one)
|
||||||
|
//! .layer(layer_two)
|
||||||
|
//! .layer(layer_three);
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Think of the middleware as being layered like an onion where each new layer
|
||||||
|
//! wraps all previous layers:
|
||||||
|
//!
|
||||||
|
//! ```not_rust
|
||||||
|
//! requests
|
||||||
|
//! |
|
||||||
|
//! v
|
||||||
|
//! +----- layer_three -----+
|
||||||
|
//! | +---- layer_two ----+ |
|
||||||
|
//! | | +-- layer_one --+ | |
|
||||||
|
//! | | | | | |
|
||||||
|
//! | | | handler | | |
|
||||||
|
//! | | | | | |
|
||||||
|
//! | | +-- layer_one --+ | |
|
||||||
|
//! | +---- layer_two ----+ |
|
||||||
|
//! +----- layer_three -----+
|
||||||
|
//! |
|
||||||
|
//! v
|
||||||
|
//! responses
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! That is:
|
||||||
|
//!
|
||||||
|
//! - 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 passed to `layer_one`
|
||||||
|
//! - Then to `layer_two`
|
||||||
|
//! - And finally to `layer_three` where it's returned out of your app
|
||||||
|
//!
|
||||||
|
//! 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 it's a useful mental model to have.
|
||||||
|
//!
|
||||||
|
//! As previously mentioned it's 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::Extension(());
|
||||||
|
//! # let layer_two = axum::Extension(());
|
||||||
|
//! # let layer_three = axum::Extension(());
|
||||||
|
//! #
|
||||||
|
//! let app = Router::new()
|
||||||
|
//! .route("/", get(handler))
|
||||||
|
//! .layer(
|
||||||
|
//! ServiceBuilder::new()
|
||||||
|
//! .layer(layer_one)
|
||||||
|
//! .layer(layer_two)
|
||||||
|
//! .layer(layer_three),
|
||||||
|
//! );
|
||||||
|
//! # let _: Router = 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::middleware::from_extractor`
|
||||||
|
//!
|
||||||
|
//! Use [`axum::middleware::from_extractor`] 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 your 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,
|
||||||
|
//! body::Body,
|
||||||
|
//! extract::Request,
|
||||||
|
//! };
|
||||||
|
//! use futures_core::future::BoxFuture;
|
||||||
|
//! use tower::{Service, Layer};
|
||||||
|
//! use std::task::{Context, Poll};
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! 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,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl<S> Service<Request> for MyMiddleware<S>
|
||||||
|
//! where
|
||||||
|
//! S: Service<Request, 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, request: Request) -> Self::Future {
|
||||||
|
//! let future = self.inner.call(request);
|
||||||
|
//! Box::pin(async move {
|
||||||
|
//! let response: Response = future.await?;
|
||||||
|
//! Ok(response)
|
||||||
|
//! })
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Note that your error type being defined as `S::Error` means that your middleware typically _returns no errors_. As a principle always try to return a response and try not to bail out with a custom error type. For example, if a 3rd party library you are using inside your new middleware returns its own specialized error type, try to convert it to some reasonable response and return `Ok` with that response.
|
||||||
|
//!
|
||||||
|
//! If you choose to implement a custom error type such as `type Error = BoxError` (a boxed opaque error), or any other error type that is not `Infallible`, you must use a `HandleErrorLayer`, here is an example using a `ServiceBuilder`:
|
||||||
|
//!
|
||||||
|
//! ```ignore
|
||||||
|
//! ServiceBuilder::new()
|
||||||
|
//! .layer(HandleErrorLayer::new(|_: BoxError| async {
|
||||||
|
//! // because axum uses infallible errors, you must handle your custom error type from your middleware here
|
||||||
|
//! StatusCode::BAD_REQUEST
|
||||||
|
//! }))
|
||||||
|
//! .layer(
|
||||||
|
//! // <your actual layer which DOES return an error>
|
||||||
|
//! );
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## `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(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)))
|
||||||
|
//! );
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! 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 won't 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][tower::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);
|
||||||
|
//! # let _: Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! However when applying middleware around your whole application in this way
|
||||||
|
//! you have to take care that errors are still being handled 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.
|
||||||
|
//!
|
||||||
|
//! # Accessing state in middleware
|
||||||
|
//!
|
||||||
|
//! How to make state available to middleware depends on how the middleware is
|
||||||
|
//! written.
|
||||||
|
//!
|
||||||
|
//! ## Accessing state in `axum::middleware::from_fn`
|
||||||
|
//!
|
||||||
|
//! Use [`axum::middleware::from_fn_with_state`](crate::middleware::from_fn_with_state).
|
||||||
|
//!
|
||||||
|
//! ## Accessing state in custom `tower::Layer`s
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{
|
||||||
|
//! Router,
|
||||||
|
//! routing::get,
|
||||||
|
//! middleware::{self, Next},
|
||||||
|
//! response::Response,
|
||||||
|
//! extract::{State, Request},
|
||||||
|
//! };
|
||||||
|
//! use tower::{Layer, Service};
|
||||||
|
//! use std::task::{Context, Poll};
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! struct AppState {}
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! struct MyLayer {
|
||||||
|
//! state: AppState,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl<S> Layer<S> for MyLayer {
|
||||||
|
//! type Service = MyService<S>;
|
||||||
|
//!
|
||||||
|
//! fn layer(&self, inner: S) -> Self::Service {
|
||||||
|
//! MyService {
|
||||||
|
//! inner,
|
||||||
|
//! state: self.state.clone(),
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! struct MyService<S> {
|
||||||
|
//! inner: S,
|
||||||
|
//! state: AppState,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl<S, B> Service<Request<B>> for MyService<S>
|
||||||
|
//! where
|
||||||
|
//! S: Service<Request<B>>,
|
||||||
|
//! {
|
||||||
|
//! type Response = S::Response;
|
||||||
|
//! type Error = S::Error;
|
||||||
|
//! type Future = S::Future;
|
||||||
|
//!
|
||||||
|
//! fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
//! self.inner.poll_ready(cx)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||||
|
//! // Do something with `self.state`.
|
||||||
|
//! //
|
||||||
|
//! // See `axum::RequestExt` for how to run extractors directly from
|
||||||
|
//! // a `Request`.
|
||||||
|
//!
|
||||||
|
//! self.inner.call(req)
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn handler(_: State<AppState>) {}
|
||||||
|
//!
|
||||||
|
//! let state = AppState {};
|
||||||
|
//!
|
||||||
|
//! let app = Router::new()
|
||||||
|
//! .route("/", get(handler))
|
||||||
|
//! .layer(MyLayer { state: state.clone() })
|
||||||
|
//! .with_state(state);
|
||||||
|
//! # let _: axum::Router = app;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Passing state from middleware to handlers
|
||||||
|
//!
|
||||||
|
//! State can be passed from middleware to handlers using [request extensions]:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{
|
||||||
|
//! Router,
|
||||||
|
//! http::StatusCode,
|
||||||
|
//! routing::get,
|
||||||
|
//! response::{IntoResponse, Response},
|
||||||
|
//! middleware::{self, Next},
|
||||||
|
//! extract::{Request, Extension},
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! struct CurrentUser { /* ... */ }
|
||||||
|
//!
|
||||||
|
//! async fn auth(mut req: Request, next: Next) -> Result<Response, StatusCode> {
|
||||||
|
//! 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 {
|
||||||
|
//! // insert the current user into a request extension so the handler can
|
||||||
|
//! // extract it
|
||||||
|
//! req.extensions_mut().insert(current_user);
|
||||||
|
//! Ok(next.run(req).await)
|
||||||
|
//! } else {
|
||||||
|
//! Err(StatusCode::UNAUTHORIZED)
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn authorize_current_user(auth_token: &str) -> Option<CurrentUser> {
|
||||||
|
//! // ...
|
||||||
|
//! # unimplemented!()
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn handler(
|
||||||
|
//! // extract the current user, set by the middleware
|
||||||
|
//! Extension(current_user): Extension<CurrentUser>,
|
||||||
|
//! ) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! let app = Router::new()
|
||||||
|
//! .route("/", get(handler))
|
||||||
|
//! .route_layer(middleware::from_fn(auth));
|
||||||
|
//! # let _: 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.
|
||||||
|
//!
|
||||||
|
//! # 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,
|
||||||
|
//! extract::Request,
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! fn rewrite_request_uri<B>(req: Request<B>) -> Request<B> {
|
||||||
|
//! // ...
|
||||||
|
//! # req
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // this can be any `tower::Layer`
|
||||||
|
//! let middleware = tower::util::MapRequestLayer::new(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 {
|
||||||
|
//! let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||||
|
//! axum::serve(listener, 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
|
||||||
|
//! [`axum::middleware::from_fn`]: fn@crate::middleware::from_fn
|
||||||
|
//! [`middleware::from_fn`]: 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::middleware::from_extractor`]: fn@crate::middleware::from_extractor
|
||||||
|
//! [`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
|
||||||
|
//! [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
|
||||||
|
|
||||||
mod from_extractor;
|
mod from_extractor;
|
||||||
mod from_fn;
|
mod from_fn;
|
||||||
|
|||||||
+321
-1
@@ -1,4 +1,324 @@
|
|||||||
#![doc = include_str!("../docs/response.md")]
|
//! Types and traits for generating responses.
|
||||||
|
//!
|
||||||
|
//! # Building responses
|
||||||
|
//!
|
||||||
|
//! Anything that implements [`IntoResponse`] can be returned from a handler. axum
|
||||||
|
//! provides implementations for common types:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use axum::{
|
||||||
|
//! Json,
|
||||||
|
//! response::{Html, IntoResponse},
|
||||||
|
//! http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}},
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! // `()` gives an empty response
|
||||||
|
//! async fn empty() {}
|
||||||
|
//!
|
||||||
|
//! // String will get a `text/plain; charset=utf-8` content-type
|
||||||
|
//! async fn plain_text(uri: Uri) -> String {
|
||||||
|
//! format!("Hi from {}", uri.path())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // Bytes will get a `application/octet-stream` content-type
|
||||||
|
//! async fn bytes() -> Vec<u8> {
|
||||||
|
//! vec![1, 2, 3, 4]
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // `Json` will get a `application/json` content-type and work with anything that
|
||||||
|
//! // implements `serde::Serialize`
|
||||||
|
//! async fn json() -> Json<Vec<String>> {
|
||||||
|
//! Json(vec!["foo".to_owned(), "bar".to_owned()])
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // `Html` will get a `text/html` content-type
|
||||||
|
//! async fn html() -> Html<&'static str> {
|
||||||
|
//! Html("<p>Hello, World!</p>")
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // `StatusCode` gives an empty response with that status code
|
||||||
|
//! async fn status() -> StatusCode {
|
||||||
|
//! StatusCode::NOT_FOUND
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // `HeaderMap` gives an empty response with some headers
|
||||||
|
//! async fn headers() -> HeaderMap {
|
||||||
|
//! let mut headers = HeaderMap::new();
|
||||||
|
//! headers.insert(header::SERVER, "axum".parse().unwrap());
|
||||||
|
//! headers
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // An array of tuples also gives headers
|
||||||
|
//! async fn array_headers() -> [(HeaderName, &'static str); 2] {
|
||||||
|
//! [
|
||||||
|
//! (header::SERVER, "axum"),
|
||||||
|
//! (header::CONTENT_TYPE, "text/plain")
|
||||||
|
//! ]
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // Use `impl IntoResponse` to avoid writing the whole type
|
||||||
|
//! async fn impl_trait() -> impl IntoResponse {
|
||||||
|
//! [
|
||||||
|
//! (header::SERVER, "axum"),
|
||||||
|
//! (header::CONTENT_TYPE, "text/plain")
|
||||||
|
//! ]
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Additionally you can return tuples to build more complex responses from
|
||||||
|
//! individual parts.
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use axum::{
|
||||||
|
//! Json,
|
||||||
|
//! response::IntoResponse,
|
||||||
|
//! http::{StatusCode, HeaderMap, Uri, header},
|
||||||
|
//! extract::Extension,
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! // `(StatusCode, impl IntoResponse)` will override the status code of the response
|
||||||
|
//! async fn with_status(uri: Uri) -> (StatusCode, String) {
|
||||||
|
//! (StatusCode::NOT_FOUND, format!("Not Found: {}", uri.path()))
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // Use `impl IntoResponse` to avoid having to type the whole type
|
||||||
|
//! async fn impl_trait(uri: Uri) -> impl IntoResponse {
|
||||||
|
//! (StatusCode::NOT_FOUND, format!("Not Found: {}", uri.path()))
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // `(HeaderMap, impl IntoResponse)` to add additional headers
|
||||||
|
//! async fn with_headers() -> impl IntoResponse {
|
||||||
|
//! let mut headers = HeaderMap::new();
|
||||||
|
//! headers.insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
|
||||||
|
//! (headers, "foo")
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // Or an array of tuples to more easily build the headers
|
||||||
|
//! async fn with_array_headers() -> impl IntoResponse {
|
||||||
|
//! ([(header::CONTENT_TYPE, "text/plain")], "foo")
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // Use string keys for custom headers
|
||||||
|
//! async fn with_array_headers_custom() -> impl IntoResponse {
|
||||||
|
//! ([("x-custom", "custom")], "foo")
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // `(StatusCode, headers, impl IntoResponse)` to set status and add headers
|
||||||
|
//! // `headers` can be either a `HeaderMap` or an array of tuples
|
||||||
|
//! async fn with_status_and_array_headers() -> impl IntoResponse {
|
||||||
|
//! (
|
||||||
|
//! StatusCode::NOT_FOUND,
|
||||||
|
//! [(header::CONTENT_TYPE, "text/plain")],
|
||||||
|
//! "foo",
|
||||||
|
//! )
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // `(Extension<_>, impl IntoResponse)` to set response extensions
|
||||||
|
//! async fn with_status_extensions() -> impl IntoResponse {
|
||||||
|
//! (
|
||||||
|
//! Extension(Foo("foo")),
|
||||||
|
//! "foo",
|
||||||
|
//! )
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! struct Foo(&'static str);
|
||||||
|
//!
|
||||||
|
//! // Or mix and match all the things
|
||||||
|
//! async fn all_the_things(uri: Uri) -> impl IntoResponse {
|
||||||
|
//! let mut header_map = HeaderMap::new();
|
||||||
|
//! if uri.path() == "/" {
|
||||||
|
//! header_map.insert(header::SERVER, "axum".parse().unwrap());
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! (
|
||||||
|
//! // set status code
|
||||||
|
//! StatusCode::NOT_FOUND,
|
||||||
|
//! // headers with an array
|
||||||
|
//! [("x-custom", "custom")],
|
||||||
|
//! // some extensions
|
||||||
|
//! Extension(Foo("foo")),
|
||||||
|
//! Extension(Foo("bar")),
|
||||||
|
//! // more headers, built dynamically
|
||||||
|
//! header_map,
|
||||||
|
//! // and finally the body
|
||||||
|
//! "foo",
|
||||||
|
//! )
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! In general you can return tuples like:
|
||||||
|
//!
|
||||||
|
//! - `(StatusCode, impl IntoResponse)`
|
||||||
|
//! - `(Parts, impl IntoResponse)`
|
||||||
|
//! - `(Response<()>, impl IntoResponse)`
|
||||||
|
//! - `(T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
||||||
|
//! - `(StatusCode, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
||||||
|
//! - `(Parts, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
||||||
|
//! - `(Response<()>, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
||||||
|
//!
|
||||||
|
//! This means you cannot accidentally override the status or body as [`IntoResponseParts`] only allows
|
||||||
|
//! setting headers and extensions.
|
||||||
|
//!
|
||||||
|
//! Use [`Response`] for more low level control:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use axum::{
|
||||||
|
//! Json,
|
||||||
|
//! response::{IntoResponse, Response},
|
||||||
|
//! body::Body,
|
||||||
|
//! http::StatusCode,
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! async fn response() -> Response {
|
||||||
|
//! Response::builder()
|
||||||
|
//! .status(StatusCode::NOT_FOUND)
|
||||||
|
//! .header("x-foo", "custom header")
|
||||||
|
//! .body(Body::from("not found"))
|
||||||
|
//! .unwrap()
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Returning different response types
|
||||||
|
//!
|
||||||
|
//! If you need to return multiple response types, and `Result<T, E>` isn't appropriate, you can call
|
||||||
|
//! `.into_response()` to turn things into `axum::response::Response`:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{
|
||||||
|
//! response::{IntoResponse, Redirect, Response},
|
||||||
|
//! http::StatusCode,
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! async fn handle() -> Response {
|
||||||
|
//! if something() {
|
||||||
|
//! "All good!".into_response()
|
||||||
|
//! } else if something_else() {
|
||||||
|
//! (
|
||||||
|
//! StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
//! "Something went wrong...",
|
||||||
|
//! ).into_response()
|
||||||
|
//! } else {
|
||||||
|
//! Redirect::to("/").into_response()
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn something() -> bool {
|
||||||
|
//! // ...
|
||||||
|
//! # true
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn something_else() -> bool {
|
||||||
|
//! // ...
|
||||||
|
//! # true
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Regarding `impl IntoResponse`
|
||||||
|
//!
|
||||||
|
//! You can use `impl IntoResponse` as the return type from handlers to avoid
|
||||||
|
//! typing large types. For example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::http::StatusCode;
|
||||||
|
//!
|
||||||
|
//! async fn handler() -> (StatusCode, [(&'static str, &'static str); 1], &'static str) {
|
||||||
|
//! (StatusCode::OK, [("x-foo", "bar")], "Hello, World!")
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Becomes easier using `impl IntoResponse`:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{http::StatusCode, response::IntoResponse};
|
||||||
|
//!
|
||||||
|
//! async fn impl_into_response() -> impl IntoResponse {
|
||||||
|
//! (StatusCode::OK, [("x-foo", "bar")], "Hello, World!")
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! However `impl IntoResponse` has a few limitations. Firstly it can only be used
|
||||||
|
//! to return a single type:
|
||||||
|
//!
|
||||||
|
//! ```rust,compile_fail
|
||||||
|
//! use axum::{http::StatusCode, response::IntoResponse};
|
||||||
|
//!
|
||||||
|
//! async fn handler() -> impl IntoResponse {
|
||||||
|
//! if check_something() {
|
||||||
|
//! StatusCode::NOT_FOUND
|
||||||
|
//! } else {
|
||||||
|
//! "Hello, World!"
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn check_something() -> bool {
|
||||||
|
//! # false
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! This function returns either a `StatusCode` or a `&'static str` which `impl
|
||||||
|
//! Trait` doesn't allow.
|
||||||
|
//!
|
||||||
|
//! Secondly `impl IntoResponse` can lead to type inference issues when used with
|
||||||
|
//! `Result` and `?`:
|
||||||
|
//!
|
||||||
|
//! ```rust,compile_fail
|
||||||
|
//! use axum::{http::StatusCode, response::IntoResponse};
|
||||||
|
//!
|
||||||
|
//! async fn handler() -> impl IntoResponse {
|
||||||
|
//! create_thing()?;
|
||||||
|
//! Ok(StatusCode::CREATED)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn create_thing() -> Result<(), StatusCode> {
|
||||||
|
//! # Ok(())
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! This is because `?` supports using the [`From`] trait to convert to a different
|
||||||
|
//! error type but it doesn't know which type to convert to, because we only
|
||||||
|
//! specified `impl IntoResponse` as the return type.
|
||||||
|
//!
|
||||||
|
//! `Result<impl IntoResponse, impl IntoResponse>` doesn't always work either:
|
||||||
|
//!
|
||||||
|
//! ```rust,compile_fail
|
||||||
|
//! use axum::{http::StatusCode, response::IntoResponse};
|
||||||
|
//!
|
||||||
|
//! async fn handler() -> Result<impl IntoResponse, impl IntoResponse> {
|
||||||
|
//! create_thing()?;
|
||||||
|
//! Ok(StatusCode::CREATED)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn create_thing() -> Result<(), StatusCode> {
|
||||||
|
//! # Ok(())
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The solution is to use a concrete error type, such as `Result<impl IntoResponse, StatusCode>`:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use axum::{http::StatusCode, response::IntoResponse};
|
||||||
|
//!
|
||||||
|
//! async fn handler() -> Result<impl IntoResponse, StatusCode> {
|
||||||
|
//! create_thing()?;
|
||||||
|
//! Ok(StatusCode::CREATED)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn create_thing() -> Result<(), StatusCode> {
|
||||||
|
//! # Ok(())
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Because of this it is generally not recommended to use `impl IntoResponse`
|
||||||
|
//! unless you're familiar with the details of how `impl Trait` works.
|
||||||
|
//!
|
||||||
|
//! [`IntoResponse`]: crate::response::IntoResponse
|
||||||
|
//! [`IntoResponseParts`]: crate::response::IntoResponseParts
|
||||||
|
//! [`StatusCode`]: http::StatusCode
|
||||||
|
|
||||||
use http::{header, HeaderValue, StatusCode};
|
use http::{header, HeaderValue, StatusCode};
|
||||||
|
|
||||||
|
|||||||
@@ -951,7 +951,61 @@ where
|
|||||||
chained_service_fn!(put_service, PUT);
|
chained_service_fn!(put_service, PUT);
|
||||||
chained_service_fn!(trace_service, TRACE);
|
chained_service_fn!(trace_service, TRACE);
|
||||||
|
|
||||||
#[doc = include_str!("../docs/method_routing/fallback.md")]
|
/// Add a fallback service to the router.
|
||||||
|
///
|
||||||
|
/// This service will be called if no routes matches the incoming request.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use axum::{
|
||||||
|
/// Router,
|
||||||
|
/// routing::get,
|
||||||
|
/// handler::Handler,
|
||||||
|
/// response::IntoResponse,
|
||||||
|
/// http::{StatusCode, Method, Uri},
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let handler = get(|| async {}).fallback(fallback);
|
||||||
|
///
|
||||||
|
/// let app = Router::new().route("/", handler);
|
||||||
|
///
|
||||||
|
/// async fn fallback(method: Method, uri: Uri) -> (StatusCode, String) {
|
||||||
|
/// (StatusCode::NOT_FOUND, format!("`{method}` not allowed for {uri}"))
|
||||||
|
/// }
|
||||||
|
/// # let _: Router = app;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## When used with `MethodRouter::merge`
|
||||||
|
///
|
||||||
|
/// Two routers that both have a fallback cannot be merged. Doing so results in a
|
||||||
|
/// panic:
|
||||||
|
///
|
||||||
|
/// ```rust,should_panic
|
||||||
|
/// use axum::{
|
||||||
|
/// routing::{get, post},
|
||||||
|
/// handler::Handler,
|
||||||
|
/// response::IntoResponse,
|
||||||
|
/// http::{StatusCode, Uri},
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let one = get(|| async {}).fallback(fallback_one);
|
||||||
|
///
|
||||||
|
/// let two = post(|| async {}).fallback(fallback_two);
|
||||||
|
///
|
||||||
|
/// let method_route = one.merge(two);
|
||||||
|
///
|
||||||
|
/// async fn fallback_one() -> impl IntoResponse { /* ... */ }
|
||||||
|
/// async fn fallback_two() -> impl IntoResponse { /* ... */ }
|
||||||
|
/// # let app: axum::Router = axum::Router::new().route("/", method_route);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Setting the `Allow` header
|
||||||
|
///
|
||||||
|
/// By default `MethodRouter` will set the `Allow` header when returning `405 Method
|
||||||
|
/// Not Allowed`. This is also done when the fallback is used unless the response
|
||||||
|
/// generated by the fallback already sets the `Allow` header.
|
||||||
|
///
|
||||||
|
/// This means if you use `fallback` to accept additional methods, you should make
|
||||||
|
/// sure you set the `Allow` header correctly.
|
||||||
pub fn fallback_service<T>(mut self, svc: T) -> Self
|
pub fn fallback_service<T>(mut self, svc: T) -> Self
|
||||||
where
|
where
|
||||||
T: Service<Request, Error = E> + Clone + Send + Sync + 'static,
|
T: Service<Request, Error = E> + Clone + Send + Sync + 'static,
|
||||||
@@ -962,7 +1016,33 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = include_str!("../docs/method_routing/layer.md")]
|
/// Apply a [`tower::Layer`] to all routes in the router.
|
||||||
|
///
|
||||||
|
/// This can be used to add additional processing to a request for a group
|
||||||
|
/// of routes.
|
||||||
|
///
|
||||||
|
/// Note that the middleware is only applied to existing routes. So you have to
|
||||||
|
/// first add your routes (and / or fallback) and then call `layer` afterwards. Additional
|
||||||
|
/// routes added after `layer` is called will not have the middleware added.
|
||||||
|
///
|
||||||
|
/// Works similarly to [`Router::layer`](super::Router::layer). See that method for
|
||||||
|
/// more details.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use axum::{routing::get, Router};
|
||||||
|
/// use tower::limit::ConcurrencyLimitLayer;
|
||||||
|
///
|
||||||
|
/// async fn handler() {}
|
||||||
|
///
|
||||||
|
/// let app = Router::new().route(
|
||||||
|
/// "/",
|
||||||
|
/// // All requests to `GET /` will be sent through `ConcurrencyLimitLayer`
|
||||||
|
/// get(handler).layer(ConcurrencyLimitLayer::new(64)),
|
||||||
|
/// );
|
||||||
|
/// # let _: Router = app;
|
||||||
|
/// ```
|
||||||
pub fn layer<L, NewError>(self, layer: L) -> MethodRouter<S, NewError>
|
pub fn layer<L, NewError>(self, layer: L) -> MethodRouter<S, NewError>
|
||||||
where
|
where
|
||||||
L: Layer<Route<E>> + Clone + Send + Sync + 'static,
|
L: Layer<Route<E>> + Clone + Send + Sync + 'static,
|
||||||
@@ -991,7 +1071,39 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = include_str!("../docs/method_routing/route_layer.md")]
|
/// Apply a [`tower::Layer`] to the router that will only run if the request matches
|
||||||
|
/// a route.
|
||||||
|
///
|
||||||
|
/// Note that the middleware is only applied to existing routes. So you have to
|
||||||
|
/// first add your routes (and / or fallback) and then call `route_layer`
|
||||||
|
/// afterwards. Additional routes added after `route_layer` is called will not have
|
||||||
|
/// the middleware added.
|
||||||
|
///
|
||||||
|
/// This works similarly to [`MethodRouter::layer`] except the middleware will only run if
|
||||||
|
/// the request matches a route. This is useful for middleware that return early
|
||||||
|
/// (such as authorization) which might otherwise convert a `405 Method Not Allowed` into a
|
||||||
|
/// `401 Unauthorized`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use axum::{
|
||||||
|
/// routing::get,
|
||||||
|
/// Router,
|
||||||
|
/// };
|
||||||
|
/// use tower_http::validate_request::ValidateRequestHeaderLayer;
|
||||||
|
///
|
||||||
|
/// let app = Router::new().route(
|
||||||
|
/// "/foo",
|
||||||
|
/// get(|| async {})
|
||||||
|
/// .route_layer(ValidateRequestHeaderLayer::bearer("password"))
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// // `GET /foo` with a valid token will receive `200 OK`
|
||||||
|
/// // `GET /foo` with a invalid token will receive `401 Unauthorized`
|
||||||
|
/// // `POST /FOO` with a invalid token will receive `405 Method Not Allowed`
|
||||||
|
/// # let _: Router = app;
|
||||||
|
/// ```
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn route_layer<L>(mut self, layer: L) -> Self
|
pub fn route_layer<L>(mut self, layer: L) -> Self
|
||||||
where
|
where
|
||||||
@@ -1085,7 +1197,29 @@ where
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = include_str!("../docs/method_routing/merge.md")]
|
/// Merge two routers into one.
|
||||||
|
///
|
||||||
|
/// This is useful for breaking routers into smaller pieces and combining them
|
||||||
|
/// into one.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use axum::{
|
||||||
|
/// routing::{get, post},
|
||||||
|
/// Router,
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let get = get(|| async {});
|
||||||
|
/// let post = post(|| async {});
|
||||||
|
///
|
||||||
|
/// let merged = get.merge(post);
|
||||||
|
///
|
||||||
|
/// let app = Router::new().route("/", merged);
|
||||||
|
///
|
||||||
|
/// // Our app now accepts
|
||||||
|
/// // - GET /
|
||||||
|
/// // - POST /
|
||||||
|
/// # let _: Router = app;
|
||||||
|
/// ```
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn merge(self, other: Self) -> Self {
|
pub fn merge(self, other: Self) -> Self {
|
||||||
match self.merge_for_path(None, other) {
|
match self.merge_for_path(None, other) {
|
||||||
|
|||||||
+1048
-11
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user