diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d4685d8..67a1e62e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/Cargo.toml b/Cargo.toml index 69ddc625..9adbb91b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 8a53a1c5..340a9839 100644 --- a/README.md +++ b/README.md @@ -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, diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index 466caceb..a42cd56f 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -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 FromRequest for RequireAuth + where + B: Send, + { + type Rejection = StatusCode; + + async fn from_request(req: &mut RequestParts) -> Result { + 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::()); + + axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); } - -async fn handler() -> Html<&'static str> { - Html("

Hello, World!

") -} diff --git a/examples/sse/src/main.rs b/examples/sse/src/main.rs index 8579f892..aa572ef3 100644 --- a/examples/sse/src/main.rs +++ b/examples/sse/src/main.rs @@ -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}; diff --git a/examples/static-file-server/src/main.rs b/examples/static-file-server/src/main.rs index 724b6e66..5524a078 100644 --- a/examples/static-file-server/src/main.rs +++ b/examples/static-file-server/src/main.rs @@ -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; diff --git a/examples/websockets/src/main.rs b/examples/websockets/src/main.rs index 69c41a3d..1f744878 100644 --- a/examples/websockets/src/main.rs +++ b/examples/websockets/src/main.rs @@ -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; diff --git a/src/docs/applying_middleware.md b/src/docs/applying_middleware.md deleted file mode 100644 index 42246dee..00000000 --- a/src/docs/applying_middleware.md +++ /dev/null @@ -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| 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(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) -> Result, Infallible> { - Ok(req) -} - -async fn map_response(res: Response) -> Result, 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 { - inner: S, -} - -impl Service> for MyMiddleware -where - S: Service, Response = Response> + 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>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx) - } - - fn call(&mut self, mut req: Request) -> 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 = 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(); -# }; -``` diff --git a/src/docs/compatibility.md b/src/docs/compatibility.md deleted file mode 100644 index 4634e385..00000000 --- a/src/docs/compatibility.md +++ /dev/null @@ -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. diff --git a/src/docs/error_handling.md b/src/docs/error_handling.md index 1358848b..a87532a1 100644 --- a/src/docs/error_handling.md +++ b/src/docs/error_handling.md @@ -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 { + # 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::() { + ( + 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 diff --git a/src/docs/example.md b/src/docs/example.md deleted file mode 100644 index ccc060d8..00000000 --- a/src/docs/example.md +++ /dev/null @@ -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(); -} -``` diff --git a/src/docs/examples.md b/src/docs/examples.md deleted file mode 100644 index e27d4c5b..00000000 --- a/src/docs/examples.md +++ /dev/null @@ -1,4 +0,0 @@ -# Examples - -The axum repo contains [a number of examples][examples] that show how to put all the -pieces together. diff --git a/src/docs/extract.md b/src/docs/extract.md new file mode 100644 index 00000000..97cae977 --- /dev/null +++ b/src/docs/extract.md @@ -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) { + // ... +} + +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) {} + +// `Query` gives you the query parameters and deserializes them. +async fn query(Query(params): Query>) {} + +// `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) {} + +// `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) {} + +// `Request` gives you the whole request for maximum control +async fn request(request: Request) {} + +// `Extension` extracts data from "request extensions" +// This is commonly used to share state with handlers +async fn extension(Extension(state): Extension) {} + +#[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, + pagination: Option>, +) { + 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, + 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) { + // ... +} + +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>) { + 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, 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` 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, 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::(&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::() { + 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 FromRequest for ExtractUserAgent +where + B: Send, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request(req: &mut RequestParts) -> Result { + 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); + +impl http_body::Body for MyBody +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>> { + Pin::new(&mut self.0).poll_data(cx) + } + + fn poll_trailers( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, 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>| async {}) + ) + .route( + "/body-stream", + // same for `extract::BodyStream` + get(|_: extract::BodyStream| async {}), + ) + .route( + // and `Request<_>` + "/request", + get(|_: Request>| 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 diff --git a/src/docs/extractors.md b/src/docs/extractors.md deleted file mode 100644 index b1f61e94..00000000 --- a/src/docs/extractors.md +++ /dev/null @@ -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) { - // ... -} -# 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) {} - -// `Query` gives you the query parameters and deserializes them. -async fn query(Query(params): Query>) {} - -// `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) {} - -// `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) {} - -// `Request` gives you the whole request for maximum control -async fn request(request: Request) {} - -// `Extension` extracts data from "request extensions" -// See the "Sharing state with handlers" section for more details -async fn extension(Extension(state): Extension) {} - -#[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, - pagination: Option>, -) { - 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, - 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) { - // ... -} - -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>) { - 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, 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` 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. diff --git a/src/docs/feature_flags.md b/src/docs/feature_flags.md deleted file mode 100644 index 090261eb..00000000 --- a/src/docs/feature_flags.md +++ /dev/null @@ -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`]. diff --git a/src/docs/handlers.md b/src/docs/handlers.md deleted file mode 100644 index 18d91975..00000000 --- a/src/docs/handlers.md +++ /dev/null @@ -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 { - 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, - | ------------- 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. diff --git a/src/docs/handlers_intro.md b/src/docs/handlers_intro.md new file mode 100644 index 00000000..4cc63b61 --- /dev/null +++ b/src/docs/handlers_intro.md @@ -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 diff --git a/src/docs/high_level_features.md b/src/docs/high_level_features.md deleted file mode 100644 index 6755fa7e..00000000 --- a/src/docs/high_level_features.md +++ /dev/null @@ -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`]. diff --git a/src/docs/middleware.md b/src/docs/middleware.md new file mode 100644 index 00000000..3282156c --- /dev/null +++ b/src/docs/middleware.md @@ -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(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) -> Result, Infallible> { + Ok(req) +} + +async fn map_response(res: Response) -> Result, 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 { + inner: S, +} + +impl Service> for MyMiddleware +where + S: Service, Response = Response> + Clone + Send + 'static, + S::Future: Send + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, mut req: Request) -> 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 = 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(); +# }; +``` diff --git a/src/docs/required_dependencies.md b/src/docs/required_dependencies.md deleted file mode 100644 index f87540de..00000000 --- a/src/docs/required_dependencies.md +++ /dev/null @@ -1,20 +0,0 @@ -# Required dependencies - -To use axum there are a few dependencies you have pull in as well: - -```toml -[dependencies] -axum = "" -hyper = { version = "", features = ["full"] } -tokio = { version = "", features = ["full"] } -tower = "" -``` - -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. diff --git a/src/docs/building_responses.md b/src/docs/response.md similarity index 96% rename from src/docs/building_responses.md rename to src/docs/response.md index b8e20b44..e3ef5883 100644 --- a/src/docs/building_responses.md +++ b/src/docs/response.md @@ -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::{ diff --git a/src/docs/routing.md b/src/docs/routing.md deleted file mode 100644 index ca4b662e..00000000 --- a/src/docs/routing.md +++ /dev/null @@ -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| 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| async { /* ... */ })) -} - -let app = Router::new() - .route("/", get(|_: Request| 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| 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| 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| 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| 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. diff --git a/src/docs/routing/fallback.md b/src/docs/routing/fallback.md new file mode 100644 index 00000000..6648db16 --- /dev/null +++ b/src/docs/routing/fallback.md @@ -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. diff --git a/src/docs/routing/into_make_service_with_connect_info.md b/src/docs/routing/into_make_service_with_connect_info.md new file mode 100644 index 00000000..beba75a7 --- /dev/null +++ b/src/docs/routing/into_make_service_with_connect_info.md @@ -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) -> String { + format!("Hello {}", addr) +} + +# async { +axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) + .serve( + app.into_make_service_with_connect_info::() + ) + .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, +) -> 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::() + ) + .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 diff --git a/src/docs/routing/layer.md b/src/docs/routing/layer.md new file mode 100644 index 00000000..5200ba8d --- /dev/null +++ b/src/docs/routing/layer.md @@ -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. diff --git a/src/docs/routing/merge.md b/src/docs/routing/merge.md new file mode 100644 index 00000000..78263029 --- /dev/null +++ b/src/docs/routing/merge.md @@ -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(); +# }; +``` diff --git a/src/docs/routing/nest.md b/src/docs/routing/nest.md new file mode 100644 index 00000000..468968dd --- /dev/null +++ b/src/docs/routing/nest.md @@ -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>) { + // 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 diff --git a/src/docs/routing/route.md b/src/docs/routing/route.md new file mode 100644 index 00000000..6e880694 --- /dev/null +++ b/src/docs/routing/route.md @@ -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) {} + +async fn do_users_action(Path((version, id)): Path<(String, u64)>) {} + +async fn serve_asset(Path(path): Path) {} +# 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| 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| 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(); +# }; +``` diff --git a/src/docs/sharing_state_with_handlers.md b/src/docs/sharing_state_with_handlers.md deleted file mode 100644 index e1266649..00000000 --- a/src/docs/sharing_state_with_handlers.md +++ /dev/null @@ -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>, -) { - let state: Arc = state.0; - - // ... -} -# async { -# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); -# }; -``` diff --git a/src/docs/table_of_contents.md b/src/docs/table_of_contents.md deleted file mode 100644 index bf3d5d98..00000000 --- a/src/docs/table_of_contents.md +++ /dev/null @@ -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) diff --git a/src/error_handling/mod.rs b/src/error_handling/mod.rs index 5264c200..176ba608 100644 --- a/src/error_handling/mod.rs +++ b/src/error_handling/mod.rs @@ -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: F, _marker: PhantomData B>, @@ -80,7 +69,7 @@ impl fmt::Debug for HandleErrorLayer { /// 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 { inner: S, f: F, @@ -130,7 +119,7 @@ where { type Response = Response; type Error = Infallible; - type Future = HandleErrorFuture>, F>; + type Future = future::HandleErrorFuture>, F>; #[inline] fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { @@ -138,16 +127,17 @@ where } fn call(&mut self, req: Request) -> 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: Service> + Sized { /// Apply a [`HandleError`] middleware. fn handle_error(self, f: F) -> HandleError { @@ -157,35 +147,55 @@ pub trait HandleErrorExt: Service> + Sized { impl HandleErrorExt for S where S: Service> {} -pin_project! { - /// Response future for [`HandleError`](super::HandleError). - #[derive(Debug)] - pub struct HandleErrorFuture { - #[pin] - pub(super) inner: Fut, - pub(super) f: Option, +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 { + #[pin] + pub(super) inner: Fut, + pub(super) f: Option, + } } -} -impl Future for HandleErrorFuture -where - Fut: Future, E>>, - F: FnOnce(E) -> Res, - Res: IntoResponse, - B: http_body::Body + Send + Sync + 'static, - B::Error: Into + Send + Sync + 'static, -{ - type Output = Result, Infallible>; + impl Future for HandleErrorFuture + where + Fut: Future, E>>, + F: FnOnce(E) -> Res, + Res: IntoResponse, + B: http_body::Body + Send + Sync + 'static, + B::Error: Into + Send + Sync + 'static, + { + type Output = Result, Infallible>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + 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() + } } } } diff --git a/src/extract/connect_info.rs b/src/extract/connect_info.rs index 761c8413..5e3ce170 100644 --- a/src/extract/connect_info.rs +++ b/src/extract/connect_info.rs @@ -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 { - svc: S, +pub struct IntoMakeServiceWithConnectInfo { + router: Router, _connect_info: PhantomData C>, } @@ -34,25 +34,21 @@ pub struct IntoMakeServiceWithConnectInfo { fn traits() { use crate::tests::*; assert_send::>(); - assert_sync::>(); } -impl IntoMakeServiceWithConnectInfo { - pub(crate) fn new(svc: S) -> Self { +impl IntoMakeServiceWithConnectInfo { + pub(crate) fn new(router: Router) -> Self { Self { - svc, + router, _connect_info: PhantomData, } } } -impl fmt::Debug for IntoMakeServiceWithConnectInfo -where - S: fmt::Debug, -{ +impl fmt::Debug for IntoMakeServiceWithConnectInfo { 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 Service for IntoMakeServiceWithConnectInfo +impl Service for IntoMakeServiceWithConnectInfo where - S: Clone, C: Connected, { - type Response = AddExtension>; + type Response = AddExtension, ConnectInfo>; type Error = Infallible; - type Future = ResponseFuture; + type Future = ResponseFuture; #[inline] fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { @@ -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 = - std::future::Ready>; + pub type ResponseFuture = + std::future::Ready, ConnectInfo>, Infallible>>; } /// Extractor for getting connection information produced by a [`Connected`]. diff --git a/src/extract/extractor_middleware.rs b/src/extract/extractor_middleware.rs index f57472e2..7ed8cb76 100644 --- a/src/extract/extractor_middleware.rs +++ b/src/extract/extractor_middleware.rs @@ -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 FromRequest for RequireAuth +/// impl FromRequest for MyExtractor /// where /// B: Send, /// { -/// type Rejection = StatusCode; +/// type Rejection = Infallible; /// /// async fn from_request(req: &mut RequestParts) -> Result { -/// 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::()); +/// .layer(extractor_middleware::()); /// # async { /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # }; diff --git a/src/extract/mod.rs b/src/extract/mod.rs index 97869d3a..6640d74c 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -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) { -//! // ... -//! } -//! -//! 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 FromRequest for ExtractUserAgent -//! where -//! B: Send, -//! { -//! type Rejection = (StatusCode, &'static str); -//! -//! async fn from_request(req: &mut RequestParts) -> Result { -//! 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); -//! -//! impl http_body::Body for MyBody -//! 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>> { -//! Pin::new(&mut self.0).poll_data(cx) -//! } -//! -//! fn poll_trailers( -//! mut self: Pin<&mut Self>, -//! cx: &mut Context<'_>, -//! ) -> Poll, 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>| async {}) -//! ) -//! .route( -//! "/body-stream", -//! // same for `extract::BodyStream` -//! get(|_: extract::BodyStream| async {}), -//! ) -//! .route( -//! // and `Request<_>` -//! "/request", -//! get(|_: Request>| 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; diff --git a/src/handler/mod.rs b/src/handler/mod.rs index b59fba9b..64cf4250 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -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` also implements `IntoResponse` +//! async fn echo(body: Bytes) -> Result { +//! 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, +//! | ------------- 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: 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: Clone + Send + Sized + 'static { /// ``` fn layer(self, layer: L) -> Layered where - L: Layer>, + L: Layer>, { - 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 Layered { #[test] fn traits() { + use crate::routing::MethodRouter; use crate::tests::*; assert_send::>(); assert_sync::>(); diff --git a/src/lib.rs b/src/lib.rs index 4670f48b..486afc92 100644 --- a/src/lib.rs +++ b/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) {} +//! +//! // `Query` gives you the query parameters and deserializes them. +//! async fn query(Query(params): Query>) {} +//! +//! // 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) {} +//! ``` +//! +//! 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 { +//! 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>, +//! ) { +//! // ... +//! } +//! # 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 = "" +//! hyper = { version = "", features = ["full"] } +//! tokio = { version = "", features = ["full"] } +//! tower = "" +//! ``` +//! +//! 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 diff --git a/src/response/mod.rs b/src/response/mod.rs index 94d39054..ec2f8a63 100644 --- a/src/response/mod.rs +++ b/src/response/mod.rs @@ -1,4 +1,4 @@ -//! Types and traits for generating responses. +#![doc = include_str!("../docs/response.md")] use crate::{ body::{box_body, BoxBody}, diff --git a/src/routing/future.rs b/src/routing/future.rs index f6393ddb..e09be0ae 100644 --- a/src/routing/future.rs +++ b/src/routing/future.rs @@ -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, }; diff --git a/src/routing/handler_method_router.rs b/src/routing/handler_method_routing.rs similarity index 100% rename from src/routing/handler_method_router.rs rename to src/routing/handler_method_routing.rs diff --git a/src/routing/into_make_service.rs b/src/routing/into_make_service.rs index 046f485a..10e5c4fa 100644 --- a/src/routing/into_make_service.rs +++ b/src/routing/into_make_service.rs @@ -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 { - service: S, +pub struct IntoMakeService { + router: Router, } -impl IntoMakeService { - pub(super) fn new(service: S) -> Self { - Self { service } +impl IntoMakeService { + pub(super) fn new(router: Router) -> Self { + Self { router } } } -impl Service for IntoMakeService -where - S: Clone, -{ - type Response = S; +impl Clone for IntoMakeService { + fn clone(&self) -> Self { + Self { + router: self.router.clone(), + } + } +} + +impl fmt::Debug for IntoMakeService { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("IntoMakeService") + .field("router", &self.router) + .finish() + } +} + +impl Service for IntoMakeService { + type Response = Router; type Error = Infallible; - type Future = MakeRouteServiceFuture; + type Future = IntoMakeServiceFuture; #[inline] fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { @@ -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 = - std::future::Ready>; + /// Response future for [`IntoMakeService`]. + pub type IntoMakeServiceFuture = + std::future::Ready, Infallible>>; } #[cfg(test)] mod tests { use super::*; + use crate::body::Body; #[test] fn traits() { use crate::tests::*; - assert_send::>(); - assert_sync::>(); + assert_send::>(); } } diff --git a/src/routing/mod.rs b/src/routing/mod.rs index 1380fb4a..361b849d 100644 --- a/src/routing/mod.rs +++ b/src/routing/mod.rs @@ -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) { /* ... */ } - /// # 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(mut self, path: &str, svc: T) -> Self + #[doc = include_str!("../docs/routing/route.md")] + pub fn route(mut self, path: &str, service: T) -> Self where T: Service, Response = Response, Error = Infallible> + Clone @@ -199,7 +126,7 @@ where panic!("Invalid route: empty path"); } - let svc = match try_downcast::, _>(svc) { + let service = match try_downcast::, _>(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>) { - /// // 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(mut self, path: &str, svc: T) -> Self where T: Service, Response = Response, 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) -> 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(self, layer: L) -> Router where L: Layer>, @@ -496,6 +266,19 @@ where } } + #[doc = include_str!("../docs/routing/fallback.md")] + pub fn fallback(mut self, svc: T) -> Self + where + T: Service, Response = Response, 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 { + pub fn into_make_service(self) -> IntoMakeService { 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) -> String { - /// format!("Hello {}", addr) - /// } - /// - /// # async { - /// axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) - /// .serve( - /// app.into_make_service_with_connect_info::() - /// ) - /// .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, - /// ) -> 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::() - /// ) - /// .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( self, - ) -> IntoMakeServiceWithConnectInfo + ) -> IntoMakeServiceWithConnectInfo where C: Connected, { 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) -> 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(mut self, svc: T) -> Self - where - T: Service, Response = Response, 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) -> RouterFuture { let id = *match_.value; diff --git a/src/routing/route.rs b/src/routing/route.rs index eafd4572..9e7f661b 100644 --- a/src/routing/route.rs +++ b/src/routing/route.rs @@ -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(CloneBoxService, Response, Infallible>); impl Route { diff --git a/src/routing/service_method_router.rs b/src/routing/service_method_routing.rs similarity index 98% rename from src/routing/service_method_router.rs rename to src/routing/service_method_routing.rs index 940a1fc0..49747fa7 100644 --- a/src/routing/service_method_router.rs +++ b/src/routing/service_method_routing.rs @@ -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 MethodRouter { /// 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 MethodRouter { /// 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; diff --git a/src/tests/get_to_head.rs b/src/tests/get_to_head.rs index ed2ba619..50f1e355 100644 --- a/src/tests/get_to_head.rs +++ b/src/tests/get_to_head.rs @@ -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] diff --git a/src/tests/merge.rs b/src/tests/merge.rs index f4ba00cd..f50e5a90 100644 --- a/src/tests/merge.rs +++ b/src/tests/merge.rs @@ -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( diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 7d1ddd91..68bb5079 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -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;