mirror of
https://github.com/tokio-rs/axum.git
synced 2025-03-13 19:27:53 +01:00
Rework docs (#437)
This reworks axum's docs in an attempt to make things easier to find. Previously I wasn't a fan of those docs for the same topic were spread across the root module docs and more specific places like types and methods. This changes it such that the root module docs only gives a high level introduction to a topic, perhaps with a small example, and then link to other places where all the details are. This means `Router` is now the single place to learn about routing, and etc for the topics like handlers and error handling.
This commit is contained in:
parent
1c6038c09f
commit
9465128f3e
46 changed files with 1978 additions and 1836 deletions
|
@ -54,12 +54,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
([#405])
|
||||
- **breaking:** Method routing for services have been moved from `axum::service`
|
||||
to `axum::routing`. So `axum::service::get` now lives at, etc.
|
||||
`axum::routing::service_method_router::get`, etc. ([#405])
|
||||
`axum::routing::service_method_routing::get`, etc. ([#405])
|
||||
- **breaking:** `Router::or` renamed to `Router::merge` and will now panic on
|
||||
overlapping routes. It now only accepts `Router`s and not general `Service`s.
|
||||
Use `Router::fallback` for adding fallback routes ([#408])
|
||||
- **added:** `Router::fallback` for adding handlers for request that didn't
|
||||
match any routes ([#408])
|
||||
match any routes. `Router::fallback` must be use instead of `nest("/", _)` ([#408])
|
||||
- **breaking:** `EmptyRouter` has been renamed to `MethodNotAllowed` as its only
|
||||
used in method routers and not in path routers (`Router`)
|
||||
- **breaking:** Remove support for routing based on the `CONNECT` method. An
|
||||
|
@ -119,7 +119,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
use axum::{
|
||||
Router, service,
|
||||
body::Body,
|
||||
routing::service_method_router::get,
|
||||
routing::service_method_routing::get,
|
||||
response::IntoResponse,
|
||||
http::{Request, Response},
|
||||
error_handling::HandleErrorExt, // for `.handle_error`
|
||||
|
|
|
@ -62,6 +62,7 @@ tokio = { version = "1.6.1", features = ["macros", "rt", "rt-multi-thread", "net
|
|||
tokio-stream = "0.1"
|
||||
tracing = "0.1"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
anyhow = "1.0"
|
||||
|
||||
[dev-dependencies.tower]
|
||||
package = "tower"
|
||||
|
|
|
@ -25,9 +25,13 @@ applications written using [`hyper`] or [`tonic`].
|
|||
|
||||
## Usage example
|
||||
|
||||
Note this example uses `main` which contains breaking changes. See the
|
||||
[v0.2.x](https://github.com/tokio-rs/axum/tree/v0.2.x) branch for an example
|
||||
using 0.2.
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
handler::{get, post},
|
||||
routing::{get, post},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
Json, Router,
|
||||
|
|
|
@ -4,23 +4,58 @@
|
|||
//! cargo run -p example-hello-world
|
||||
//! ```
|
||||
|
||||
use axum::{response::Html, routing::get, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// build our application with a route
|
||||
let app = Router::new().route("/", get(handler));
|
||||
use axum::async_trait;
|
||||
use axum::http::StatusCode;
|
||||
use axum::{
|
||||
extract::{extractor_middleware, FromRequest, RequestParts},
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
|
||||
// run it
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
println!("listening on {}", addr);
|
||||
axum::Server::bind(&addr)
|
||||
// An extractor that performs authorization.
|
||||
struct RequireAuth;
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for RequireAuth
|
||||
where
|
||||
B: Send,
|
||||
{
|
||||
type Rejection = StatusCode;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
let auth_header = req
|
||||
.headers()
|
||||
.and_then(|headers| headers.get(axum::http::header::AUTHORIZATION))
|
||||
.and_then(|value| value.to_str().ok());
|
||||
|
||||
if let Some(value) = auth_header {
|
||||
if value == "secret" {
|
||||
return Ok(Self);
|
||||
}
|
||||
}
|
||||
|
||||
Err(StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
|
||||
async fn handler() {
|
||||
// If we get here the request has been authorized
|
||||
}
|
||||
|
||||
async fn other_handler() {
|
||||
// If we get here the request has been authorized
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.route("/foo", post(other_handler))
|
||||
// The extractor will run before all routes
|
||||
.layer(extractor_middleware::<RequireAuth>());
|
||||
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn handler() -> Html<&'static str> {
|
||||
Html("<h1>Hello, World!</h1>")
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use axum::{
|
|||
extract::TypedHeader,
|
||||
http::StatusCode,
|
||||
response::sse::{Event, Sse},
|
||||
routing::{get, service_method_router as service},
|
||||
routing::{get, service_method_routing as service},
|
||||
Router,
|
||||
};
|
||||
use futures::stream::{self, Stream};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//! ```
|
||||
|
||||
use axum::{
|
||||
error_handling::HandleErrorExt, http::StatusCode, routing::service_method_router as service,
|
||||
error_handling::HandleErrorExt, http::StatusCode, routing::service_method_routing as service,
|
||||
Router,
|
||||
};
|
||||
use std::net::SocketAddr;
|
||||
|
|
|
@ -14,7 +14,7 @@ use axum::{
|
|||
},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{get, service_method_router as service},
|
||||
routing::{get, service_method_routing as service},
|
||||
Router,
|
||||
};
|
||||
use std::net::SocketAddr;
|
||||
|
|
|
@ -1,253 +0,0 @@
|
|||
# Applying middleware
|
||||
|
||||
axum is designed to take full advantage of the tower and tower-http
|
||||
ecosystem of middleware.
|
||||
|
||||
If you're new to tower we recommend you read its [guides][tower-guides] for
|
||||
a general introduction to tower and its concepts.
|
||||
|
||||
## To individual handlers
|
||||
|
||||
A middleware can be applied to a single handler like so:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
handler::Handler,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use tower::limit::ConcurrencyLimitLayer;
|
||||
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/",
|
||||
get(handler.layer(ConcurrencyLimitLayer::new(100))),
|
||||
);
|
||||
|
||||
async fn handler() {}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
## To groups of routes
|
||||
|
||||
Middleware can also be applied to a group of routes like so:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use tower::limit::ConcurrencyLimitLayer;
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.route("/foo", post(handler))
|
||||
.layer(ConcurrencyLimitLayer::new(100));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Note that [`Router::layer`] applies the middleware to all previously added
|
||||
routes, of that particular `Router`. If you need multiple groups of routes
|
||||
with different middleware build them separately and combine them with
|
||||
[`Router::merge`]:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use tower::limit::ConcurrencyLimitLayer;
|
||||
# type MyAuthLayer = tower::layer::util::Identity;
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
let foo = Router::new()
|
||||
.route("/", get(handler))
|
||||
.route("/foo", post(handler))
|
||||
.layer(ConcurrencyLimitLayer::new(100));
|
||||
|
||||
let bar = Router::new()
|
||||
.route("/requires-auth", get(handler))
|
||||
.layer(MyAuthLayer::new());
|
||||
|
||||
let app = foo.merge(bar);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
## Applying multiple middleware
|
||||
|
||||
[`tower::ServiceBuilder`] can be used to combine multiple middleware:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
body::Body,
|
||||
routing::get,
|
||||
http::{Request, StatusCode},
|
||||
error_handling::HandleErrorLayer,
|
||||
response::IntoResponse,
|
||||
Router, BoxError,
|
||||
};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
use std::{borrow::Cow, time::Duration};
|
||||
|
||||
let middleware_stack = ServiceBuilder::new()
|
||||
// Handle errors from middleware
|
||||
//
|
||||
// This middleware most be added above any fallible
|
||||
// ones if you're using `ServiceBuilder`, due to how ordering works
|
||||
.layer(HandleErrorLayer::new(handle_error))
|
||||
// Return an error after 30 seconds
|
||||
.timeout(Duration::from_secs(30))
|
||||
// Shed load if we're receiving too many requests
|
||||
.load_shed()
|
||||
// Process at most 100 requests concurrently
|
||||
.concurrency_limit(100)
|
||||
// Compress response bodies
|
||||
.layer(CompressionLayer::new());
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|_: Request<Body>| async { /* ... */ }))
|
||||
.layer(middleware_stack);
|
||||
|
||||
fn handle_error(error: BoxError) -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", error),
|
||||
)
|
||||
}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
See [Error handling](#error-handling) for more details on general error handling in axum.
|
||||
|
||||
## Commonly used middleware
|
||||
|
||||
[`tower::util`] and [`tower_http`] have a large collection of middleware that are compatible
|
||||
with axum. Some commonly used are:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
body::{Body, BoxBody},
|
||||
routing::get,
|
||||
http::{Request, Response},
|
||||
error_handling::HandleErrorLayer,
|
||||
Router,
|
||||
};
|
||||
use tower::{
|
||||
filter::AsyncFilterLayer,
|
||||
util::AndThenLayer,
|
||||
ServiceBuilder,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
use tower_http::trace::TraceLayer;
|
||||
#
|
||||
# fn handle_error<T>(error: T) -> axum::http::StatusCode {
|
||||
# axum::http::StatusCode::INTERNAL_SERVER_ERROR
|
||||
# }
|
||||
|
||||
let middleware_stack = ServiceBuilder::new()
|
||||
// Handle errors from middleware
|
||||
//
|
||||
// This middleware most be added above any fallible
|
||||
// ones if you're using `ServiceBuilder`, due to how ordering works
|
||||
.layer(HandleErrorLayer::new(handle_error))
|
||||
// `TraceLayer` adds high level tracing and logging
|
||||
.layer(TraceLayer::new_for_http())
|
||||
// `AsyncFilterLayer` lets you asynchronously transform the request
|
||||
.layer(AsyncFilterLayer::new(map_request))
|
||||
// `AndThenLayer` lets you asynchronously transform the response
|
||||
.layer(AndThenLayer::new(map_response));
|
||||
|
||||
async fn map_request(req: Request<Body>) -> Result<Request<Body>, Infallible> {
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
async fn map_response(res: Response<BoxBody>) -> Result<Response<BoxBody>, Infallible> {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { /* ... */ }))
|
||||
.layer(middleware_stack);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Additionally axum provides [`extract::extractor_middleware()`] for converting any extractor into
|
||||
a middleware. Among other things, this can be useful for doing authorization. See
|
||||
[`extract::extractor_middleware()`] for more details.
|
||||
|
||||
See [Error handling](#error-handling) for more details on general error handling in axum.
|
||||
|
||||
## Writing your own middleware
|
||||
|
||||
You can also write you own middleware by implementing [`tower::Service`]:
|
||||
|
||||
```
|
||||
use axum::{
|
||||
body::{Body, BoxBody},
|
||||
routing::get,
|
||||
http::{Request, Response},
|
||||
Router,
|
||||
};
|
||||
use futures::future::BoxFuture;
|
||||
use tower::{Service, layer::layer_fn};
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MyMiddleware<S> {
|
||||
inner: S,
|
||||
}
|
||||
|
||||
impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for MyMiddleware<S>
|
||||
where
|
||||
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + 'static,
|
||||
S::Future: Send + 'static,
|
||||
ReqBody: Send + 'static,
|
||||
ResBody: Send + 'static,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
|
||||
println!("`MyMiddleware` called!");
|
||||
|
||||
// best practice is to clone the inner service like this
|
||||
// see https://github.com/tower-rs/tower/issues/547 for details
|
||||
let clone = self.inner.clone();
|
||||
let mut inner = std::mem::replace(&mut self.inner, clone);
|
||||
|
||||
Box::pin(async move {
|
||||
let res: Response<ResBody> = inner.call(req).await?;
|
||||
|
||||
println!("`MyMiddleware` received the response");
|
||||
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { /* ... */ }))
|
||||
.layer(layer_fn(|inner| MyMiddleware { inner }));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
|
@ -1,4 +0,0 @@
|
|||
# Compatibility
|
||||
|
||||
axum is designed to work with [tokio] and [hyper]. Runtime and
|
||||
transport layer independence is not a goal, at least for the time being.
|
|
@ -1,19 +1,130 @@
|
|||
# Error handling
|
||||
Error handling model and utilities
|
||||
|
||||
In the context of axum an "error" specifically means if a [`Service`]'s
|
||||
response future resolves to `Err(Service::Error)`. That means async handler
|
||||
functions can _never_ fail since they always produce a response and their
|
||||
`Service::Error` type is [`Infallible`]. Returning statuses like 404 or 500
|
||||
are _not_ errors.
|
||||
# axum's error handling model
|
||||
|
||||
axum works this way because hyper will close the connection, without sending
|
||||
a response, if an error is encountered. This is not desireable so axum makes
|
||||
it impossible to forget to handle errors.
|
||||
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.
|
||||
|
||||
Sometimes you need to route to fallible services or apply fallible
|
||||
middleware in which case you need to handle the errors. That can be done
|
||||
using things from [`error_handling`].
|
||||
axum does this by requiring all services have [`Infallible`] as their error
|
||||
type. `Infallible` is the error type for errors that can never happen.
|
||||
|
||||
You can find examples here:
|
||||
- [Routing to fallible services](#routing-to-fallible-services)
|
||||
- [Applying fallible middleware](#applying-multiple-middleware)
|
||||
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.
|
||||
|
||||
# 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.
|
||||
|
||||
You can handle errors from services using [`HandleErrorExt::handle_error`]:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
Router,
|
||||
body::Body,
|
||||
http::{Request, Response, StatusCode},
|
||||
error_handling::HandleErrorExt, // for `.handle_error()`
|
||||
};
|
||||
|
||||
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(
|
||||
"/",
|
||||
// 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`.
|
||||
some_fallible_service.handle_error(handle_anyhow_error),
|
||||
);
|
||||
|
||||
// handle errors by converting them into something that implements
|
||||
// `IntoResponse`
|
||||
fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", err),
|
||||
)
|
||||
}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# 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))
|
||||
);
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
[`tower::Service`]: `tower::Service`
|
||||
[`Infallible`]: std::convert::Infallible
|
||||
[`Response<_>`]: http::Response
|
||||
[`IntoResponse`]: crate::response::IntoResponse
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
# Example
|
||||
|
||||
The "Hello, World!" of axum is:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// build our application with a single route
|
||||
let app = Router::new().route("/", get(|| async { "Hello, World!" }));
|
||||
|
||||
// run it with hyper on localhost:3000
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
```
|
|
@ -1,4 +0,0 @@
|
|||
# Examples
|
||||
|
||||
The axum repo contains [a number of examples][examples] that show how to put all the
|
||||
pieces together.
|
476
src/docs/extract.md
Normal file
476
src/docs/extract.md
Normal file
|
@ -0,0 +1,476 @@
|
|||
Types and traits for extracting data from requests.
|
||||
|
||||
A handler function is an async function that takes any number of
|
||||
"extractors" as arguments. An extractor is a type that implements
|
||||
[`FromRequest`](crate::extract::FromRequest).
|
||||
|
||||
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));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# Common extractors
|
||||
|
||||
Some commonly used extractors are:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
extract::{Json, TypedHeader, Path, Extension, Query},
|
||||
routing::post,
|
||||
http::{Request, header::HeaderMap},
|
||||
body::{Bytes, Body},
|
||||
Router,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use headers::UserAgent;
|
||||
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) {}
|
||||
|
||||
// `TypedHeader` can be used to extract a single header
|
||||
// note this requires you've enabled axum's `headers` feature
|
||||
async fn user_agent(TypedHeader(user_agent): TypedHeader<UserAgent>) {}
|
||||
|
||||
// `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<Body>) {}
|
||||
|
||||
// `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", post(path))
|
||||
.route("/query", post(query))
|
||||
.route("/user_agent", post(user_agent))
|
||||
.route("/headers", post(headers))
|
||||
.route("/string", post(string))
|
||||
.route("/bytes", post(bytes))
|
||||
.route("/json", post(json))
|
||||
.route("/request", post(request))
|
||||
.route("/extension", post(extension));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# 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,
|
||||
}
|
||||
|
||||
impl Default for Pagination {
|
||||
fn default() -> Self {
|
||||
Self { page: 1, per_page: 30 }
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_user_things(
|
||||
Path(user_id): Path<Uuid>,
|
||||
pagination: Option<Query<Pagination>>,
|
||||
) {
|
||||
let Query(pagination) = pagination.unwrap_or_default();
|
||||
|
||||
// ...
|
||||
}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Take care of the order in which you apply extractors as some extractors
|
||||
mutate the request.
|
||||
|
||||
For example using [`HeaderMap`] as an extractor will make the headers
|
||||
inaccessible for other extractors on the handler. If you need to extract
|
||||
individual headers _and_ a [`HeaderMap`] make sure to apply the extractor of
|
||||
individual headers first:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
extract::TypedHeader,
|
||||
routing::get,
|
||||
http::header::HeaderMap,
|
||||
Router,
|
||||
};
|
||||
use headers::UserAgent;
|
||||
|
||||
async fn handler(
|
||||
TypedHeader(user_agent): TypedHeader<UserAgent>,
|
||||
all_headers: HeaderMap,
|
||||
) {
|
||||
// ...
|
||||
}
|
||||
|
||||
let app = Router::new().route("/", get(handler));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Extractors that consume the request body can also only be applied once as
|
||||
well as [`Request`], which consumes the entire request:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
routing::get,
|
||||
http::Request,
|
||||
body::Body,
|
||||
Router,
|
||||
};
|
||||
|
||||
async fn handler(request: Request<Body>) {
|
||||
// ...
|
||||
}
|
||||
|
||||
let app = Router::new().route("/", get(handler));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Extractors always run in the order of the function parameters that is from
|
||||
left to right.
|
||||
|
||||
# Optional extractors
|
||||
|
||||
All extractors defined in axum will reject the request if it doesn't match.
|
||||
If you wish to make an extractor optional you can wrap it in `Option`:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
extract::Json,
|
||||
routing::post,
|
||||
Router,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
async fn create_user(payload: Option<Json<Value>>) {
|
||||
if let Some(payload) = payload {
|
||||
// We got a valid JSON payload
|
||||
} else {
|
||||
// Payload wasn't valid JSON
|
||||
}
|
||||
}
|
||||
|
||||
let app = Router::new().route("/users", post(create_user));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Wrapping extractors in `Result` makes them optional and gives you the reason
|
||||
the extraction failed:
|
||||
|
||||
```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::InvalidJsonBody(_)) => {
|
||||
// Couldn't deserialize the body into the target type
|
||||
}
|
||||
Err(JsonRejection::BodyAlreadyExtracted(_)) => {
|
||||
// Another extractor had already consumed the body
|
||||
}
|
||||
Err(_) => {
|
||||
// `JsonRejection` is marked `#[non_exhaustive]` so match must
|
||||
// include a catch-all case.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let app = Router::new().route("/users", post(create_user));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# 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 a two
|
||||
options:
|
||||
|
||||
1. Use `Result<T, T::Rejection>` as your extractor like shown in ["Optional
|
||||
extractors"](#optional-extractors). This works well if you're only using
|
||||
the extractor in a single handler.
|
||||
2. Create your own extractor that in its [`FromRequest`] implemention 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`] thats contained in
|
||||
[`JsonRejection::InvalidJsonBody`]. 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>) -> impl IntoResponse {
|
||||
match result {
|
||||
// if the client sent valid JSON then we're good
|
||||
Ok(Json(payload)) => Ok(Json(json!({ "payload": payload }))),
|
||||
|
||||
Err(err) => match err {
|
||||
// attempt to extract the inner `serde_json::Error`, if that
|
||||
// succeeds we can provide a more specific error
|
||||
JsonRejection::InvalidJsonBody(err) => {
|
||||
if let Some(serde_json_err) = find_error_source::<serde_json::Error>(&err) {
|
||||
Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
format!(
|
||||
"Invalid JSON at line {} column {}",
|
||||
serde_json_err.line(),
|
||||
serde_json_err.column()
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Err((StatusCode::BAD_REQUEST, "Unknown error".to_string()))
|
||||
}
|
||||
}
|
||||
// handle other rejections from the `Json` extractor
|
||||
JsonRejection::MissingJsonContentType(_) => Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Missing `Content-Type: application/json` header".to_string(),
|
||||
)),
|
||||
JsonRejection::BodyAlreadyExtracted(_) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Body already extracted".to_string(),
|
||||
)),
|
||||
JsonRejection::HeadersAlreadyExtracted(_) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Headers already extracted".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 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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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 [`FromRequest`]:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{FromRequest, RequestParts},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use http::{StatusCode, header::{HeaderValue, USER_AGENT}};
|
||||
|
||||
struct ExtractUserAgent(HeaderValue);
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for ExtractUserAgent
|
||||
where
|
||||
B: Send,
|
||||
{
|
||||
type Rejection = (StatusCode, &'static str);
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
let user_agent = req.headers().and_then(|headers| headers.get(USER_AGENT));
|
||||
|
||||
if let Some(user_agent) = 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));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# Request body extractors
|
||||
|
||||
Most of the time your request body type will be [`body::Body`] (a re-export
|
||||
of [`hyper::Body`]), which is directly supported by all extractors.
|
||||
|
||||
However if you're applying a tower middleware that changes the request body type
|
||||
you might have to apply a different body type to some extractors:
|
||||
|
||||
```rust
|
||||
use std::{
|
||||
task::{Context, Poll},
|
||||
pin::Pin,
|
||||
};
|
||||
use tower_http::map_request_body::MapRequestBodyLayer;
|
||||
use axum::{
|
||||
extract::{self, BodyStream},
|
||||
body::Body,
|
||||
routing::get,
|
||||
http::{header::HeaderMap, Request},
|
||||
Router,
|
||||
};
|
||||
|
||||
struct MyBody<B>(B);
|
||||
|
||||
impl<B> http_body::Body for MyBody<B>
|
||||
where
|
||||
B: http_body::Body + Unpin,
|
||||
{
|
||||
type Data = B::Data;
|
||||
type Error = B::Error;
|
||||
|
||||
fn poll_data(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
Pin::new(&mut self.0).poll_data(cx)
|
||||
}
|
||||
|
||||
fn poll_trailers(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
|
||||
Pin::new(&mut self.0).poll_trailers(cx)
|
||||
}
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/string",
|
||||
// `String` works directly with any body type
|
||||
get(|_: String| async {})
|
||||
)
|
||||
.route(
|
||||
"/body",
|
||||
// `extract::Body` defaults to `axum::body::Body`
|
||||
// but can be customized
|
||||
get(|_: extract::RawBody<MyBody<Body>>| async {})
|
||||
)
|
||||
.route(
|
||||
"/body-stream",
|
||||
// same for `extract::BodyStream`
|
||||
get(|_: extract::BodyStream| async {}),
|
||||
)
|
||||
.route(
|
||||
// and `Request<_>`
|
||||
"/request",
|
||||
get(|_: Request<MyBody<Body>>| async {})
|
||||
)
|
||||
// middleware that changes the request body type
|
||||
.layer(MapRequestBodyLayer::new(MyBody));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
[`body::Body`]: crate::body::Body
|
||||
[customize-extractor-error]: https://github.com/tokio-rs/axum/blob/main/examples/customize-extractor-error/src/main.rs
|
|
@ -1,270 +0,0 @@
|
|||
# Extractors
|
||||
|
||||
An extractor is a type that implements [`FromRequest`]. Extractors is how
|
||||
you pick apart the incoming request to get the parts your handler needs.
|
||||
|
||||
For example, [`extract::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,
|
||||
Router,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
let app = Router::new().route("/users", post(create_user));
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreateUser {
|
||||
email: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
async fn create_user(Json(payload): Json<CreateUser>) {
|
||||
// ...
|
||||
}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
See the [`extract`] module for everything that can be used as an extractor.
|
||||
|
||||
## Common extractors
|
||||
|
||||
Some commonly used extractors are:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
extract::{Json, TypedHeader, Path, Extension, Query},
|
||||
routing::post,
|
||||
http::{Request, header::HeaderMap},
|
||||
body::{Bytes, Body},
|
||||
Router,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use headers::UserAgent;
|
||||
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) {}
|
||||
|
||||
// `TypedHeader` can be used to extract a single header
|
||||
// note this requires you've enabled axum's `headers`
|
||||
async fn user_agent(TypedHeader(user_agent): TypedHeader<UserAgent>) {}
|
||||
|
||||
// `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<Body>) {}
|
||||
|
||||
// `Extension` extracts data from "request extensions"
|
||||
// See the "Sharing state with handlers" section for more details
|
||||
async fn extension(Extension(state): Extension<State>) {}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State { /* ... */ }
|
||||
|
||||
let app = Router::new()
|
||||
.route("/path", post(path))
|
||||
.route("/query", post(query))
|
||||
.route("/user_agent", post(user_agent))
|
||||
.route("/headers", post(headers))
|
||||
.route("/string", post(string))
|
||||
.route("/bytes", post(bytes))
|
||||
.route("/json", post(json))
|
||||
.route("/request", post(request))
|
||||
.route("/extension", post(extension));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
## Applying multiple extractors
|
||||
|
||||
You can also apply multiple extractors:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
extract,
|
||||
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,
|
||||
}
|
||||
|
||||
impl Default for Pagination {
|
||||
fn default() -> Self {
|
||||
Self { page: 1, per_page: 30 }
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_user_things(
|
||||
extract::Path(user_id): extract::Path<Uuid>,
|
||||
pagination: Option<extract::Query<Pagination>>,
|
||||
) {
|
||||
let pagination: Pagination = pagination.unwrap_or_default().0;
|
||||
|
||||
// ...
|
||||
}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Take care of the order in which you apply extractors as some extractors
|
||||
mutate the request.
|
||||
|
||||
For example using [`HeaderMap`] as an extractor will make the headers
|
||||
inaccessible for other extractors on the handler. If you need to extract
|
||||
individual headers _and_ a [`HeaderMap`] make sure to apply the extractor of
|
||||
individual headers first:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
extract::TypedHeader,
|
||||
routing::get,
|
||||
http::header::HeaderMap,
|
||||
Router,
|
||||
};
|
||||
use headers::UserAgent;
|
||||
|
||||
async fn handler(
|
||||
TypedHeader(user_agent): TypedHeader<UserAgent>,
|
||||
all_headers: HeaderMap,
|
||||
) {
|
||||
// ...
|
||||
}
|
||||
|
||||
let app = Router::new().route("/", get(handler));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Extractors that consume the request body can also only be applied once as
|
||||
well as [`Request`], which consumes the entire request:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
routing::get,
|
||||
http::Request,
|
||||
body::Body,
|
||||
Router,
|
||||
};
|
||||
|
||||
async fn handler(request: Request<Body>) {
|
||||
// ...
|
||||
}
|
||||
|
||||
let app = Router::new().route("/", get(handler));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Extractors always run in the order of the function parameters that is from
|
||||
left to right.
|
||||
|
||||
## Optional extractors
|
||||
|
||||
All extractors defined in axum will reject the request if it doesn't match.
|
||||
If you wish to make an extractor optional you can wrap it in `Option`:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
extract::Json,
|
||||
routing::post,
|
||||
Router,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
async fn create_user(payload: Option<Json<Value>>) {
|
||||
if let Some(payload) = payload {
|
||||
// We got a valid JSON payload
|
||||
} else {
|
||||
// Payload wasn't valid JSON
|
||||
}
|
||||
}
|
||||
|
||||
let app = Router::new().route("/users", post(create_user));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Wrapping extractors in `Result` makes them optional and gives you the reason
|
||||
the extraction failed:
|
||||
|
||||
```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::InvalidJsonBody(_)) => {
|
||||
// Couldn't deserialize the body into the target type
|
||||
}
|
||||
Err(JsonRejection::BodyAlreadyExtracted(_)) => {
|
||||
// Another extractor had already consumed the body
|
||||
}
|
||||
Err(_) => {
|
||||
// `JsonRejection` is marked `#[non_exhaustive]` so match must
|
||||
// include a catch-all case.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let app = Router::new().route("/users", post(create_user));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
## 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 a two
|
||||
options:
|
||||
|
||||
1. Use `Result<T, T::Rejection>` as your extractor like shown in ["Optional
|
||||
extractors"](#optional-extractors). This works well if you're only using
|
||||
the extractor in a single handler.
|
||||
2. Create your own extractor that in its [`FromRequest`] implemention calls
|
||||
one of axum's built in extractors but returns a different response for
|
||||
rejections. See the [customize-extractor-error] example for more details.
|
|
@ -1,15 +0,0 @@
|
|||
# Feature flags
|
||||
|
||||
axum uses a set of [feature flags] to reduce the amount of compiled and
|
||||
optional dependencies.
|
||||
|
||||
The following optional features are available:
|
||||
|
||||
- `headers`: Enables extracting typed headers via [`extract::TypedHeader`].
|
||||
- `http1`: Enables hyper's `http1` feature. Enabled by default.
|
||||
- `http2`: Enables hyper's `http2` feature.
|
||||
- `json`: Enables the [`Json`] type and some similar convenience functionality.
|
||||
Enabled by default.
|
||||
- `multipart`: Enables parsing `multipart/form-data` requests with [`extract::Multipart`].
|
||||
- `tower-log`: Enables `tower`'s `log` feature. Enabled by default.
|
||||
- `ws`: Enables WebSockets support via [`extract::ws`].
|
|
@ -1,68 +0,0 @@
|
|||
# Handlers
|
||||
|
||||
In axum a "handler" is an async function that accepts zero or more
|
||||
["extractors"](#extractors) as arguments and returns something that
|
||||
can be converted [into a response](#building-responses).
|
||||
|
||||
Handlers is where your custom domain logic lives and axum applications are
|
||||
built by routing between handlers.
|
||||
|
||||
Some examples of handlers:
|
||||
|
||||
```rust
|
||||
use bytes::Bytes;
|
||||
use http::StatusCode;
|
||||
|
||||
// Handler that immediately returns an empty `200 OK` response.
|
||||
async fn unit_handler() {}
|
||||
|
||||
// Handler that immediately returns an empty `200 OK` response with a plain
|
||||
// text body.
|
||||
async fn string_handler() -> String {
|
||||
"Hello, World!".to_string()
|
||||
}
|
||||
|
||||
// Handler that buffers the request body and returns it.
|
||||
async fn echo(body: Bytes) -> Result<String, StatusCode> {
|
||||
if let Ok(string) = String::from_utf8(body.to_vec()) {
|
||||
Ok(string)
|
||||
} else {
|
||||
Err(StatusCode::BAD_REQUEST)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging handler type errors
|
||||
|
||||
For a function to 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 [`FromRequest`].
|
||||
- Returns something that implements [`IntoResponse`].
|
||||
- If a closure is used it must implement `Clone + Send + Sync` 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<B, T>,
|
||||
| ------------- 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-debug] crate.
|
8
src/docs/handlers_intro.md
Normal file
8
src/docs/handlers_intro.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
In axum a "handler" is an async function that accepts zero or more
|
||||
["extractors"](#extractors) as arguments and returns something that
|
||||
can be converted [into a response](crate::response).
|
||||
|
||||
Handlers is where your application logic lives and axum applications are built
|
||||
by routing between handlers.
|
||||
|
||||
[`debug_handler`]: https://docs.rs/axum-debug/latest/axum_debug/attr.debug_handler.html
|
|
@ -1,14 +0,0 @@
|
|||
# High level features
|
||||
|
||||
- Route requests to handlers with a macro free API.
|
||||
- Declaratively parse requests using extractors.
|
||||
- Simple and predictable error handling model.
|
||||
- Generate responses with minimal boilerplate.
|
||||
- Take full advantage of the [`tower`] and [`tower-http`] ecosystem of
|
||||
middleware, services, and utilities.
|
||||
|
||||
In particular the last point is what sets `axum` apart from other frameworks.
|
||||
`axum` doesn't have its own middleware system but instead uses
|
||||
[`tower::Service`]. This means `axum` gets timeouts, tracing, compression,
|
||||
authorization, and more, for free. It also enables you to share middleware with
|
||||
applications written using [`hyper`] or [`tonic`].
|
166
src/docs/middleware.md
Normal file
166
src/docs/middleware.md
Normal file
|
@ -0,0 +1,166 @@
|
|||
axum is designed to take full advantage of the [`tower`] and [`tower-http`]
|
||||
ecosystem of middleware.
|
||||
|
||||
If you're new to tower we recommend you read its [guides][tower-guides] for
|
||||
a general introduction to tower and its concepts.
|
||||
|
||||
axum supports adding middleware to both individual handlers and entire routers.
|
||||
For more details on that see
|
||||
|
||||
- [Individual handlers](crate::handler::Handler::layer)
|
||||
- [Routers](crate::routing::Router::layer)
|
||||
|
||||
## Applying multiple middleware
|
||||
|
||||
Its recommended to use [`tower::ServiceBuilder`] to apply multiple middleware at
|
||||
once, instead of calling [`Router::layer`] repeatedly:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::get,
|
||||
AddExtensionLayer,
|
||||
Router,
|
||||
};
|
||||
use tower_http::{trace::TraceLayer};
|
||||
use tower::{ServiceBuilder, limit::ConcurrencyLimitLayer};
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State {}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(ConcurrencyLimitLayer::new(64))
|
||||
.layer(AddExtensionLayer::new(State {}))
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
## Middleware and errors
|
||||
|
||||
If you're applying middleware that produces errors you have to handle the errors
|
||||
so they're converted into responses. You can learn more about doing that
|
||||
[here](crate::error_handling).
|
||||
|
||||
## Commonly used middleware
|
||||
|
||||
[`tower`] and [`tower_http`] have a large collection of middleware that are
|
||||
compatible with axum. Some commonly used middleware are:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
body::{Body, BoxBody},
|
||||
routing::get,
|
||||
http::{Request, Response},
|
||||
error_handling::HandleErrorLayer,
|
||||
Router,
|
||||
};
|
||||
use tower::{
|
||||
filter::AsyncFilterLayer,
|
||||
util::AndThenLayer,
|
||||
ServiceBuilder,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
use tower_http::trace::TraceLayer;
|
||||
#
|
||||
# fn handle_error<T>(error: T) -> axum::http::StatusCode {
|
||||
# axum::http::StatusCode::INTERNAL_SERVER_ERROR
|
||||
# }
|
||||
|
||||
let middleware_stack = ServiceBuilder::new()
|
||||
// Handle errors from middleware
|
||||
//
|
||||
// This middleware most be added above any fallible
|
||||
// ones if you're using `ServiceBuilder`, due to how ordering works
|
||||
.layer(HandleErrorLayer::new(handle_error))
|
||||
// `TraceLayer` adds high level tracing and logging
|
||||
.layer(TraceLayer::new_for_http())
|
||||
// `AsyncFilterLayer` lets you asynchronously transform the request
|
||||
.layer(AsyncFilterLayer::new(map_request))
|
||||
// `AndThenLayer` lets you asynchronously transform the response
|
||||
.layer(AndThenLayer::new(map_response));
|
||||
|
||||
async fn map_request(req: Request<Body>) -> Result<Request<Body>, Infallible> {
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
async fn map_response(res: Response<BoxBody>) -> Result<Response<BoxBody>, Infallible> {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { /* ... */ }))
|
||||
.layer(middleware_stack);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Additionally axum provides [`extract::extractor_middleware()`] for converting
|
||||
any extractor into a middleware. See [`extract::extractor_middleware()`] for
|
||||
more details.
|
||||
|
||||
## Writing your own middleware
|
||||
|
||||
You can also write you own middleware by implementing [`tower::Service`]:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
body::{Body, BoxBody},
|
||||
routing::get,
|
||||
http::{Request, Response},
|
||||
Router,
|
||||
};
|
||||
use futures::future::BoxFuture;
|
||||
use tower::{Service, layer::layer_fn};
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MyMiddleware<S> {
|
||||
inner: S,
|
||||
}
|
||||
|
||||
impl<S> Service<Request<Body>> for MyMiddleware<S>
|
||||
where
|
||||
S: Service<Request<Body>, Response = Response<BoxBody>> + Clone + Send + 'static,
|
||||
S::Future: Send + 'static,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: Request<Body>) -> Self::Future {
|
||||
println!("`MyMiddleware` called!");
|
||||
|
||||
// best practice is to clone the inner service like this
|
||||
// see https://github.com/tower-rs/tower/issues/547 for details
|
||||
let clone = self.inner.clone();
|
||||
let mut inner = std::mem::replace(&mut self.inner, clone);
|
||||
|
||||
Box::pin(async move {
|
||||
let res: Response<BoxBody> = inner.call(req).await?;
|
||||
|
||||
println!("`MyMiddleware` received the response");
|
||||
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { /* ... */ }))
|
||||
.layer(layer_fn(|inner| MyMiddleware { inner }));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
|
@ -1,20 +0,0 @@
|
|||
# Required dependencies
|
||||
|
||||
To use axum there are a few dependencies you have pull in as well:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
axum = "<latest-version>"
|
||||
hyper = { version = "<latest-version>", features = ["full"] }
|
||||
tokio = { version = "<latest-version>", features = ["full"] }
|
||||
tower = "<latest-version>"
|
||||
```
|
||||
|
||||
The `"full"` feature for hyper and tokio isn't strictly necessary but its
|
||||
the easiest way to get started.
|
||||
|
||||
Note that [`hyper::Server`] is re-exported by axum so if thats all you need
|
||||
then you don't have to explicitly depend on hyper.
|
||||
|
||||
Tower isn't strictly necessary either but helpful for testing. See the
|
||||
testing example in the repo to learn more about testing axum apps.
|
|
@ -1,7 +1,8 @@
|
|||
Types and traits for generating responses.
|
||||
|
||||
# Building responses
|
||||
|
||||
Anything that implements [`IntoResponse`](response::IntoResponse) can be
|
||||
returned from a handler:
|
||||
Anything that implements [`IntoResponse`] can be returned from a handler:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
|
@ -1,286 +0,0 @@
|
|||
# Routing
|
||||
|
||||
[`Router::route`] is the main way to add routes:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(get_slash).post(post_slash))
|
||||
.route("/foo", get(get_foo));
|
||||
|
||||
async fn get_slash() {
|
||||
// `GET /` called
|
||||
}
|
||||
|
||||
async fn post_slash() {
|
||||
// `POST /` called
|
||||
}
|
||||
|
||||
async fn get_foo() {
|
||||
// `GET /foo` called
|
||||
}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Routes can also be dynamic like `/users/:id`. See [extractors](#extractors)
|
||||
for more details.
|
||||
|
||||
You can also define routes separately and merge them with [`Router::merge`].
|
||||
|
||||
Routes are not allowed to overlap and will panic if an overlapping route is
|
||||
added. This also means the order in which routes are added doesn't matter.
|
||||
|
||||
## Wildcard routes
|
||||
|
||||
axum also supports wildcard routes:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
// this matches any request that starts with `/api`
|
||||
.route("/api/*rest", get(|| async { /* ... */ }));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
The matched path can be extracted via [`extract::Path`]:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
routing::get,
|
||||
extract::Path,
|
||||
Router,
|
||||
};
|
||||
|
||||
let app = Router::new().route("/api/*rest", get(|Path(rest): Path<String>| async {
|
||||
// `rest` will be everything after `/api`
|
||||
}));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
## Nesting routes
|
||||
|
||||
Routes can be nested by calling [`Router::nest`](routing::Router::nest):
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
body::{Body, BoxBody},
|
||||
http::Request,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use tower_http::services::ServeFile;
|
||||
use http::Response;
|
||||
|
||||
fn api_routes() -> Router {
|
||||
Router::new()
|
||||
.route("/users", get(|_: Request<Body>| async { /* ... */ }))
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|_: Request<Body>| async { /* ... */ }))
|
||||
.nest("/api", api_routes());
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Note that nested routes will not see the orignal 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.
|
||||
|
||||
Nested routes are similar to wild card 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 app = Router::new()
|
||||
.route("/foo/*rest", get(|uri: Uri| async {
|
||||
// `uri` will contain `/foo`
|
||||
}))
|
||||
.nest("/bar", get(|uri: Uri| async {
|
||||
// `uri` will _not_ contain `/bar`
|
||||
}));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
## Fallback routes
|
||||
|
||||
By default axum will respond with an empty `404 Not Found` response to unhandled requests. To
|
||||
override that you can use [`Router::fallback`]:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
Router,
|
||||
routing::get,
|
||||
handler::Handler,
|
||||
response::IntoResponse,
|
||||
http::{StatusCode, Uri},
|
||||
};
|
||||
|
||||
async fn fallback(uri: Uri) -> impl IntoResponse {
|
||||
(StatusCode::NOT_FOUND, format!("No route for {}", uri))
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/foo", get(|| async { /* ... */ }))
|
||||
.fallback(fallback.into_service());
|
||||
# async {
|
||||
# hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
See [`Router::fallback`] for more details.
|
||||
|
||||
## Routing to any [`Service`]
|
||||
|
||||
axum also supports routing to general [`Service`]s:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
Router,
|
||||
body::Body,
|
||||
routing::service_method_router as service,
|
||||
error_handling::HandleErrorExt,
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
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 who's response body is not `axum::body::BoxBody`
|
||||
// can be wrapped in `axum::service::any` (or one of the other routing filters)
|
||||
// to have the response body mapped
|
||||
service::any(service_fn(|_: Request<Body>| async {
|
||||
let res = Response::new(Body::from("Hi from `GET /`"));
|
||||
Ok::<_, Infallible>(res)
|
||||
}))
|
||||
)
|
||||
.route(
|
||||
"/foo",
|
||||
// This service's response body is `axum::body::BoxBody` so
|
||||
// it can be routed to directly.
|
||||
service_fn(|req: Request<Body>| async move {
|
||||
let body = Body::from(format!("Hi from `{} /foo`", req.method()));
|
||||
let body = axum::body::box_body(body);
|
||||
let res = Response::new(body);
|
||||
Ok::<_, Infallible>(res)
|
||||
})
|
||||
)
|
||||
.route(
|
||||
// GET `/static/Cargo.toml` goes to a service from tower-http
|
||||
"/static/Cargo.toml",
|
||||
service::get(ServeFile::new("Cargo.toml"))
|
||||
// though we must handle any potential errors
|
||||
.handle_error(|error: io::Error| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Unhandled internal error: {}", error),
|
||||
)
|
||||
})
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Routing to arbitrary services in this way has complications for backpressure
|
||||
([`Service::poll_ready`]). See the [`service`] module for more details.
|
||||
|
||||
### Routing to fallible services
|
||||
|
||||
Note that routing to general services has a small gotcha when it comes to
|
||||
errors. axum currently does not support mixing routes to fallible services
|
||||
with infallible handlers. For example this does _not_ compile:
|
||||
|
||||
```compile_fail
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{get, service_method_router as service},
|
||||
http::{Request, Response},
|
||||
body::Body,
|
||||
};
|
||||
use std::io;
|
||||
use tower::service_fn;
|
||||
|
||||
let app = Router::new()
|
||||
// this route cannot fail
|
||||
.route("/foo", get(|| async {}))
|
||||
// this route can fail with io::Error
|
||||
.route(
|
||||
"/",
|
||||
service::get(service_fn(|_req: Request<Body>| async {
|
||||
let contents = tokio::fs::read_to_string("some_file").await?;
|
||||
Ok::<_, io::Error>(Response::new(Body::from(contents)))
|
||||
})),
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
The solution is to use [`handle_error`] and handle the error from the
|
||||
service:
|
||||
|
||||
```
|
||||
use axum::{
|
||||
Router,
|
||||
body::Body,
|
||||
routing::{get, service_method_router as service},
|
||||
response::IntoResponse,
|
||||
http::{Request, Response},
|
||||
error_handling::HandleErrorExt,
|
||||
};
|
||||
use std::{io, convert::Infallible};
|
||||
use tower::service_fn;
|
||||
|
||||
let app = Router::new()
|
||||
// this route cannot fail
|
||||
.route("/foo", get(|| async {}))
|
||||
// this route can fail with io::Error
|
||||
.route(
|
||||
"/",
|
||||
service::get(service_fn(|_req: Request<Body>| async {
|
||||
let contents = tokio::fs::read_to_string("some_file").await?;
|
||||
Ok::<_, io::Error>(Response::new(Body::from(contents)))
|
||||
}))
|
||||
.handle_error(handle_io_error),
|
||||
);
|
||||
|
||||
fn handle_io_error(error: io::Error) -> impl IntoResponse {
|
||||
// ...
|
||||
}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
In this particular case you can also handle the error directly in
|
||||
`service_fn` but that is not possible, if you're routing to a service which
|
||||
you don't control.
|
||||
|
||||
See ["Error handling"](#error-handling) for more details on [`handle_error`]
|
||||
and error handling in general.
|
70
src/docs/routing/fallback.md
Normal file
70
src/docs/routing/fallback.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
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, Uri},
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/foo", get(|| async { /* ... */ }))
|
||||
.fallback(fallback.into_service());
|
||||
|
||||
async fn fallback(uri: Uri) -> impl IntoResponse {
|
||||
(StatusCode::NOT_FOUND, format!("No route for {}", uri))
|
||||
}
|
||||
# async {
|
||||
# hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## When used with `Router::merge`
|
||||
|
||||
If a router with a fallback is merged with another router that also has
|
||||
a fallback the fallback of the second router takes precedence:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
Router,
|
||||
routing::get,
|
||||
handler::Handler,
|
||||
response::IntoResponse,
|
||||
http::{StatusCode, Uri},
|
||||
};
|
||||
|
||||
let one = Router::new()
|
||||
.route("/one", get(|| async {}))
|
||||
.fallback(fallback_one.into_service());
|
||||
|
||||
let two = Router::new()
|
||||
.route("/two", get(|| async {}))
|
||||
.fallback(fallback_two.into_service());
|
||||
|
||||
let app = one.merge(two);
|
||||
|
||||
async fn fallback_one() -> impl IntoResponse {}
|
||||
async fn fallback_two() -> impl IntoResponse {}
|
||||
|
||||
// the fallback for `app` is `fallback_two`
|
||||
# async {
|
||||
# hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
If only one of the routers have a fallback that will be used in the
|
||||
merged router.
|
||||
|
||||
## When used with `Router::nest`
|
||||
|
||||
If a router with a fallback is nested inside another router the fallback
|
||||
of the nested router will be discarded and not used. This is such that
|
||||
the outer router's fallback takes precedence.
|
80
src/docs/routing/into_make_service_with_connect_info.md
Normal file
80
src/docs/routing/into_make_service_with_connect_info.md
Normal file
|
@ -0,0 +1,80 @@
|
|||
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 {
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(
|
||||
app.into_make_service_with_connect_info::<SocketAddr, _>()
|
||||
)
|
||||
.await
|
||||
.expect("server failed");
|
||||
# };
|
||||
```
|
||||
|
||||
You can implement custom a [`Connected`] like so:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
extract::connect_info::{ConnectInfo, Connected},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use hyper::server::conn::AddrStream;
|
||||
|
||||
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<&AddrStream> for MyConnectInfo {
|
||||
fn connect_info(target: &AddrStream) -> Self {
|
||||
MyConnectInfo {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# async {
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(
|
||||
app.into_make_service_with_connect_info::<MyConnectInfo, _>()
|
||||
)
|
||||
.await
|
||||
.expect("server failed");
|
||||
# };
|
||||
```
|
||||
|
||||
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.rs
|
136
src/docs/routing/layer.md
Normal file
136
src/docs/routing/layer.md
Normal file
|
@ -0,0 +1,136 @@
|
|||
Apply a [`tower::Layer`] to the router.
|
||||
|
||||
All requests to the router will be processed by the layer's
|
||||
corresponding middleware.
|
||||
|
||||
This can be used to add additional processing to a request for a group
|
||||
of routes.
|
||||
|
||||
Note this differs from [`handler::Layered`](crate::handler::Layered)
|
||||
which adds a middleware to a single handler.
|
||||
|
||||
# Example
|
||||
|
||||
Adding the [`tower::limit::ConcurrencyLimit`] middleware to a group of
|
||||
routes can be done like so:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
|
||||
|
||||
async fn first_handler() {}
|
||||
|
||||
async fn second_handler() {}
|
||||
|
||||
async fn third_handler() {}
|
||||
|
||||
// All requests to `handler` and `other_handler` will be sent through
|
||||
// `ConcurrencyLimit`
|
||||
let app = Router::new().route("/", get(first_handler))
|
||||
.route("/foo", get(second_handler))
|
||||
.layer(ConcurrencyLimitLayer::new(64))
|
||||
// Request to `GET /bar` will go directly to `third_handler` and
|
||||
// wont be sent through `ConcurrencyLimit`
|
||||
.route("/bar", get(third_handler));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
This is commonly used to add middleware such as tracing/logging to your
|
||||
entire app:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
||||
async fn first_handler() {}
|
||||
|
||||
async fn second_handler() {}
|
||||
|
||||
async fn third_handler() {}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(first_handler))
|
||||
.route("/foo", get(second_handler))
|
||||
.route("/bar", get(third_handler))
|
||||
.layer(TraceLayer::new_for_http());
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# Applying multiple middleware
|
||||
|
||||
Its recommended to use [`tower::ServiceBuilder`] to apply multiple middleware at
|
||||
once, instead of calling `layer` repeatedly:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::get,
|
||||
AddExtensionLayer,
|
||||
Router,
|
||||
};
|
||||
use tower_http::{trace::TraceLayer};
|
||||
use tower::{ServiceBuilder, limit::ConcurrencyLimitLayer};
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State {}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(ConcurrencyLimitLayer::new(64))
|
||||
.layer(AddExtensionLayer::new(State {}))
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# Error handling
|
||||
|
||||
axum's error handling model requires handlers to always return a response.
|
||||
However middleware is one possible way to introduce errors into an application.
|
||||
If hyper receives an error the connection will be closed without sending a
|
||||
response. Thus axum requires those errors to be handled gracefully:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
routing::get,
|
||||
error_handling::HandleErrorLayer,
|
||||
http::StatusCode,
|
||||
BoxError,
|
||||
Router,
|
||||
};
|
||||
use tower::{ServiceBuilder, timeout::TimeoutLayer};
|
||||
use std::time::Duration;
|
||||
|
||||
async fn handler() {}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
// this middleware goes above `TimeoutLayer` because it will receive
|
||||
// errors returned by `TimeoutLayer`
|
||||
.layer(HandleErrorLayer::new(|_: BoxError| StatusCode::REQUEST_TIMEOUT))
|
||||
.layer(TimeoutLayer::new(Duration::from_secs(10)))
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
See [`error_handling`](crate::error_handling) for more details on axum's error
|
||||
handling model.
|
38
src/docs/routing/merge.md
Normal file
38
src/docs/routing/merge.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
Merge two routers into one.
|
||||
|
||||
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
|
||||
// - POST /teams
|
||||
# async {
|
||||
# hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
125
src/docs/routing/nest.md
Normal file
125
src/docs/routing/nest.md
Normal file
|
@ -0,0 +1,125 @@
|
|||
Nest a group of routes (or a [`Service`]) 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
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# How the URI changes
|
||||
|
||||
Note that nested routes will not see the orignal 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);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# Nesting services
|
||||
|
||||
`nest` also accepts any [`Service`]. This can for example be used with
|
||||
[`tower_http::services::ServeDir`] to serve static files from a directory:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
Router,
|
||||
routing::service_method_routing::get,
|
||||
error_handling::HandleErrorExt,
|
||||
http::StatusCode,
|
||||
};
|
||||
use std::{io, convert::Infallible};
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
// Serves files inside the `public` directory at `GET /public/*`
|
||||
let serve_dir_service = ServeDir::new("public")
|
||||
.handle_error(|error: io::Error| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Unhandled internal error: {}", error),
|
||||
)
|
||||
});
|
||||
|
||||
let app = Router::new().nest("/public", get(serve_dir_service));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# Differences to 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 app = Router::new()
|
||||
.route("/foo/*rest", get(|uri: Uri| async {
|
||||
// `uri` will contain `/foo`
|
||||
}))
|
||||
.nest("/bar", get(|uri: Uri| async {
|
||||
// `uri` will _not_ contain `/bar`
|
||||
}));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# 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
|
196
src/docs/routing/route.md
Normal file
196
src/docs/routing/route.md
Normal file
|
@ -0,0 +1,196 @@
|
|||
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.
|
||||
|
||||
`service` is the [`Service`] that should receive the request if the path matches
|
||||
`path`. `service` will commonly be a handler wrapped in a method router like
|
||||
[`get`](crate::routing::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`.
|
||||
|
||||
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`](crate::extract::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`
|
||||
|
||||
Wildcard captures can also be extracted using [`Path`](crate::extract::Path).
|
||||
|
||||
# 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>) {}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
# Routing to any [`Service`]
|
||||
|
||||
axum also supports routing to general [`Service`]s:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
Router,
|
||||
body::Body,
|
||||
routing::service_method_routing as service,
|
||||
error_handling::HandleErrorExt,
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
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 who's response body is not `axum::body::BoxBody`
|
||||
// can be wrapped in `axum::service::any` (or one of the other routing filters)
|
||||
// to have the response body mapped
|
||||
service::any(service_fn(|_: Request<Body>| async {
|
||||
let res = Response::new(Body::from("Hi from `GET /`"));
|
||||
Ok::<_, Infallible>(res)
|
||||
}))
|
||||
)
|
||||
.route(
|
||||
"/foo",
|
||||
// This service's response body is `axum::body::BoxBody` so
|
||||
// it can be routed to directly.
|
||||
service_fn(|req: Request<Body>| async move {
|
||||
let body = Body::from(format!("Hi from `{} /foo`", req.method()));
|
||||
let body = axum::body::box_body(body);
|
||||
let res = Response::new(body);
|
||||
Ok::<_, Infallible>(res)
|
||||
})
|
||||
)
|
||||
.route(
|
||||
// GET `/static/Cargo.toml` goes to a service from tower-http
|
||||
"/static/Cargo.toml",
|
||||
service::get(ServeFile::new("Cargo.toml"))
|
||||
// though we must handle any potential errors
|
||||
.handle_error(|error: io::Error| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Unhandled internal error: {}", error),
|
||||
)
|
||||
})
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Routing to arbitrary services in this way has complications for backpressure
|
||||
([`Service::poll_ready`]). See the [`service_method_routing`] module for more
|
||||
details.
|
||||
|
||||
# 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 {}));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
The static route `/foo` and the dynamic route `/:key` are not considered to
|
||||
overlap and `/foo` will take precedence.
|
||||
|
||||
Take care when using [`Router::nest`] as it behaves like a wildcard route.
|
||||
Therefore this setup panics:
|
||||
|
||||
```rust,should_panic
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
let app = Router::new()
|
||||
// this is similar to `/api/*`
|
||||
.nest("/api", get(|| async {}))
|
||||
// which overlaps with this route
|
||||
.route("/api/users", get(|| async {}));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Also panics if `path` is empty.
|
||||
|
||||
## Nesting
|
||||
|
||||
`route` cannot be used to nest `Router`s. Instead use [`Router::nest`].
|
||||
|
||||
Attempting to will result in a panic:
|
||||
|
||||
```rust,should_panic
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
let app = Router::new().route(
|
||||
"/",
|
||||
Router::new().route("/foo", get(|| async {})),
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
|
@ -1,37 +0,0 @@
|
|||
# Sharing state with handlers
|
||||
|
||||
It is common to share some state between handlers for example to share a
|
||||
pool of database connections or clients to other services. That can be done
|
||||
using the [`AddExtension`] middleware (applied with [`AddExtensionLayer`])
|
||||
and the [`extract::Extension`] extractor:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
AddExtensionLayer,
|
||||
extract,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
struct State {
|
||||
// ...
|
||||
}
|
||||
|
||||
let shared_state = Arc::new(State { /* ... */ });
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.layer(AddExtensionLayer::new(shared_state));
|
||||
|
||||
async fn handler(
|
||||
state: extract::Extension<Arc<State>>,
|
||||
) {
|
||||
let state: Arc<State> = state.0;
|
||||
|
||||
// ...
|
||||
}
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
|
@ -1,29 +0,0 @@
|
|||
# Table of contents
|
||||
|
||||
- [High level features](#high-level-features)
|
||||
- [Compatibility](#compatibility)
|
||||
- [Handlers](#handlers)
|
||||
- [Debugging handler type errors](#debugging-handler-type-errors)
|
||||
- [Routing](#routing)
|
||||
- [Wildcard routes](#wildcard-routes)
|
||||
- [Nesting routes](#nesting-routes)
|
||||
- [Fallback routes](#fallback-routes)
|
||||
- [Routing to any `Service`](#routing-to-any-service)
|
||||
- [Routing to fallible services](#routing-to-fallible-services)
|
||||
- [Extractors](#extractors)
|
||||
- [Common extractors](#common-extractors)
|
||||
- [Applying multiple extractors](#applying-multiple-extractors)
|
||||
- [Optional extractors](#optional-extractors)
|
||||
- [Customizing extractor responses](#customizing-extractor-responses)
|
||||
- [Building responses](#building-responses)
|
||||
- [Error handling](#error-handling)
|
||||
- [Applying middleware](#applying-middleware)
|
||||
- [To individual handlers](#to-individual-handlers)
|
||||
- [To groups of routes](#to-groups-of-routes)
|
||||
- [Applying multiple middleware](#applying-multiple-middleware)
|
||||
- [Commonly used middleware](#commonly-used-middleware)
|
||||
- [Writing your own middleware](#writing-your-own-middleware)
|
||||
- [Sharing state with handlers](#sharing-state-with-handlers)
|
||||
- [Required dependencies](#required-dependencies)
|
||||
- [Examples](#examples)
|
||||
- [Feature flags](#feature-flags)
|
|
@ -1,33 +1,22 @@
|
|||
//! Error handling utilities
|
||||
//!
|
||||
//! See [error handling](../index.html#error-handling) for more details on how
|
||||
//! error handling works in axum.
|
||||
#![doc = include_str!("../docs/error_handling.md")]
|
||||
|
||||
use crate::{
|
||||
body::{box_body, BoxBody},
|
||||
response::IntoResponse,
|
||||
BoxError,
|
||||
};
|
||||
use crate::{body::BoxBody, response::IntoResponse, BoxError};
|
||||
use bytes::Bytes;
|
||||
use futures_util::ready;
|
||||
use http::{Request, Response};
|
||||
use pin_project_lite::pin_project;
|
||||
use std::convert::Infallible;
|
||||
use std::{
|
||||
fmt,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower::{util::Oneshot, ServiceExt as _};
|
||||
use tower_layer::Layer;
|
||||
use tower_service::Service;
|
||||
|
||||
/// [`Layer`] that applies [`HandleErrorLayer`] which is a [`Service`] adapter
|
||||
/// [`Layer`] that applies [`HandleError`] which is a [`Service`] adapter
|
||||
/// that handles errors by converting them into responses.
|
||||
///
|
||||
/// See [error handling](../index.html#error-handling) for more details.
|
||||
/// See [module docs](self) for more details on axum's error handling model.
|
||||
pub struct HandleErrorLayer<F, B> {
|
||||
f: F,
|
||||
_marker: PhantomData<fn() -> B>,
|
||||
|
@ -80,7 +69,7 @@ impl<F, B> fmt::Debug for HandleErrorLayer<F, B> {
|
|||
|
||||
/// A [`Service`] adapter that handles errors by converting them into responses.
|
||||
///
|
||||
/// See [error handling](../index.html#error-handling) for more details.
|
||||
/// See [module docs](self) for more details on axum's error handling model.
|
||||
pub struct HandleError<S, F, B> {
|
||||
inner: S,
|
||||
f: F,
|
||||
|
@ -130,7 +119,7 @@ where
|
|||
{
|
||||
type Response = Response<BoxBody>;
|
||||
type Error = Infallible;
|
||||
type Future = HandleErrorFuture<Oneshot<S, Request<ReqBody>>, F>;
|
||||
type Future = future::HandleErrorFuture<Oneshot<S, Request<ReqBody>>, F>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
|
@ -138,16 +127,17 @@ where
|
|||
}
|
||||
|
||||
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
|
||||
HandleErrorFuture {
|
||||
future::HandleErrorFuture {
|
||||
f: Some(self.f.clone()),
|
||||
inner: self.inner.clone().oneshot(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle errors this service might produce, by mapping them to responses.
|
||||
/// Extension trait to [`Service`] for handling errors by mapping them to
|
||||
/// responses.
|
||||
///
|
||||
/// See [error handling](../index.html#error-handling) for more details.
|
||||
/// See [module docs](self) for more details on axum's error handling model.
|
||||
pub trait HandleErrorExt<B>: Service<Request<B>> + Sized {
|
||||
/// Apply a [`HandleError`] middleware.
|
||||
fn handle_error<F>(self, f: F) -> HandleError<Self, F, B> {
|
||||
|
@ -157,35 +147,55 @@ pub trait HandleErrorExt<B>: Service<Request<B>> + Sized {
|
|||
|
||||
impl<B, S> HandleErrorExt<B> for S where S: Service<Request<B>> {}
|
||||
|
||||
pin_project! {
|
||||
/// Response future for [`HandleError`](super::HandleError).
|
||||
#[derive(Debug)]
|
||||
pub struct HandleErrorFuture<Fut, F> {
|
||||
#[pin]
|
||||
pub(super) inner: Fut,
|
||||
pub(super) f: Option<F>,
|
||||
pub mod future {
|
||||
//! Future types.
|
||||
|
||||
use crate::{
|
||||
body::{box_body, BoxBody},
|
||||
response::IntoResponse,
|
||||
BoxError,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_util::ready;
|
||||
use http::Response;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::convert::Infallible;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
pin_project! {
|
||||
/// Response future for [`HandleError`](super::HandleError).
|
||||
#[derive(Debug)]
|
||||
pub struct HandleErrorFuture<Fut, F> {
|
||||
#[pin]
|
||||
pub(super) inner: Fut,
|
||||
pub(super) f: Option<F>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut, F, E, B, Res> Future for HandleErrorFuture<Fut, F>
|
||||
where
|
||||
Fut: Future<Output = Result<Response<B>, E>>,
|
||||
F: FnOnce(E) -> Res,
|
||||
Res: IntoResponse,
|
||||
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
B::Error: Into<BoxError> + Send + Sync + 'static,
|
||||
{
|
||||
type Output = Result<Response<BoxBody>, Infallible>;
|
||||
impl<Fut, F, E, B, Res> Future for HandleErrorFuture<Fut, F>
|
||||
where
|
||||
Fut: Future<Output = Result<Response<B>, E>>,
|
||||
F: FnOnce(E) -> Res,
|
||||
Res: IntoResponse,
|
||||
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||
B::Error: Into<BoxError> + Send + Sync + 'static,
|
||||
{
|
||||
type Output = Result<Response<BoxBody>, Infallible>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
match ready!(this.inner.poll(cx)) {
|
||||
Ok(res) => Ok(res.map(box_body)).into(),
|
||||
Err(err) => {
|
||||
let f = this.f.take().unwrap();
|
||||
let res = f(err);
|
||||
Ok(res.into_response().map(box_body)).into()
|
||||
match ready!(this.inner.poll(cx)) {
|
||||
Ok(res) => Ok(res.map(box_body)).into(),
|
||||
Err(err) => {
|
||||
let f = this.f.take().unwrap();
|
||||
let res = f(err);
|
||||
Ok(res.into_response().map(box_body)).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//! [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
|
||||
|
||||
use super::{Extension, FromRequest, RequestParts};
|
||||
use crate::{AddExtension, AddExtensionLayer};
|
||||
use crate::{AddExtension, AddExtensionLayer, Router};
|
||||
use async_trait::async_trait;
|
||||
use hyper::server::conn::AddrStream;
|
||||
use std::future::ready;
|
||||
|
@ -25,8 +25,8 @@ use tower_service::Service;
|
|||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
|
||||
pub struct IntoMakeServiceWithConnectInfo<S, C> {
|
||||
svc: S,
|
||||
pub struct IntoMakeServiceWithConnectInfo<B, C> {
|
||||
router: Router<B>,
|
||||
_connect_info: PhantomData<fn() -> C>,
|
||||
}
|
||||
|
||||
|
@ -34,25 +34,21 @@ pub struct IntoMakeServiceWithConnectInfo<S, C> {
|
|||
fn traits() {
|
||||
use crate::tests::*;
|
||||
assert_send::<IntoMakeServiceWithConnectInfo<(), NotSendSync>>();
|
||||
assert_sync::<IntoMakeServiceWithConnectInfo<(), NotSendSync>>();
|
||||
}
|
||||
|
||||
impl<S, C> IntoMakeServiceWithConnectInfo<S, C> {
|
||||
pub(crate) fn new(svc: S) -> Self {
|
||||
impl<B, C> IntoMakeServiceWithConnectInfo<B, C> {
|
||||
pub(crate) fn new(router: Router<B>) -> Self {
|
||||
Self {
|
||||
svc,
|
||||
router,
|
||||
_connect_info: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, C> fmt::Debug for IntoMakeServiceWithConnectInfo<S, C>
|
||||
where
|
||||
S: fmt::Debug,
|
||||
{
|
||||
impl<B, C> fmt::Debug for IntoMakeServiceWithConnectInfo<B, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("IntoMakeServiceWithConnectInfo")
|
||||
.field("svc", &self.svc)
|
||||
.field("router", &self.router)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -77,14 +73,13 @@ impl Connected<&AddrStream> for SocketAddr {
|
|||
}
|
||||
}
|
||||
|
||||
impl<S, C, T> Service<T> for IntoMakeServiceWithConnectInfo<S, C>
|
||||
impl<B, C, T> Service<T> for IntoMakeServiceWithConnectInfo<B, C>
|
||||
where
|
||||
S: Clone,
|
||||
C: Connected<T>,
|
||||
{
|
||||
type Response = AddExtension<S, ConnectInfo<C>>;
|
||||
type Response = AddExtension<Router<B>, ConnectInfo<C>>;
|
||||
type Error = Infallible;
|
||||
type Future = ResponseFuture<Self::Response>;
|
||||
type Future = ResponseFuture<B, C>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
|
@ -93,15 +88,15 @@ where
|
|||
|
||||
fn call(&mut self, target: T) -> Self::Future {
|
||||
let connect_info = ConnectInfo(C::connect_info(target));
|
||||
let svc = AddExtensionLayer::new(connect_info).layer(self.svc.clone());
|
||||
let svc = AddExtensionLayer::new(connect_info).layer(self.router.clone());
|
||||
ResponseFuture::new(ready(Ok(svc)))
|
||||
}
|
||||
}
|
||||
|
||||
opaque_future! {
|
||||
/// Response future for [`IntoMakeServiceWithConnectInfo`].
|
||||
pub type ResponseFuture<T> =
|
||||
std::future::Ready<Result<T, Infallible>>;
|
||||
pub type ResponseFuture<B, C> =
|
||||
std::future::Ready<Result<AddExtension<Router<B>, ConnectInfo<C>>, Infallible>>;
|
||||
}
|
||||
|
||||
/// Extractor for getting connection information produced by a [`Connected`].
|
||||
|
|
|
@ -37,52 +37,38 @@ use tower_service::Service;
|
|||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// extract::{extractor_middleware, FromRequest, RequestParts},
|
||||
/// routing::{get, post},
|
||||
/// Router,
|
||||
/// async_trait,
|
||||
/// extract::{extractor_middleware, FromRequest, RequestParts},
|
||||
/// http::StatusCode,
|
||||
/// routing::{get, post},
|
||||
/// };
|
||||
/// use http::StatusCode;
|
||||
/// use async_trait::async_trait;
|
||||
/// use std::convert::Infallible;
|
||||
///
|
||||
/// // An extractor that performs authorization.
|
||||
/// struct RequireAuth;
|
||||
/// struct MyExtractor;
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B> FromRequest<B> for RequireAuth
|
||||
/// impl<B> FromRequest<B> for MyExtractor
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// {
|
||||
/// type Rejection = StatusCode;
|
||||
/// type Rejection = Infallible;
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// let auth_header = req
|
||||
/// .headers()
|
||||
/// .and_then(|headers| headers.get(http::header::AUTHORIZATION))
|
||||
/// .and_then(|value| value.to_str().ok());
|
||||
///
|
||||
/// if let Some(value) = auth_header {
|
||||
/// if value == "secret" {
|
||||
/// return Ok(Self);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// Err(StatusCode::UNAUTHORIZED)
|
||||
/// # Ok(Self)
|
||||
/// // ...
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// async fn handler() {
|
||||
/// // If we get here the request has been authorized
|
||||
/// }
|
||||
/// async fn handler() {}
|
||||
///
|
||||
/// async fn other_handler() {
|
||||
/// // If we get here the request has been authorized
|
||||
/// }
|
||||
/// async fn other_handler() {}
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(handler))
|
||||
/// .route("/foo", post(other_handler))
|
||||
/// // The extractor will run before all routes
|
||||
/// .layer(extractor_middleware::<RequireAuth>());
|
||||
/// .layer(extractor_middleware::<MyExtractor>());
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
|
|
|
@ -1,159 +1,4 @@
|
|||
//! Types and traits for extracting data from requests.
|
||||
//!
|
||||
//! A handler function is an async function that takes any number of
|
||||
//! "extractors" as arguments. An extractor is a type that implements
|
||||
//! [`FromRequest`](crate::extract::FromRequest).
|
||||
//!
|
||||
//! 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));
|
||||
//! # async {
|
||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! # Defining custom extractors
|
||||
//!
|
||||
//! You can also define your own extractors by implementing [`FromRequest`]:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use axum::{
|
||||
//! async_trait,
|
||||
//! extract::{FromRequest, RequestParts},
|
||||
//! routing::get,
|
||||
//! Router,
|
||||
//! };
|
||||
//! use http::{StatusCode, header::{HeaderValue, USER_AGENT}};
|
||||
//!
|
||||
//! struct ExtractUserAgent(HeaderValue);
|
||||
//!
|
||||
//! #[async_trait]
|
||||
//! impl<B> FromRequest<B> for ExtractUserAgent
|
||||
//! where
|
||||
//! B: Send,
|
||||
//! {
|
||||
//! type Rejection = (StatusCode, &'static str);
|
||||
//!
|
||||
//! async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
//! let user_agent = req.headers().and_then(|headers| headers.get(USER_AGENT));
|
||||
//!
|
||||
//! if let Some(user_agent) = 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));
|
||||
//! # async {
|
||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! Note that only one extractor can consume the request body. If multiple body extractors are
|
||||
//! applied a `500 Internal Server Error` response will be returned.
|
||||
//!
|
||||
//! # Request body extractors
|
||||
//!
|
||||
//! Most of the time your request body type will be [`body::Body`] (a re-export
|
||||
//! of [`hyper::Body`]), which is directly supported by all extractors.
|
||||
//!
|
||||
//! However if you're applying a tower middleware that changes the response you
|
||||
//! might have to apply a different body type to some extractors:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use std::{
|
||||
//! task::{Context, Poll},
|
||||
//! pin::Pin,
|
||||
//! };
|
||||
//! use tower_http::map_request_body::MapRequestBodyLayer;
|
||||
//! use axum::{
|
||||
//! extract::{self, BodyStream},
|
||||
//! body::Body,
|
||||
//! routing::get,
|
||||
//! http::{header::HeaderMap, Request},
|
||||
//! Router,
|
||||
//! };
|
||||
//!
|
||||
//! struct MyBody<B>(B);
|
||||
//!
|
||||
//! impl<B> http_body::Body for MyBody<B>
|
||||
//! where
|
||||
//! B: http_body::Body + Unpin,
|
||||
//! {
|
||||
//! type Data = B::Data;
|
||||
//! type Error = B::Error;
|
||||
//!
|
||||
//! fn poll_data(
|
||||
//! mut self: Pin<&mut Self>,
|
||||
//! cx: &mut Context<'_>,
|
||||
//! ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
//! Pin::new(&mut self.0).poll_data(cx)
|
||||
//! }
|
||||
//!
|
||||
//! fn poll_trailers(
|
||||
//! mut self: Pin<&mut Self>,
|
||||
//! cx: &mut Context<'_>,
|
||||
//! ) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
|
||||
//! Pin::new(&mut self.0).poll_trailers(cx)
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! let app = Router::new()
|
||||
//! .route(
|
||||
//! "/string",
|
||||
//! // `String` works directly with any body type
|
||||
//! get(|_: String| async {})
|
||||
//! )
|
||||
//! .route(
|
||||
//! "/body",
|
||||
//! // `extract::Body` defaults to `axum::body::Body`
|
||||
//! // but can be customized
|
||||
//! get(|_: extract::RawBody<MyBody<Body>>| async {})
|
||||
//! )
|
||||
//! .route(
|
||||
//! "/body-stream",
|
||||
//! // same for `extract::BodyStream`
|
||||
//! get(|_: extract::BodyStream| async {}),
|
||||
//! )
|
||||
//! .route(
|
||||
//! // and `Request<_>`
|
||||
//! "/request",
|
||||
//! get(|_: Request<MyBody<Body>>| async {})
|
||||
//! )
|
||||
//! // middleware that changes the request body type
|
||||
//! .layer(MapRequestBodyLayer::new(MyBody));
|
||||
//! # async {
|
||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! [`body::Body`]: crate::body::Body
|
||||
#![doc = include_str!("../docs/extract.md")]
|
||||
|
||||
use crate::{response::IntoResponse, Error};
|
||||
use async_trait::async_trait;
|
||||
|
|
|
@ -1,10 +1,77 @@
|
|||
//! Async functions that can be used to handle requests.
|
||||
//!
|
||||
#![doc = include_str!("../docs/handlers_intro.md")]
|
||||
//!
|
||||
//! Some examples of handlers:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use bytes::Bytes;
|
||||
//! use http::StatusCode;
|
||||
//!
|
||||
//! // Handler that immediately returns an empty `200 OK` response.
|
||||
//! async fn unit_handler() {}
|
||||
//!
|
||||
//! // Handler that immediately returns an empty `200 OK` response with a plain
|
||||
//! // text body.
|
||||
//! async fn string_handler() -> String {
|
||||
//! "Hello, World!".to_string()
|
||||
//! }
|
||||
//!
|
||||
//! // Handler that buffers the request body and returns it.
|
||||
//! //
|
||||
//! // This works because `Bytes` implements `FromRequest`
|
||||
//! // and therefore can be used as an extractor.
|
||||
//! //
|
||||
//! // `String` and `StatusCode` both implement `IntoResponse` and
|
||||
//! // therefore `Result<String, StatusCode>` also implements `IntoResponse`
|
||||
//! async fn echo(body: Bytes) -> Result<String, StatusCode> {
|
||||
//! if let Ok(string) = String::from_utf8(body.to_vec()) {
|
||||
//! Ok(string)
|
||||
//! } else {
|
||||
//! Err(StatusCode::BAD_REQUEST)
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Debugging handler type errors
|
||||
//!
|
||||
//! For a function to 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 [`FromRequest`].
|
||||
//! - Returns something that implements [`IntoResponse`].
|
||||
//! - If a closure is used it must implement `Clone + Send + Sync` 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<B, T>,
|
||||
//! | ------------- 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-debug] crate.
|
||||
|
||||
use crate::{
|
||||
body::{box_body, BoxBody},
|
||||
extract::{FromRequest, RequestParts},
|
||||
response::IntoResponse,
|
||||
routing::{MethodNotAllowed, MethodRouter},
|
||||
BoxError,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
@ -55,6 +122,10 @@ pub trait Handler<B, T>: Clone + Send + Sized + 'static {
|
|||
/// Note this differs from [`routing::Router::layer`](crate::routing::Router::layer)
|
||||
/// which adds a middleware to a group of routes.
|
||||
///
|
||||
/// If you're applying middleware that produces errors you have to handle the errors
|
||||
/// so they're converted into responses. You can learn more about doing that
|
||||
/// [here](crate::error_handling).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Adding the [`tower::limit::ConcurrencyLimit`] middleware to a handler
|
||||
|
@ -78,9 +149,9 @@ pub trait Handler<B, T>: Clone + Send + Sized + 'static {
|
|||
/// ```
|
||||
fn layer<L>(self, layer: L) -> Layered<L::Service, T>
|
||||
where
|
||||
L: Layer<MethodRouter<Self, B, T, MethodNotAllowed>>,
|
||||
L: Layer<IntoService<Self, B, T>>,
|
||||
{
|
||||
Layered::new(layer.layer(crate::routing::any(self)))
|
||||
Layered::new(layer.layer(self.into_service()))
|
||||
}
|
||||
|
||||
/// Convert the handler into a [`Service`].
|
||||
|
@ -239,6 +310,7 @@ impl<S, T> Layered<S, T> {
|
|||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::routing::MethodRouter;
|
||||
use crate::tests::*;
|
||||
assert_send::<MethodRouter<(), NotSendSync, NotSendSync, ()>>();
|
||||
assert_sync::<MethodRouter<(), NotSendSync, NotSendSync, ()>>();
|
||||
|
|
253
src/lib.rs
253
src/lib.rs
|
@ -1,20 +1,245 @@
|
|||
//! axum is a web application framework that focuses on ergonomics and modularity.
|
||||
//!
|
||||
#![doc = include_str!("docs/table_of_contents.md")]
|
||||
#![doc = include_str!("docs/high_level_features.md")]
|
||||
#![doc = include_str!("docs/compatibility.md")]
|
||||
#![doc = include_str!("docs/example.md")]
|
||||
#![doc = include_str!("docs/handlers.md")]
|
||||
#![doc = include_str!("docs/routing.md")]
|
||||
#![doc = include_str!("docs/extractors.md")]
|
||||
#![doc = include_str!("docs/building_responses.md")]
|
||||
#![doc = include_str!("docs/error_handling.md")]
|
||||
#![doc = include_str!("docs/applying_middleware.md")]
|
||||
#![doc = include_str!("docs/sharing_state_with_handlers.md")]
|
||||
#![doc = include_str!("docs/required_dependencies.md")]
|
||||
#![doc = include_str!("docs/examples.md")]
|
||||
#![doc = include_str!("docs/feature_flags.md")]
|
||||
//! # Table of contents
|
||||
//!
|
||||
//! - [High level features](#high-level-features)
|
||||
//! - [Compatibility](#compatibility)
|
||||
//! - [Example](#example)
|
||||
//! - [Routing](#routing)
|
||||
//! - [Handlers](#handlers)
|
||||
//! - [Extractors](#extractors)
|
||||
//! - [Responses](#responses)
|
||||
//! - [Error handling](#error-handling)
|
||||
//! - [Middleware](#middleware)
|
||||
//! - [Sharing state with handlers](#sharing-state-with-handlers)
|
||||
//! - [Required dependencies](#required-dependencies)
|
||||
//! - [Examples](#examples)
|
||||
//! - [Feature flags](#feature-flags)
|
||||
//!
|
||||
//! # High level features
|
||||
//!
|
||||
//! - Route requests to handlers with a macro free API.
|
||||
//! - Declaratively parse requests using extractors.
|
||||
//! - Simple and predictable error handling model.
|
||||
//! - Generate responses with minimal boilerplate.
|
||||
//! - Take full advantage of the [`tower`] and [`tower-http`] ecosystem of
|
||||
//! middleware, services, and utilities.
|
||||
//!
|
||||
//! In particular the last point is what sets `axum` apart from other frameworks.
|
||||
//! `axum` doesn't have its own middleware system but instead uses
|
||||
//! [`tower::Service`]. This means `axum` gets timeouts, tracing, compression,
|
||||
//! authorization, and more, for free. It also enables you to share middleware with
|
||||
//! applications written using [`hyper`] or [`tonic`].
|
||||
//!
|
||||
//! # Compatibility
|
||||
//!
|
||||
//! axum is designed to work with [tokio] and [hyper]. Runtime and
|
||||
//! transport layer independence is not a goal, at least for the time being.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! The "Hello, World!" of axum is:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use axum::{
|
||||
//! routing::get,
|
||||
//! Router,
|
||||
//! };
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! // build our application with a single route
|
||||
//! let app = Router::new().route("/", get(|| async { "Hello, World!" }));
|
||||
//!
|
||||
//! // run it with hyper on localhost:3000
|
||||
//! axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
//! .serve(app.into_make_service())
|
||||
//! .await
|
||||
//! .unwrap();
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Routing
|
||||
//!
|
||||
//! [`Router`] is used to setup which paths goes to which services:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use axum::{Router, routing::get};
|
||||
//!
|
||||
//! // our router
|
||||
//! let app = Router::new()
|
||||
//! .route("/", get(root))
|
||||
//! .route("/foo", get(foo))
|
||||
//! .route("/foo/bar", get(foo_bar));
|
||||
//!
|
||||
//! // which calls one of these handlers
|
||||
//! async fn root() {}
|
||||
//! async fn foo() {}
|
||||
//! async fn foo_bar() {}
|
||||
//! # async {
|
||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! See [`Router`] for more details on routing.
|
||||
//!
|
||||
//! # Handlers
|
||||
//!
|
||||
#![doc = include_str!("docs/handlers_intro.md")]
|
||||
//!
|
||||
//! See [`handler`](crate::handler) for more details on handlers.
|
||||
//!
|
||||
//! # Extractors
|
||||
//!
|
||||
//! An extractor is a type that implements [`FromRequest`]. Extractors is how
|
||||
//! you pick apart the incoming request to get the parts your handler needs.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use axum::extract::{Path, Query, Json};
|
||||
//! use std::collections::HashMap;
|
||||
//!
|
||||
//! // `Path` gives you the path parameters and deserializes them.
|
||||
//! 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>>) {}
|
||||
//!
|
||||
//! // Buffer the request body and deserialize it as JSON into a
|
||||
//! // `serde_json::Value`. `Json` supports any type that implements
|
||||
//! // `serde::Deserialize`.
|
||||
//! async fn json(Json(payload): Json<serde_json::Value>) {}
|
||||
//! ```
|
||||
//!
|
||||
//! See [`extract`](crate::extract) for more details on extractors.
|
||||
//!
|
||||
//! # Responses
|
||||
//!
|
||||
//! Anything that implements [`IntoResponse`] can be returned from handlers.
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use axum::{
|
||||
//! body::Body,
|
||||
//! routing::get,
|
||||
//! response::Json,
|
||||
//! Router,
|
||||
//! };
|
||||
//! use serde_json::{Value, json};
|
||||
//!
|
||||
//! // `&'static str` becomes a `200 OK` with `content-type: text/plain`
|
||||
//! async fn plain_text() -> &'static str {
|
||||
//! "foo"
|
||||
//! }
|
||||
//!
|
||||
//! // `Json` gives a content-type of `application/json` and works with any type
|
||||
//! // that implements `serde::Serialize`
|
||||
//! async fn json() -> Json<Value> {
|
||||
//! Json(json!({ "data": 42 }))
|
||||
//! }
|
||||
//!
|
||||
//! let app = Router::new()
|
||||
//! .route("/plain_text", get(plain_text))
|
||||
//! .route("/json", get(json));
|
||||
//! # async {
|
||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! See [`response`](crate::response) for more details on building responses.
|
||||
//!
|
||||
//! # Error handling
|
||||
//!
|
||||
//! axum aims to have a simple and predictable error handling model. That means
|
||||
//! it is simple to convert errors into responses and you are guaranteed that
|
||||
//! all errors are handled.
|
||||
//!
|
||||
//! See [`error_handling`](crate::error_handling) for more details on axum's
|
||||
//! error handling model and how to handle errors gracefully.
|
||||
//!
|
||||
//! # Middleware
|
||||
//!
|
||||
#![doc = include_str!("docs/middleware.md")]
|
||||
//!
|
||||
//! # Sharing state with handlers
|
||||
//!
|
||||
//! It is common to share some state between handlers for example to share a
|
||||
//! pool of database connections or clients to other services. That can be done
|
||||
//! using the [`AddExtension`] middleware (applied with [`AddExtensionLayer`])
|
||||
//! and the [`Extension`](crate::extract::Extension) extractor:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use axum::{
|
||||
//! AddExtensionLayer,
|
||||
//! extract::Extension,
|
||||
//! routing::get,
|
||||
//! Router,
|
||||
//! };
|
||||
//! use std::sync::Arc;
|
||||
//!
|
||||
//! struct State {
|
||||
//! // ...
|
||||
//! }
|
||||
//!
|
||||
//! let shared_state = Arc::new(State { /* ... */ });
|
||||
//!
|
||||
//! let app = Router::new()
|
||||
//! .route("/", get(handler))
|
||||
//! .layer(AddExtensionLayer::new(shared_state));
|
||||
//!
|
||||
//! async fn handler(
|
||||
//! Extension(state): Extension<Arc<State>>,
|
||||
//! ) {
|
||||
//! // ...
|
||||
//! }
|
||||
//! # async {
|
||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! # Required dependencies
|
||||
//!
|
||||
//! To use axum there are a few dependencies you have pull in as well:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! axum = "<latest-version>"
|
||||
//! hyper = { version = "<latest-version>", features = ["full"] }
|
||||
//! tokio = { version = "<latest-version>", features = ["full"] }
|
||||
//! tower = "<latest-version>"
|
||||
//! ```
|
||||
//!
|
||||
//! The `"full"` feature for hyper and tokio isn't strictly necessary but its
|
||||
//! the easiest way to get started.
|
||||
//!
|
||||
//! Note that [`hyper::Server`] is re-exported by axum so if thats all you need
|
||||
//! then you don't have to explicitly depend on hyper.
|
||||
//!
|
||||
//! Tower isn't strictly necessary either but helpful for testing. See the
|
||||
//! testing example in the repo to learn more about testing axum apps.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! The axum repo contains [a number of examples][examples] that show how to put all the
|
||||
//! pieces together.
|
||||
//!
|
||||
//! # Feature flags
|
||||
//!
|
||||
//! axum uses a set of [feature flags] to reduce the amount of compiled and
|
||||
//! optional dependencies.
|
||||
//!
|
||||
//! The following optional features are available:
|
||||
//!
|
||||
//! Name | Description | Default?
|
||||
//! ---|---|---
|
||||
//! `headers` | Enables extracting typed headers via [`TypedHeader`] | No
|
||||
//! `http1` | Enables hyper's `http1` feature | Yes
|
||||
//! `http2` | Enables hyper's `http2` feature | No
|
||||
//! `json` | Enables the [`Json`] type and some similar convenience functionality | Yes
|
||||
//! `multipart` | Enables parsing `multipart/form-data` requests with [`Multipart`] | No
|
||||
//! `tower-log` | Enables `tower`'s `log` feature | Yes
|
||||
//! `ws` | Enables WebSockets support via [`extract::ws`] | No
|
||||
//!
|
||||
//! [`TypedHeader`]: crate::extract::TypedHeader
|
||||
//! [`Multipart`]: crate::extract::Multipart
|
||||
//! [`tower`]: https://crates.io/crates/tower
|
||||
//! [`tower-http`]: https://crates.io/crates/tower-http
|
||||
//! [`tokio`]: http://crates.io/crates/tokio
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Types and traits for generating responses.
|
||||
#![doc = include_str!("../docs/response.md")]
|
||||
|
||||
use crate::{
|
||||
body::{box_body, BoxBody},
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::{convert::Infallible, future::ready};
|
|||
use tower::util::Oneshot;
|
||||
|
||||
pub use super::{
|
||||
into_make_service::IntoMakeService, method_not_allowed::MethodNotAllowedFuture,
|
||||
into_make_service::IntoMakeServiceFuture, method_not_allowed::MethodNotAllowedFuture,
|
||||
route::RouteFuture,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use super::Router;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
fmt,
|
||||
future::ready,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
@ -8,24 +10,36 @@ use tower_service::Service;
|
|||
/// A [`MakeService`] that produces axum router services.
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IntoMakeService<S> {
|
||||
service: S,
|
||||
pub struct IntoMakeService<B> {
|
||||
router: Router<B>,
|
||||
}
|
||||
|
||||
impl<S> IntoMakeService<S> {
|
||||
pub(super) fn new(service: S) -> Self {
|
||||
Self { service }
|
||||
impl<B> IntoMakeService<B> {
|
||||
pub(super) fn new(router: Router<B>) -> Self {
|
||||
Self { router }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> Service<T> for IntoMakeService<S>
|
||||
where
|
||||
S: Clone,
|
||||
{
|
||||
type Response = S;
|
||||
impl<B> Clone for IntoMakeService<B> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
router: self.router.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for IntoMakeService<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("IntoMakeService")
|
||||
.field("router", &self.router)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, T> Service<T> for IntoMakeService<B> {
|
||||
type Response = Router<B>;
|
||||
type Error = Infallible;
|
||||
type Future = MakeRouteServiceFuture<S>;
|
||||
type Future = IntoMakeServiceFuture<B>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
|
@ -33,25 +47,25 @@ where
|
|||
}
|
||||
|
||||
fn call(&mut self, _target: T) -> Self::Future {
|
||||
MakeRouteServiceFuture::new(ready(Ok(self.service.clone())))
|
||||
IntoMakeServiceFuture::new(ready(Ok(self.router.clone())))
|
||||
}
|
||||
}
|
||||
|
||||
opaque_future! {
|
||||
/// Response future from [`MakeRouteService`] services.
|
||||
pub type MakeRouteServiceFuture<S> =
|
||||
std::future::Ready<Result<S, Infallible>>;
|
||||
/// Response future for [`IntoMakeService`].
|
||||
pub type IntoMakeServiceFuture<B> =
|
||||
std::future::Ready<Result<Router<B>, Infallible>>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::body::Body;
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::tests::*;
|
||||
|
||||
assert_send::<IntoMakeService<()>>();
|
||||
assert_sync::<IntoMakeService<()>>();
|
||||
assert_send::<IntoMakeService<Body>>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ use tower_layer::Layer;
|
|||
use tower_service::Service;
|
||||
|
||||
pub mod future;
|
||||
pub mod handler_method_router;
|
||||
pub mod service_method_router;
|
||||
pub mod handler_method_routing;
|
||||
pub mod service_method_routing;
|
||||
|
||||
mod into_make_service;
|
||||
mod method_filter;
|
||||
|
@ -43,7 +43,7 @@ pub use self::{
|
|||
};
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use self::handler_method_router::{
|
||||
pub use self::handler_method_routing::{
|
||||
any, delete, get, head, on, options, patch, post, put, trace, MethodRouter,
|
||||
};
|
||||
|
||||
|
@ -103,7 +103,7 @@ where
|
|||
{
|
||||
/// Create a new `Router`.
|
||||
///
|
||||
/// Unless you add additional routes this will respond to `404 Not Found` to
|
||||
/// Unless you add additional routes this will respond with `404 Not Found` to
|
||||
/// all requests.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
@ -113,81 +113,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Add another route to the router.
|
||||
///
|
||||
/// `path` is a string of path segments separated by `/`. Each segment
|
||||
/// can be either concrete, a capture, or a wildcard:
|
||||
///
|
||||
/// - `/foo/bar/baz` will only match requests where the path is `/foo/bar/bar`.
|
||||
/// - `/:foo` will match any route with exactly one segment _and_ it will
|
||||
/// capture the first segment and store it at the key `foo`.
|
||||
/// - `/foo/bar/*rest` will match all requests that start with `/foo/bar`
|
||||
/// and any number of segments after that. It will also create a capture
|
||||
/// with the key `rest` that contains the matched segments.
|
||||
///
|
||||
/// `service` is the [`Service`] that should receive the request if the path
|
||||
/// matches `path`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```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() { /* ... */ }
|
||||
///
|
||||
/// async fn do_users_action() { /* ... */ }
|
||||
///
|
||||
/// async fn serve_asset(Path(path): Path<String>) { /* ... */ }
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the route overlaps with another route:
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use axum::{routing::get, Router};
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(|| async {}))
|
||||
/// .route("/", get(|| async {}));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// This also applies to `nest` which is similar to a wildcard route:
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use axum::{routing::get, Router};
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// // this is similar to `/api/*`
|
||||
/// .nest("/api", get(|| async {}))
|
||||
/// // which overlaps with this route
|
||||
/// .route("/api/users", get(|| async {}));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// Also panics if `path` is empty.
|
||||
pub fn route<T>(mut self, path: &str, svc: T) -> Self
|
||||
#[doc = include_str!("../docs/routing/route.md")]
|
||||
pub fn route<T>(mut self, path: &str, service: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
|
||||
+ Clone
|
||||
|
@ -199,7 +126,7 @@ where
|
|||
panic!("Invalid route: empty path");
|
||||
}
|
||||
|
||||
let svc = match try_downcast::<Router<B>, _>(svc) {
|
||||
let service = match try_downcast::<Router<B>, _>(service) {
|
||||
Ok(_) => {
|
||||
panic!("Invalid route: `Router::route` cannot be used with `Router`s. Use `Router::nest` instead")
|
||||
}
|
||||
|
@ -212,130 +139,12 @@ where
|
|||
panic!("Invalid route: {}", err);
|
||||
}
|
||||
|
||||
self.routes.insert(id, Route::new(svc));
|
||||
self.routes.insert(id, Route::new(service));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Nest a group of routes (or a [`Service`]) at some path.
|
||||
///
|
||||
/// This allows you to break your application into smaller pieces and compose
|
||||
/// them together.
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use http::Uri;
|
||||
///
|
||||
/// async fn users_get(uri: Uri) {
|
||||
/// // `uri` will be `/users` since `nest` strips the matching prefix.
|
||||
/// // use `OriginalUri` to always get the full URI.
|
||||
/// }
|
||||
///
|
||||
/// async fn users_post() {}
|
||||
///
|
||||
/// async fn careers() {}
|
||||
///
|
||||
/// let users_api = Router::new().route("/users", get(users_get).post(users_post));
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .nest("/api", users_api)
|
||||
/// .route("/careers", get(careers));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// Note that nested routes will not see the orignal 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.
|
||||
///
|
||||
/// Take care when using `nest` together with dynamic routes as nesting also
|
||||
/// captures from the outer routes:
|
||||
///
|
||||
/// ```
|
||||
/// 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);
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// `nest` also accepts any [`Service`]. This can for example be used with
|
||||
/// [`tower_http::services::ServeDir`] to serve static files from a directory:
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// routing::service_method_router::get,
|
||||
/// error_handling::HandleErrorExt,
|
||||
/// http::StatusCode,
|
||||
/// };
|
||||
/// use std::{io, convert::Infallible};
|
||||
/// use tower_http::services::ServeDir;
|
||||
///
|
||||
/// // Serves files inside the `public` directory at `GET /public/*`
|
||||
/// let serve_dir_service = ServeDir::new("public")
|
||||
/// .handle_error(|error: io::Error| {
|
||||
/// (
|
||||
/// StatusCode::INTERNAL_SERVER_ERROR,
|
||||
/// format!("Unhandled internal error: {}", error),
|
||||
/// )
|
||||
/// });
|
||||
///
|
||||
/// let app = Router::new().nest("/public", get(serve_dir_service));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// # Differences to 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 app = Router::new()
|
||||
/// .route("/foo/*rest", get(|uri: Uri| async {
|
||||
/// // `uri` will contain `/foo`
|
||||
/// }))
|
||||
/// .nest("/bar", get(|uri: Uri| async {
|
||||
/// // `uri` will _not_ contain `/bar`
|
||||
/// }));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// # 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
|
||||
#[doc = include_str!("../docs/routing/nest.md")]
|
||||
pub fn nest<T>(mut self, path: &str, svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
|
||||
|
@ -362,6 +171,7 @@ where
|
|||
let Router {
|
||||
mut routes,
|
||||
node,
|
||||
// discard the fallback of the nested router
|
||||
fallback: _,
|
||||
} = router;
|
||||
|
||||
|
@ -392,73 +202,33 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Apply a [`tower::Layer`] to the router.
|
||||
///
|
||||
/// All requests to the router will be processed by the layer's
|
||||
/// corresponding middleware.
|
||||
///
|
||||
/// This can be used to add additional processing to a request for a group
|
||||
/// of routes.
|
||||
///
|
||||
/// Note this differs from [`handler::Layered`](crate::handler::Layered)
|
||||
/// which adds a middleware to a single handler.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Adding the [`tower::limit::ConcurrencyLimit`] middleware to a group of
|
||||
/// routes can be done like so:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
|
||||
///
|
||||
/// async fn first_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn second_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn third_handler() { /* ... */ }
|
||||
///
|
||||
/// // All requests to `handler` and `other_handler` will be sent through
|
||||
/// // `ConcurrencyLimit`
|
||||
/// let app = Router::new().route("/", get(first_handler))
|
||||
/// .route("/foo", get(second_handler))
|
||||
/// .layer(ConcurrencyLimitLayer::new(64))
|
||||
/// // Request to `GET /bar` will go directly to `third_handler` and
|
||||
/// // wont be sent through `ConcurrencyLimit`
|
||||
/// .route("/bar", get(third_handler));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// This is commonly used to add middleware such as tracing/logging to your
|
||||
/// entire app:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use tower_http::trace::TraceLayer;
|
||||
///
|
||||
/// async fn first_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn second_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn third_handler() { /* ... */ }
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(first_handler))
|
||||
/// .route("/foo", get(second_handler))
|
||||
/// .route("/bar", get(third_handler))
|
||||
/// .layer(TraceLayer::new_for_http());
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/routing/merge.md")]
|
||||
pub fn merge(mut self, other: Router<B>) -> Self {
|
||||
let Router {
|
||||
routes,
|
||||
node,
|
||||
fallback,
|
||||
} = other;
|
||||
|
||||
if let Err(err) = self.node.merge(node) {
|
||||
panic!("Invalid route: {}", err);
|
||||
}
|
||||
|
||||
for (id, route) in routes {
|
||||
assert!(self.routes.insert(id, route).is_none());
|
||||
}
|
||||
|
||||
self.fallback = match (self.fallback, fallback) {
|
||||
(Fallback::Default(_), pick @ Fallback::Default(_)) => pick,
|
||||
(Fallback::Default(_), pick @ Fallback::Custom(_)) => pick,
|
||||
(pick @ Fallback::Custom(_), Fallback::Default(_)) => pick,
|
||||
(Fallback::Custom(_), pick @ Fallback::Custom(_)) => pick,
|
||||
};
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[doc = include_str!("../docs/routing/layer.md")]
|
||||
pub fn layer<L, LayeredReqBody, LayeredResBody>(self, layer: L) -> Router<LayeredReqBody>
|
||||
where
|
||||
L: Layer<Route<B>>,
|
||||
|
@ -496,6 +266,19 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[doc = include_str!("../docs/routing/fallback.md")]
|
||||
pub fn fallback<T>(mut self, svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
self.fallback = Fallback::Custom(Route::new(svc));
|
||||
self
|
||||
}
|
||||
|
||||
/// Convert this router into a [`MakeService`], that is a [`Service`] who's
|
||||
/// response is another service.
|
||||
///
|
||||
|
@ -519,234 +302,20 @@ where
|
|||
/// ```
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
pub fn into_make_service(self) -> IntoMakeService<Self> {
|
||||
pub fn into_make_service(self) -> IntoMakeService<B> {
|
||||
IntoMakeService::new(self)
|
||||
}
|
||||
|
||||
/// 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:
|
||||
///
|
||||
/// ```
|
||||
/// 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 {
|
||||
/// axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
/// .serve(
|
||||
/// app.into_make_service_with_connect_info::<SocketAddr, _>()
|
||||
/// )
|
||||
/// .await
|
||||
/// .expect("server failed");
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// You can implement custom a [`Connected`] like so:
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// extract::connect_info::{ConnectInfo, Connected},
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use hyper::server::conn::AddrStream;
|
||||
///
|
||||
/// 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<&AddrStream> for MyConnectInfo {
|
||||
/// fn connect_info(target: &AddrStream) -> Self {
|
||||
/// MyConnectInfo {
|
||||
/// // ...
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # async {
|
||||
/// axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
/// .serve(
|
||||
/// app.into_make_service_with_connect_info::<MyConnectInfo, _>()
|
||||
/// )
|
||||
/// .await
|
||||
/// .expect("server failed");
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// 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.rs
|
||||
#[doc = include_str!("../docs/routing/into_make_service_with_connect_info.md")]
|
||||
pub fn into_make_service_with_connect_info<C, Target>(
|
||||
self,
|
||||
) -> IntoMakeServiceWithConnectInfo<Self, C>
|
||||
) -> IntoMakeServiceWithConnectInfo<B, C>
|
||||
where
|
||||
C: Connected<Target>,
|
||||
{
|
||||
IntoMakeServiceWithConnectInfo::new(self)
|
||||
}
|
||||
|
||||
/// Merge two routers into one.
|
||||
///
|
||||
/// This is useful for breaking apps into smaller pieces and combining them
|
||||
/// into one.
|
||||
///
|
||||
/// ```
|
||||
/// 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 = user_routes.merge(team_routes);
|
||||
/// # async {
|
||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub fn merge(mut self, other: Router<B>) -> Self {
|
||||
let Router {
|
||||
routes,
|
||||
node,
|
||||
fallback,
|
||||
} = other;
|
||||
|
||||
if let Err(err) = self.node.merge(node) {
|
||||
panic!("Invalid route: {}", err);
|
||||
}
|
||||
|
||||
for (id, route) in routes {
|
||||
assert!(self.routes.insert(id, route).is_none());
|
||||
}
|
||||
|
||||
self.fallback = match (self.fallback, fallback) {
|
||||
(Fallback::Default(_), pick @ Fallback::Default(_)) => pick,
|
||||
(Fallback::Default(_), pick @ Fallback::Custom(_)) => pick,
|
||||
(pick @ Fallback::Custom(_), Fallback::Default(_)) => pick,
|
||||
(Fallback::Custom(_), pick @ Fallback::Custom(_)) => pick,
|
||||
};
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// 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, Uri},
|
||||
/// };
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/foo", get(|| async { /* ... */ }))
|
||||
/// .fallback(fallback.into_service());
|
||||
///
|
||||
/// async fn fallback(uri: Uri) -> impl IntoResponse {
|
||||
/// (StatusCode::NOT_FOUND, format!("No route for {}", uri))
|
||||
/// }
|
||||
/// # async {
|
||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// ## When used with `Router::merge`
|
||||
///
|
||||
/// If a router with a fallback is merged with another router that also has
|
||||
/// a fallback the fallback of the second router will be used:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// routing::get,
|
||||
/// handler::Handler,
|
||||
/// response::IntoResponse,
|
||||
/// http::{StatusCode, Uri},
|
||||
/// };
|
||||
///
|
||||
/// let one = Router::new()
|
||||
/// .route("/one", get(|| async { /* ... */ }))
|
||||
/// .fallback(fallback_one.into_service());
|
||||
///
|
||||
/// let two = Router::new()
|
||||
/// .route("/two", get(|| async { /* ... */ }))
|
||||
/// .fallback(fallback_two.into_service());
|
||||
///
|
||||
/// let app = one.merge(two);
|
||||
///
|
||||
/// async fn fallback_one() -> impl IntoResponse { /* ... */ }
|
||||
/// async fn fallback_two() -> impl IntoResponse { /* ... */ }
|
||||
///
|
||||
/// // the fallback for `app` is `fallback_two`
|
||||
/// # async {
|
||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// If only one of the routers have a fallback that will be used in the
|
||||
/// merged router.
|
||||
///
|
||||
/// ## When used with `Router::nest`
|
||||
///
|
||||
/// If a router with a fallback is nested inside another router the fallback
|
||||
/// of the nested router will be discarded and not used. This is such that
|
||||
/// the outer router's fallback takes precedence.
|
||||
pub fn fallback<T>(mut self, svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
self.fallback = Fallback::Custom(Route::new(svc));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call_route(&self, match_: matchit::Match<&RouteId>, mut req: Request<B>) -> RouterFuture<B> {
|
||||
let id = *match_.value;
|
||||
|
|
|
@ -16,7 +16,8 @@ use tower_service::Service;
|
|||
|
||||
/// How routes are stored inside a [`Router`](super::Router).
|
||||
///
|
||||
/// You normally shouldn't need to care about this type.
|
||||
/// You normally shouldn't need to care about this type. Its used in
|
||||
/// [`Router::layer`](super::Router::layer).
|
||||
pub struct Route<B = Body>(CloneBoxService<Request<B>, Response<BoxBody>, Infallible>);
|
||||
|
||||
impl<B> Route<B> {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
//! use tower_http::services::Redirect;
|
||||
//! use axum::{
|
||||
//! body::Body,
|
||||
//! routing::{get, service_method_router as service},
|
||||
//! routing::{get, service_method_routing as service},
|
||||
//! http::Request,
|
||||
//! Router,
|
||||
//! };
|
||||
|
@ -148,7 +148,7 @@ where
|
|||
/// use axum::{
|
||||
/// http::Request,
|
||||
/// Router,
|
||||
/// routing::service_method_router as service,
|
||||
/// routing::service_method_routing as service,
|
||||
/// };
|
||||
/// use http::Response;
|
||||
/// use std::convert::Infallible;
|
||||
|
@ -244,7 +244,7 @@ where
|
|||
/// http::Request,
|
||||
/// routing::on,
|
||||
/// Router,
|
||||
/// routing::{MethodFilter, service_method_router as service},
|
||||
/// routing::{MethodFilter, service_method_routing as service},
|
||||
/// };
|
||||
/// use http::Response;
|
||||
/// use std::convert::Infallible;
|
||||
|
@ -327,7 +327,7 @@ impl<S, F, B> MethodRouter<S, F, B> {
|
|||
/// use axum::{
|
||||
/// http::Request,
|
||||
/// Router,
|
||||
/// routing::{MethodFilter, on, service_method_router as service},
|
||||
/// routing::{MethodFilter, on, service_method_routing as service},
|
||||
/// };
|
||||
/// use http::Response;
|
||||
/// use std::convert::Infallible;
|
||||
|
@ -428,7 +428,7 @@ impl<S, F, B> MethodRouter<S, F, B> {
|
|||
/// use axum::{
|
||||
/// http::Request,
|
||||
/// Router,
|
||||
/// routing::{MethodFilter, on, service_method_router as service},
|
||||
/// routing::{MethodFilter, on, service_method_routing as service},
|
||||
/// };
|
||||
/// use http::Response;
|
||||
/// use std::convert::Infallible;
|
|
@ -38,7 +38,7 @@ mod for_handlers {
|
|||
|
||||
mod for_services {
|
||||
use super::*;
|
||||
use crate::routing::service_method_router::get;
|
||||
use crate::routing::service_method_routing::get;
|
||||
use http::header::HeaderValue;
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
@ -196,7 +196,7 @@ async fn many_ors() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn services() {
|
||||
use crate::routing::service_method_router::get;
|
||||
use crate::routing::service_method_routing::get;
|
||||
|
||||
let app = Router::new()
|
||||
.route(
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
extract::{self, Path},
|
||||
handler::Handler,
|
||||
response::IntoResponse,
|
||||
routing::{any, delete, get, on, patch, post, service_method_router as service, MethodFilter},
|
||||
routing::{any, delete, get, on, patch, post, service_method_routing as service, MethodFilter},
|
||||
Json, Router,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
|
|
Loading…
Add table
Reference in a new issue