Docs improvements (#37)

This commit is contained in:
David Pedersen
2021-07-22 15:00:33 +02:00
committed by GitHub
parent a658c94f23
commit 8faed8120f
11 changed files with 263 additions and 291 deletions
+7 -6
View File
@@ -19,6 +19,7 @@ use http::StatusCode;
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::HashMap, collections::HashMap,
convert::Infallible,
net::SocketAddr, net::SocketAddr,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
time::Duration, time::Duration,
@@ -152,20 +153,20 @@ where
} }
} }
fn handle_error(error: BoxError) -> impl IntoResponse { fn handle_error(error: BoxError) -> Result<impl IntoResponse, Infallible> {
if error.is::<tower::timeout::error::Elapsed>() { if error.is::<tower::timeout::error::Elapsed>() {
return (StatusCode::REQUEST_TIMEOUT, Cow::from("request timed out")); return Ok((StatusCode::REQUEST_TIMEOUT, Cow::from("request timed out")));
} }
if error.is::<tower::load_shed::error::Overloaded>() { if error.is::<tower::load_shed::error::Overloaded>() {
return ( return Ok((
StatusCode::SERVICE_UNAVAILABLE, StatusCode::SERVICE_UNAVAILABLE,
Cow::from("service is overloaded, try again later"), Cow::from("service is overloaded, try again later"),
); ));
} }
( Ok((
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
Cow::from(format!("Unhandled internal error: {}", error)), Cow::from(format!("Unhandled internal error: {}", error)),
) ))
} }
+2 -2
View File
@@ -10,10 +10,10 @@ async fn main() {
let app = axum::routing::nest( let app = axum::routing::nest(
"/static", "/static",
axum::service::get(ServeDir::new(".").handle_error(|error: std::io::Error| { axum::service::get(ServeDir::new(".").handle_error(|error: std::io::Error| {
( Ok::<_, std::convert::Infallible>((
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled interal error: {}", error), format!("Unhandled interal error: {}", error),
) ))
})), })),
) )
.layer(TraceLayer::new_for_http()); .layer(TraceLayer::new_for_http());
+2 -2
View File
@@ -33,10 +33,10 @@ async fn main() {
ServeDir::new("examples/websocket") ServeDir::new("examples/websocket")
.append_index_html_on_directories(true) .append_index_html_on_directories(true)
.handle_error(|error: std::io::Error| { .handle_error(|error: std::io::Error| {
( Ok::<_, std::convert::Infallible>((
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled interal error: {}", error), format!("Unhandled interal error: {}", error),
) ))
}), }),
), ),
) )
+16 -6
View File
@@ -796,12 +796,22 @@ where
type Rejection = RequestAlreadyExtracted; type Rejection = RequestAlreadyExtracted;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> { async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let all_parts = req let RequestParts {
.method() method,
.zip(req.uri()) uri,
.zip(req.headers()) version,
.zip(req.extensions()) headers,
.zip(req.body()); extensions,
body,
} = req;
let all_parts = method
.as_ref()
.zip(version.as_ref())
.zip(uri.as_ref())
.zip(extensions.as_ref())
.zip(body.as_ref())
.zip(headers.as_ref());
if all_parts.is_some() { if all_parts.is_some() {
Ok(req.into_request()) Ok(req.into_request())
+4 -75
View File
@@ -1,42 +1,4 @@
//! Async functions that can be used to handle requests. //! Async functions that can be used to handle requests.
//!
//! # What is a handler?
//!
//! In axum a "handler" is an async function that accepts zero or more
//! ["extractors"](crate::extract) as arguments and returns something that
//! implements [`IntoResponse`].
//!
//! # Example
//!
//! Some examples of handlers:
//!
//! ```rust
//! use axum::prelude::*;
//! 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 if it is valid UTF-8
//! async fn buffer_body(body: Bytes) -> Result<String, StatusCode> {
//! if let Ok(string) = String::from_utf8(body.to_vec()) {
//! Ok(string)
//! } else {
//! Err(StatusCode::BAD_REQUEST)
//! }
//! }
//! ```
//!
//! For more details on generating responses see the
//! [`response`](crate::response) module and for more details on extractors see
//! the [`extract`](crate::extract) module.
use crate::{ use crate::{
body::{box_body, BoxBody}, body::{box_body, BoxBody},
@@ -402,49 +364,16 @@ impl<S, T> Layered<S, T> {
/// This is used to convert errors to responses rather than simply /// This is used to convert errors to responses rather than simply
/// terminating the connection. /// terminating the connection.
/// ///
/// `handle_error` can be used like so: /// It works similarly to [`routing::Layered::handle_error`]. See that for more details.
/// ///
/// ```rust /// [`routing::Layered::handle_error`]: crate::routing::Layered::handle_error
/// use axum::prelude::*; pub fn handle_error<F, ReqBody, ResBody, Res, E>(
/// use http::StatusCode;
/// use tower::{BoxError, timeout::TimeoutLayer};
/// use std::time::Duration;
///
/// async fn handler() { /* ... */ }
///
/// // `Timeout` will fail with `BoxError` if the timeout elapses...
/// let layered_handler = handler
/// .layer(TimeoutLayer::new(Duration::from_secs(30)));
///
/// // ...so we should handle that error
/// let layered_handler = layered_handler.handle_error(|error: BoxError| {
/// if error.is::<tower::timeout::error::Elapsed>() {
/// (
/// StatusCode::REQUEST_TIMEOUT,
/// "request took too long".to_string(),
/// )
/// } else {
/// (
/// StatusCode::INTERNAL_SERVER_ERROR,
/// format!("Unhandled internal error: {}", error),
/// )
/// }
/// });
///
/// let app = route("/", get(layered_handler));
/// # async {
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
///
/// The closure can return any type that implements [`IntoResponse`].
pub fn handle_error<F, ReqBody, ResBody, Res>(
self, self,
f: F, f: F,
) -> Layered<HandleError<S, F, ReqBody>, T> ) -> Layered<HandleError<S, F, ReqBody>, T>
where where
S: Service<Request<ReqBody>, Response = Response<ResBody>>, S: Service<Request<ReqBody>, Response = Response<ResBody>>,
F: FnOnce(S::Error) -> Res, F: FnOnce(S::Error) -> Result<Res, E>,
Res: IntoResponse, Res: IntoResponse,
{ {
let svc = HandleError::new(self.svc, f); let svc = HandleError::new(self.svc, f);
+158 -168
View File
@@ -1,12 +1,31 @@
//! axum is a web application framework that focuses on ergonomics and modularity. //! axum is a web application framework that focuses on ergonomics and modularity.
//! //!
//! ## Goals //! # Table of contents
//!
//! - [Goals](#goals)
//! - [Compatibility](#compatibility)
//! - [Handlers](#handlers)
//! - [Routing](#routing)
//! - [Extractors](#extractors)
//! - [Building responses](#building-responses)
//! - [Applying middleware](#applying-middleware)
//! - [To individual handlers](#to-individual-handlers)
//! - [To groups of routes](#to-groups-of-routes)
//! - [Error handling](#error-handling)
//! - [Sharing state with handlers](#sharing-state-with-handlers)
//! - [Routing to any `Service`](#routing-to-any-service)
//! - [Nesting applications](#nesting-applications)
//! - [Feature flags](#feature-flags)
//!
//! # Goals
//! //!
//! - Ease of use. Building web apps in Rust should be as easy as `async fn //! - Ease of use. Building web apps in Rust should be as easy as `async fn
//! handle(Request) -> Response`. //! handle(Request) -> Response`.
//! - Solid foundation. axum is built on top of [tower] and [hyper] and makes it //! - Solid foundation. axum is built on top of [tower] and [hyper] and makes it
//! easy to plug in any middleware from the [tower] and [tower-http] ecosystem. //! easy to plug in any middleware from the [tower] and [tower-http] ecosystem.
//! - Focus on routing, extracting data from requests, and generating responses. //! This improves compatibility since axum doesn't have its own custom
//! middleware system.
//! - Focus on routing, extracting data from requests, and building responses.
//! tower middleware can handle the rest. //! tower middleware can handle the rest.
//! - Macro free core. Macro frameworks have their place but axum focuses //! - Macro free core. Macro frameworks have their place but axum focuses
//! on providing a core that is macro free. //! on providing a core that is macro free.
@@ -39,6 +58,41 @@
//! } //! }
//! ``` //! ```
//! //!
//! # 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 you custom domain logic lives and axum applications are
//! built by routing between handlers.
//!
//! Some examples of handlers:
//!
//! ```rust
//! use axum::prelude::*;
//! 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 if it is valid UTF-8
//! async fn buffer_body(body: Bytes) -> Result<String, StatusCode> {
//! if let Ok(string) = String::from_utf8(body.to_vec()) {
//! Ok(string)
//! } else {
//! Err(StatusCode::BAD_REQUEST)
//! }
//! }
//! ```
//!
//! # Routing //! # Routing
//! //!
//! Routing between handlers looks like this: //! Routing between handlers looks like this:
@@ -65,92 +119,12 @@
//! # }; //! # };
//! ``` //! ```
//! //!
//! Routes can also be dynamic like `/users/:id`. See ["Extracting data from //! Routes can also be dynamic like `/users/:id`.
//! requests"](#extracting-data-from-requests) for more details on that.
//! //!
//! # Responses //! # Extractors
//! //!
//! Anything that implements [`IntoResponse`](response::IntoResponse) can be //! An extractor is a type that implements [`FromRequest`]. Extractors is how
//! returned from a handler: //! you pick apart the incoming request to get the parts your handler needs.
//!
//! ```rust,no_run
//! use axum::{body::Body, response::{Html, Json}, prelude::*};
//! use http::{StatusCode, Response, Uri};
//! use serde_json::{Value, json};
//!
//! // We've already seen returning &'static str
//! async fn plain_text() -> &'static str {
//! "foo"
//! }
//!
//! // String works too and will get a text/plain content-type
//! async fn plain_text_string(uri: Uri) -> String {
//! format!("Hi from {}", uri.path())
//! }
//!
//! // Bytes will get a `application/octet-stream` content-type
//! async fn bytes() -> Vec<u8> {
//! vec![1, 2, 3, 4]
//! }
//!
//! // `()` gives an empty response
//! async fn empty() {}
//!
//! // `StatusCode` gives an empty response with that status code
//! async fn empty_with_status() -> StatusCode {
//! StatusCode::NOT_FOUND
//! }
//!
//! // A tuple of `StatusCode` and something that implements `IntoResponse` can
//! // be used to override the status code
//! async fn with_status() -> (StatusCode, &'static str) {
//! (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong")
//! }
//!
//! // `Html` gives a content-type of `text/html`
//! async fn html() -> Html<&'static str> {
//! Html("<h1>Hello, World!</h1>")
//! }
//!
//! // `Json` gives a content-type of `application/json` and works with any type
//! // that implements `serde::Serialize`
//! async fn json() -> Json<Value> {
//! Json(json!({ "data": 42 }))
//! }
//!
//! // `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
//! // returning errors
//! async fn result() -> Result<&'static str, StatusCode> {
//! Ok("all good")
//! }
//!
//! // `Response` gives full control
//! async fn response() -> Response<Body> {
//! Response::builder().body(Body::empty()).unwrap()
//! }
//!
//! let app = route("/plain_text", get(plain_text))
//! .route("/plain_text_string", get(plain_text_string))
//! .route("/bytes", get(bytes))
//! .route("/empty", get(empty))
//! .route("/empty_with_status", get(empty_with_status))
//! .route("/with_status", get(with_status))
//! .route("/html", get(html))
//! .route("/json", get(json))
//! .route("/result", get(result))
//! .route("/response", get(response));
//! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
//! # };
//! ```
//!
//! See the [`response`] module for more details.
//!
//! # Extracting data from requests
//!
//! A handler function is an async function take takes any number of
//! "extractors" as arguments. An extractor is a type that implements
//! [`FromRequest`](crate::extract::FromRequest).
//! //!
//! For example, [`extract::Json`] is an extractor that consumes the request //! For example, [`extract::Json`] is an extractor that consumes the request
//! body and deserializes it as JSON into some target type: //! body and deserializes it as JSON into some target type:
@@ -256,13 +230,90 @@
//! See the [`extract`] module for more details. //! See the [`extract`] module for more details.
//! //!
//! [`Uuid`]: https://docs.rs/uuid/latest/uuid/ //! [`Uuid`]: https://docs.rs/uuid/latest/uuid/
//! [`FromRequest`]: crate::extract::FromRequest
//!
//! # Building responses
//!
//! Anything that implements [`IntoResponse`](response::IntoResponse) can be
//! returned from a handler:
//!
//! ```rust,no_run
//! use axum::{body::Body, response::{Html, Json}, prelude::*};
//! use http::{StatusCode, Response, Uri};
//! use serde_json::{Value, json};
//!
//! // We've already seen returning &'static str
//! async fn plain_text() -> &'static str {
//! "foo"
//! }
//!
//! // String works too and will get a text/plain content-type
//! async fn plain_text_string(uri: Uri) -> String {
//! format!("Hi from {}", uri.path())
//! }
//!
//! // Bytes will get a `application/octet-stream` content-type
//! async fn bytes() -> Vec<u8> {
//! vec![1, 2, 3, 4]
//! }
//!
//! // `()` gives an empty response
//! async fn empty() {}
//!
//! // `StatusCode` gives an empty response with that status code
//! async fn empty_with_status() -> StatusCode {
//! StatusCode::NOT_FOUND
//! }
//!
//! // A tuple of `StatusCode` and something that implements `IntoResponse` can
//! // be used to override the status code
//! async fn with_status() -> (StatusCode, &'static str) {
//! (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong")
//! }
//!
//! // `Html` gives a content-type of `text/html`
//! async fn html() -> Html<&'static str> {
//! Html("<h1>Hello, World!</h1>")
//! }
//!
//! // `Json` gives a content-type of `application/json` and works with any type
//! // that implements `serde::Serialize`
//! async fn json() -> Json<Value> {
//! Json(json!({ "data": 42 }))
//! }
//!
//! // `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
//! // returning errors
//! async fn result() -> Result<&'static str, StatusCode> {
//! Ok("all good")
//! }
//!
//! // `Response` gives full control
//! async fn response() -> Response<Body> {
//! Response::builder().body(Body::empty()).unwrap()
//! }
//!
//! let app = route("/plain_text", get(plain_text))
//! .route("/plain_text_string", get(plain_text_string))
//! .route("/bytes", get(bytes))
//! .route("/empty", get(empty))
//! .route("/empty_with_status", get(empty_with_status))
//! .route("/with_status", get(with_status))
//! .route("/html", get(html))
//! .route("/json", get(json))
//! .route("/result", get(result))
//! .route("/response", get(response));
//! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
//! # };
//! ```
//! //!
//! # Applying middleware //! # Applying middleware
//! //!
//! axum is designed to take full advantage of the tower and tower-http //! axum is designed to take full advantage of the tower and tower-http
//! ecosystem of middleware: //! ecosystem of middleware:
//! //!
//! ## Applying middleware to individual handlers //! ## To individual handlers
//! //!
//! A middleware can be applied to a single handler like so: //! A middleware can be applied to a single handler like so:
//! //!
@@ -281,7 +332,7 @@
//! # }; //! # };
//! ``` //! ```
//! //!
//! ## Applying middleware to groups of routes //! ## To groups of routes
//! //!
//! Middleware can also be applied to a group of routes like so: //! Middleware can also be applied to a group of routes like so:
//! //!
@@ -320,7 +371,7 @@
//! use tower::{ //! use tower::{
//! BoxError, timeout::{TimeoutLayer, error::Elapsed}, //! BoxError, timeout::{TimeoutLayer, error::Elapsed},
//! }; //! };
//! use std::{borrow::Cow, time::Duration}; //! use std::{borrow::Cow, time::Duration, convert::Infallible};
//! use http::StatusCode; //! use http::StatusCode;
//! //!
//! let app = route( //! let app = route(
@@ -332,16 +383,19 @@
//! // Check if the actual error type is `Elapsed` which //! // Check if the actual error type is `Elapsed` which
//! // `Timeout` returns //! // `Timeout` returns
//! if error.is::<Elapsed>() { //! if error.is::<Elapsed>() {
//! return (StatusCode::REQUEST_TIMEOUT, "Request took too long".into()); //! return Ok::<_, Infallible>((
//! StatusCode::REQUEST_TIMEOUT,
//! "Request took too long".into(),
//! ));
//! } //! }
//! //!
//! // If we encounter some error we don't handle return a generic //! // If we encounter some error we don't handle return a generic
//! // error //! // error
//! return ( //! return Ok::<_, Infallible>((
//! StatusCode::INTERNAL_SERVER_ERROR, //! StatusCode::INTERNAL_SERVER_ERROR,
//! // `Cow` lets us return either `&str` or `String` //! // `Cow` lets us return either `&str` or `String`
//! Cow::from(format!("Unhandled internal error: {}", error)), //! Cow::from(format!("Unhandled internal error: {}", error)),
//! ); //! ));
//! })), //! })),
//! ); //! );
//! //!
@@ -352,30 +406,10 @@
//! ``` //! ```
//! //!
//! The closure passed to [`handle_error`](handler::Layered::handle_error) must //! The closure passed to [`handle_error`](handler::Layered::handle_error) must
//! return something that implements [`IntoResponse`](response::IntoResponse). //! return `Result<T, E>` where `T` implements
//! [`IntoResponse`](response::IntoResponse).
//! //!
//! [`handle_error`](routing::Layered::handle_error) is also available on a //! See [`routing::Layered::handle_error`] fo more details.
//! group of routes with middleware applied:
//!
//! ```rust,no_run
//! use axum::prelude::*;
//! use tower::{BoxError, timeout::TimeoutLayer};
//! use std::time::Duration;
//!
//! let app = route("/", get(handle))
//! .route("/foo", post(other_handle))
//! .layer(TimeoutLayer::new(Duration::from_secs(30)))
//! .handle_error(|error: BoxError| {
//! // ...
//! });
//!
//! async fn handle() {}
//!
//! async fn other_handle() {}
//! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
//! # };
//! ```
//! //!
//! ## Applying multiple middleware //! ## Applying multiple middleware
//! //!
@@ -383,14 +417,9 @@
//! //!
//! ```rust,no_run //! ```rust,no_run
//! use axum::prelude::*; //! use axum::prelude::*;
//! use tower::{ //! use tower::ServiceBuilder;
//! ServiceBuilder, BoxError,
//! load_shed::error::Overloaded,
//! timeout::error::Elapsed,
//! };
//! use tower_http::compression::CompressionLayer; //! use tower_http::compression::CompressionLayer;
//! use std::{borrow::Cow, time::Duration}; //! use std::{borrow::Cow, time::Duration};
//! use http::StatusCode;
//! //!
//! let middleware_stack = ServiceBuilder::new() //! let middleware_stack = ServiceBuilder::new()
//! // Return an error after 30 seconds //! // Return an error after 30 seconds
@@ -404,27 +433,7 @@
//! .into_inner(); //! .into_inner();
//! //!
//! let app = route("/", get(|_: Request<Body>| async { /* ... */ })) //! let app = route("/", get(|_: Request<Body>| async { /* ... */ }))
//! .layer(middleware_stack) //! .layer(middleware_stack);
//! .handle_error(|error: BoxError| {
//! if error.is::<Overloaded>() {
//! return (
//! StatusCode::SERVICE_UNAVAILABLE,
//! "Try again later".into(),
//! );
//! }
//!
//! if error.is::<Elapsed>() {
//! return (
//! StatusCode::REQUEST_TIMEOUT,
//! "Request took too long".into(),
//! );
//! };
//!
//! return (
//! StatusCode::INTERNAL_SERVER_ERROR,
//! Cow::from(format!("Unhandled internal error: {}", error)),
//! );
//! });
//! # async { //! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); //! # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
//! # }; //! # };
@@ -466,10 +475,7 @@
//! axum also supports routing to general [`Service`]s: //! axum also supports routing to general [`Service`]s:
//! //!
//! ```rust,no_run //! ```rust,no_run
//! use axum::{ //! use axum::{service, prelude::*};
//! // `ServiceExt` adds `handle_error` to any `Service`
//! service::{self, ServiceExt}, prelude::*,
//! };
//! use tower_http::services::ServeFile; //! use tower_http::services::ServeFile;
//! use http::Response; //! use http::Response;
//! use std::convert::Infallible; //! use std::convert::Infallible;
@@ -485,10 +491,7 @@
//! ).route( //! ).route(
//! // GET `/static/Cargo.toml` goes to a service from tower-http //! // GET `/static/Cargo.toml` goes to a service from tower-http
//! "/static/Cargo.toml", //! "/static/Cargo.toml",
//! service::get( //! service::get(ServeFile::new("Cargo.toml"))
//! ServeFile::new("Cargo.toml")
//! .handle_error(|error: std::io::Error| { /* ... */ })
//! )
//! ); //! );
//! # async { //! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); //! # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
@@ -518,26 +521,12 @@
//! # }; //! # };
//! ``` //! ```
//! //!
//! [`nest`](routing::nest) can also be used to serve static files from a directory: //! # Examples
//! //!
//! ```rust,no_run //! The axum repo contains [a number of examples][examples] that show how to put all the
//! use axum::{prelude::*, service::ServiceExt, routing::nest}; //! pieces togehter.
//! use tower_http::services::ServeDir;
//! use http::Response;
//! use tower::{service_fn, BoxError};
//! //!
//! let app = nest( //! # Feature flags
//! "/images",
//! ServeDir::new("public/images").handle_error(|error: std::io::Error| {
//! // ...
//! })
//! );
//! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
//! # };
//! ```
//!
//! # Features
//! //!
//! axum uses a set of [feature flags] to reduce the amount of compiled and //! axum uses a set of [feature flags] to reduce the amount of compiled and
//! optional dependencies. //! optional dependencies.
@@ -555,6 +544,7 @@
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html#the-features-section //! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html#the-features-section
//! [`IntoResponse`]: crate::response::IntoResponse //! [`IntoResponse`]: crate::response::IntoResponse
//! [`Timeout`]: tower::timeout::Timeout //! [`Timeout`]: tower::timeout::Timeout
//! [examples]: https://github.com/davidpdrsn/axum/tree/main/examples
#![doc(html_root_url = "https://docs.rs/tower-http/0.1.0")] #![doc(html_root_url = "https://docs.rs/tower-http/0.1.0")]
#![warn( #![warn(
+42 -12
View File
@@ -28,7 +28,7 @@ use tower::{
}; };
use tower_http::map_response_body::MapResponseBodyLayer; use tower_http::map_response_body::MapResponseBodyLayer;
/// A filter that matches one or more HTTP method. /// A filter that matches one or more HTTP methods.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum MethodFilter { pub enum MethodFilter {
/// Match any method. /// Match any method.
@@ -632,7 +632,7 @@ impl<S> Layered<S> {
/// use axum::prelude::*; /// use axum::prelude::*;
/// use http::StatusCode; /// use http::StatusCode;
/// use tower::{BoxError, timeout::TimeoutLayer}; /// use tower::{BoxError, timeout::TimeoutLayer};
/// use std::time::Duration; /// use std::{convert::Infallible, time::Duration};
/// ///
/// async fn handler() { /* ... */ } /// async fn handler() { /* ... */ }
/// ///
@@ -643,18 +643,17 @@ impl<S> Layered<S> {
/// // ...so we should handle that error /// // ...so we should handle that error
/// let with_errors_handled = layered_app.handle_error(|error: BoxError| { /// let with_errors_handled = layered_app.handle_error(|error: BoxError| {
/// if error.is::<tower::timeout::error::Elapsed>() { /// if error.is::<tower::timeout::error::Elapsed>() {
/// ( /// Ok::<_, Infallible>((
/// StatusCode::REQUEST_TIMEOUT, /// StatusCode::REQUEST_TIMEOUT,
/// "request took too long".to_string(), /// "request took too long".to_string(),
/// ) /// ))
/// } else { /// } else {
/// ( /// Ok::<_, Infallible>((
/// StatusCode::INTERNAL_SERVER_ERROR, /// StatusCode::INTERNAL_SERVER_ERROR,
/// format!("Unhandled internal error: {}", error), /// format!("Unhandled internal error: {}", error),
/// ) /// ))
/// } /// }
/// }); /// });
///
/// # async { /// # async {
/// # hyper::Server::bind(&"".parse().unwrap()) /// # hyper::Server::bind(&"".parse().unwrap())
/// # .serve(with_errors_handled.into_make_service()) /// # .serve(with_errors_handled.into_make_service())
@@ -663,14 +662,46 @@ impl<S> Layered<S> {
/// # }; /// # };
/// ``` /// ```
/// ///
/// The closure can return any type that implements [`IntoResponse`]. /// The closure must return `Result<T, E>` where `T` implements [`IntoResponse`].
pub fn handle_error<F, ReqBody, ResBody, Res>( ///
/// You can also return `Err(_)` if you don't wish to handle the error:
///
/// ```rust
/// use axum::prelude::*;
/// use http::StatusCode;
/// use tower::{BoxError, timeout::TimeoutLayer};
/// use std::time::Duration;
///
/// async fn handler() { /* ... */ }
///
/// let layered_app = route("/", get(handler))
/// .layer(TimeoutLayer::new(Duration::from_secs(30)));
///
/// let with_errors_handled = layered_app.handle_error(|error: BoxError| {
/// if error.is::<tower::timeout::error::Elapsed>() {
/// Ok((
/// StatusCode::REQUEST_TIMEOUT,
/// "request took too long".to_string(),
/// ))
/// } else {
/// // keep the error as is
/// Err(error)
/// }
/// });
/// # async {
/// # hyper::Server::bind(&"".parse().unwrap())
/// # .serve(with_errors_handled.into_make_service())
/// # .await
/// # .unwrap();
/// # };
/// ```
pub fn handle_error<F, ReqBody, ResBody, Res, E>(
self, self,
f: F, f: F,
) -> crate::service::HandleError<S, F, ReqBody> ) -> crate::service::HandleError<S, F, ReqBody>
where where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone, S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
F: FnOnce(S::Error) -> Res, F: FnOnce(S::Error) -> Result<Res, E>,
Res: IntoResponse, Res: IntoResponse,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static, ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
ResBody::Error: Into<BoxError> + Send + Sync + 'static, ResBody::Error: Into<BoxError> + Send + Sync + 'static,
@@ -757,8 +788,7 @@ where
/// use tower_http::services::ServeDir; /// use tower_http::services::ServeDir;
/// ///
/// // Serves files inside the `public` directory at `GET /public/*` /// // Serves files inside the `public` directory at `GET /public/*`
/// let serve_dir_service = ServeDir::new("public") /// let serve_dir_service = ServeDir::new("public");
/// .handle_error(|error: std::io::Error| { /* ... */ });
/// ///
/// let app = nest("/public", get(serve_dir_service)); /// let app = nest("/public", get(serve_dir_service));
/// # async { /// # async {
+7 -6
View File
@@ -9,7 +9,6 @@ use futures_util::ready;
use http::Response; use http::Response;
use pin_project::pin_project; use pin_project::pin_project;
use std::{ use std::{
convert::Infallible,
future::Future, future::Future,
pin::Pin, pin::Pin,
task::{Context, Poll}, task::{Context, Poll},
@@ -25,15 +24,15 @@ pub struct HandleErrorFuture<Fut, F> {
pub(super) f: Option<F>, pub(super) f: Option<F>,
} }
impl<Fut, F, E, B, Res> Future for HandleErrorFuture<Fut, F> impl<Fut, F, E, E2, B, Res> Future for HandleErrorFuture<Fut, F>
where where
Fut: Future<Output = Result<Response<B>, E>>, Fut: Future<Output = Result<Response<B>, E>>,
F: FnOnce(E) -> Res, F: FnOnce(E) -> Result<Res, E2>,
Res: IntoResponse, Res: IntoResponse,
B: http_body::Body<Data = Bytes> + Send + Sync + 'static, B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
B::Error: Into<BoxError> + Send + Sync + 'static, B::Error: Into<BoxError> + Send + Sync + 'static,
{ {
type Output = Result<Response<BoxBody>, Infallible>; type Output = Result<Response<BoxBody>, E2>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project(); let this = self.project();
@@ -42,8 +41,10 @@ where
Ok(res) => Ok(res.map(box_body)).into(), Ok(res) => Ok(res.map(box_body)).into(),
Err(err) => { Err(err) => {
let f = this.f.take().unwrap(); let f = this.f.take().unwrap();
let res = f(err).into_response(); match f(err) {
Ok(res.map(box_body)).into() Ok(res) => Ok(res.into_response().map(box_body)).into(),
Err(err) => Err(err).into(),
}
} }
} }
} }
+17 -9
View File
@@ -507,16 +507,16 @@ where
} }
} }
impl<S, F, ReqBody, ResBody, Res> Service<Request<ReqBody>> for HandleError<S, F, ReqBody> impl<S, F, ReqBody, ResBody, Res, E> Service<Request<ReqBody>> for HandleError<S, F, ReqBody>
where where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone, S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
F: FnOnce(S::Error) -> Res + Clone, F: FnOnce(S::Error) -> Result<Res, E> + Clone,
Res: IntoResponse, Res: IntoResponse,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static, ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
ResBody::Error: Into<BoxError> + Send + Sync + 'static, ResBody::Error: Into<BoxError> + Send + Sync + 'static,
{ {
type Response = Response<BoxBody>; type Response = Response<BoxBody>;
type Error = Infallible; type Error = E;
type Future = future::HandleErrorFuture<Oneshot<S, Request<ReqBody>>, F>; type Future = future::HandleErrorFuture<Oneshot<S, Request<ReqBody>>, F>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@@ -538,15 +538,16 @@ pub trait ServiceExt<ReqBody, ResBody>:
/// Handle errors from a service. /// Handle errors from a service.
/// ///
/// `handle_error` takes a closure that will map errors from the service /// `handle_error` takes a closure that will map errors from the service
/// into responses. The closure's return type must implement /// into responses. The closure's return type must be `Result<T, E>` where
/// [`IntoResponse`]. /// `T` implements [`IntoIntoResponse`](crate::response::IntoResponse).
/// ///
/// # Example /// # Example
/// ///
/// ```rust,no_run /// ```rust,no_run
/// use axum::{service::{self, ServiceExt}, prelude::*}; /// use axum::{service::{self, ServiceExt}, prelude::*};
/// use http::Response; /// use http::{Response, StatusCode};
/// use tower::{service_fn, BoxError}; /// use tower::{service_fn, BoxError};
/// use std::convert::Infallible;
/// ///
/// // A service that might fail with `std::io::Error` /// // A service that might fail with `std::io::Error`
/// let service = service_fn(|_: Request<Body>| async { /// let service = service_fn(|_: Request<Body>| async {
@@ -557,7 +558,10 @@ pub trait ServiceExt<ReqBody, ResBody>:
/// let app = route( /// let app = route(
/// "/", /// "/",
/// service.handle_error(|error: std::io::Error| { /// service.handle_error(|error: std::io::Error| {
/// // Handle error by returning something that implements `IntoResponse` /// Ok::<_, Infallible>((
/// StatusCode::INTERNAL_SERVER_ERROR,
/// error.to_string(),
/// ))
/// }), /// }),
/// ); /// );
/// # /// #
@@ -565,10 +569,14 @@ pub trait ServiceExt<ReqBody, ResBody>:
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # }; /// # };
/// ``` /// ```
fn handle_error<F, Res>(self, f: F) -> HandleError<Self, F, ReqBody> ///
/// It works similarly to [`routing::Layered::handle_error`]. See that for more details.
///
/// [`routing::Layered::handle_error`]: crate::routing::Layered::handle_error
fn handle_error<F, Res, E>(self, f: F) -> HandleError<Self, F, ReqBody>
where where
Self: Sized, Self: Sized,
F: FnOnce(Self::Error) -> Res, F: FnOnce(Self::Error) -> Result<Res, E>,
Res: IntoResponse, Res: IntoResponse,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static, ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
ResBody::Error: Into<BoxError> + Send + Sync + 'static, ResBody::Error: Into<BoxError> + Send + Sync + 'static,
+7 -4
View File
@@ -8,6 +8,7 @@ use hyper::{Body, Server};
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; use serde_json::json;
use std::{ use std::{
convert::Infallible,
net::{SocketAddr, TcpListener}, net::{SocketAddr, TcpListener},
time::Duration, time::Duration,
}; };
@@ -340,7 +341,7 @@ async fn service_handlers() {
service_fn(|req: Request<Body>| async move { service_fn(|req: Request<Body>| async move {
Ok::<_, BoxError>(Response::new(req.into_body())) Ok::<_, BoxError>(Response::new(req.into_body()))
}) })
.handle_error(|_error: BoxError| StatusCode::INTERNAL_SERVER_ERROR), .handle_error(|_error: BoxError| Ok(StatusCode::INTERNAL_SERVER_ERROR)),
), ),
) )
.route( .route(
@@ -348,7 +349,7 @@ async fn service_handlers() {
service::on( service::on(
MethodFilter::Get, MethodFilter::Get,
ServeFile::new("Cargo.toml").handle_error(|error: std::io::Error| { ServeFile::new("Cargo.toml").handle_error(|error: std::io::Error| {
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()) Ok::<_, Infallible>((StatusCode::INTERNAL_SERVER_ERROR, error.to_string()))
}), }),
), ),
); );
@@ -485,7 +486,9 @@ async fn handling_errors_from_layered_single_routes() {
.layer(TraceLayer::new_for_http()) .layer(TraceLayer::new_for_http())
.into_inner(), .into_inner(),
) )
.handle_error(|_error: BoxError| StatusCode::INTERNAL_SERVER_ERROR)), .handle_error(|_error: BoxError| {
Ok::<_, Infallible>(StatusCode::INTERNAL_SERVER_ERROR)
})),
); );
let addr = run_in_background(app).await; let addr = run_in_background(app).await;
@@ -508,7 +511,7 @@ async fn layer_on_whole_router() {
.timeout(Duration::from_millis(100)) .timeout(Duration::from_millis(100))
.into_inner(), .into_inner(),
) )
.handle_error(|_err: BoxError| StatusCode::INTERNAL_SERVER_ERROR); .handle_error(|_err: BoxError| Ok::<_, Infallible>(StatusCode::INTERNAL_SERVER_ERROR));
let addr = run_in_background(app).await; let addr = run_in_background(app).await;
+1 -1
View File
@@ -56,7 +56,7 @@ where
crate::service::get::<_, B>(svc) crate::service::get::<_, B>(svc)
} }
/// [`Service`] that ugprades connections to websockets and spawns a task to /// [`Service`] that upgrades connections to websockets and spawns a task to
/// handle the stream. /// handle the stream.
/// ///
/// Created with [`ws`]. /// Created with [`ws`].