mirror of
https://github.com/tokio-rs/axum.git
synced 2024-12-28 07:20:12 +01:00
Revamp error handling model (#402)
* Revamp error handling model * changelog improvements and typo fixes * Fix a few more Infallible bounds * minor docs fixes
This commit is contained in:
parent
1a78a3f224
commit
f10508db0b
19 changed files with 501 additions and 558 deletions
72
CHANGELOG.md
72
CHANGELOG.md
|
@ -13,8 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Adding a conflicting route will now cause a panic instead of silently making
|
- Adding a conflicting route will now cause a panic instead of silently making
|
||||||
a route unreachable.
|
a route unreachable.
|
||||||
- Route matching is faster as number of routes increase.
|
- Route matching is faster as number of routes increase.
|
||||||
- The routes `/foo` and `/:key` are considered to overlap and will cause a
|
- **breaking:** The routes `/foo` and `/:key` are considered to overlap and
|
||||||
panic when constructing the router. This might be fixed in the future.
|
will cause a panic when constructing the router. This might be fixed in the future.
|
||||||
- Improve performance of `BoxRoute` ([#339])
|
- Improve performance of `BoxRoute` ([#339])
|
||||||
- Expand accepted content types for JSON requests ([#378])
|
- Expand accepted content types for JSON requests ([#378])
|
||||||
- **breaking:** Automatically do percent decoding in `extract::Path`
|
- **breaking:** Automatically do percent decoding in `extract::Path`
|
||||||
|
@ -28,6 +28,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
thus wasn't necessary
|
thus wasn't necessary
|
||||||
- **breaking:** Change `Connected::connect_info` to return `Self` and remove
|
- **breaking:** Change `Connected::connect_info` to return `Self` and remove
|
||||||
the associated type `ConnectInfo` ([#396])
|
the associated type `ConnectInfo` ([#396])
|
||||||
|
- **breaking:** Simplify error handling model ([#402]):
|
||||||
|
- All services part of the router are now required to be infallible.
|
||||||
|
- Error handling utilities have been moved to an `error_handling` module.
|
||||||
|
- `Router::check_infallible` has been removed since routers are always
|
||||||
|
infallible with the error handling changes.
|
||||||
|
- Error handling closures must now handle all errors and thus always return
|
||||||
|
something that implements `IntoResponse`.
|
||||||
|
|
||||||
|
With these changes handling errors from fallible middleware is done like so:
|
||||||
|
|
||||||
|
```rust,no_run
|
||||||
|
use axum::{
|
||||||
|
handler::get,
|
||||||
|
http::StatusCode,
|
||||||
|
error_handling::HandleErrorLayer,
|
||||||
|
response::IntoResponse,
|
||||||
|
Router, BoxError,
|
||||||
|
};
|
||||||
|
use tower::ServiceBuilder;
|
||||||
|
use std::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));
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(|| async { /* ... */ }))
|
||||||
|
.layer(middleware_stack);
|
||||||
|
|
||||||
|
fn handle_error(_error: BoxError) -> impl IntoResponse {
|
||||||
|
StatusCode::REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And handling errors from fallible leaf services is done like so:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use axum::{
|
||||||
|
Router, service,
|
||||||
|
body::Body,
|
||||||
|
handler::get,
|
||||||
|
response::IntoResponse,
|
||||||
|
http::{Request, Response},
|
||||||
|
error_handling::HandleErrorExt, // for `.handle_error`
|
||||||
|
};
|
||||||
|
use std::{io, convert::Infallible};
|
||||||
|
use tower::service_fn;
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route(
|
||||||
|
"/",
|
||||||
|
service::get(service_fn(|_req: Request<Body>| async {
|
||||||
|
let contents = tokio::fs::read_to_string("some_file").await?;
|
||||||
|
Ok::<_, io::Error>(Response::new(Body::from(contents)))
|
||||||
|
}))
|
||||||
|
.handle_error(handle_io_error),
|
||||||
|
);
|
||||||
|
|
||||||
|
fn handle_io_error(error: io::Error) -> impl IntoResponse {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
[#339]: https://github.com/tokio-rs/axum/pull/339
|
[#339]: https://github.com/tokio-rs/axum/pull/339
|
||||||
[#286]: https://github.com/tokio-rs/axum/pull/286
|
[#286]: https://github.com/tokio-rs/axum/pull/286
|
||||||
|
@ -35,6 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
[#378]: https://github.com/tokio-rs/axum/pull/378
|
[#378]: https://github.com/tokio-rs/axum/pull/378
|
||||||
[#363]: https://github.com/tokio-rs/axum/pull/363
|
[#363]: https://github.com/tokio-rs/axum/pull/363
|
||||||
[#396]: https://github.com/tokio-rs/axum/pull/396
|
[#396]: https://github.com/tokio-rs/axum/pull/396
|
||||||
|
[#402]: https://github.com/tokio-rs/axum/pull/402
|
||||||
|
|
||||||
# 0.2.8 (07. October, 2021)
|
# 0.2.8 (07. October, 2021)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Bytes,
|
body::Bytes,
|
||||||
|
error_handling::HandleErrorLayer,
|
||||||
extract::{ContentLengthLimit, Extension, Path},
|
extract::{ContentLengthLimit, Extension, Path},
|
||||||
handler::{delete, get, Handler},
|
handler::{delete, get, Handler},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
|
@ -18,7 +19,6 @@ use axum::{
|
||||||
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,
|
||||||
|
@ -52,16 +52,15 @@ async fn main() {
|
||||||
// Add middleware to all routes
|
// Add middleware to all routes
|
||||||
.layer(
|
.layer(
|
||||||
ServiceBuilder::new()
|
ServiceBuilder::new()
|
||||||
|
// Handle errors from middleware
|
||||||
|
.layer(HandleErrorLayer::new(handle_error))
|
||||||
.load_shed()
|
.load_shed()
|
||||||
.concurrency_limit(1024)
|
.concurrency_limit(1024)
|
||||||
.timeout(Duration::from_secs(10))
|
.timeout(Duration::from_secs(10))
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
.layer(AddExtensionLayer::new(SharedState::default()))
|
.layer(AddExtensionLayer::new(SharedState::default()))
|
||||||
.into_inner(),
|
.into_inner(),
|
||||||
)
|
);
|
||||||
// Handle errors from middleware
|
|
||||||
.handle_error(handle_error)
|
|
||||||
.check_infallible();
|
|
||||||
|
|
||||||
// Run our app with hyper
|
// Run our app with hyper
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
@ -126,20 +125,20 @@ fn admin_routes() -> Router<BoxRoute> {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_error(error: BoxError) -> Result<impl IntoResponse, Infallible> {
|
fn handle_error(error: BoxError) -> impl IntoResponse {
|
||||||
if error.is::<tower::timeout::error::Elapsed>() {
|
if error.is::<tower::timeout::error::Elapsed>() {
|
||||||
return Ok((StatusCode::REQUEST_TIMEOUT, Cow::from("request timed out")));
|
return (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 Ok((
|
return (
|
||||||
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)),
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,13 @@
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
body::{Body, BoxBody, Bytes},
|
body::{Body, BoxBody, Bytes},
|
||||||
|
error_handling::HandleErrorLayer,
|
||||||
handler::post,
|
handler::post,
|
||||||
http::{Request, Response},
|
http::{Request, Response, StatusCode},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tower::{filter::AsyncFilterLayer, util::AndThenLayer, BoxError};
|
use tower::{filter::AsyncFilterLayer, util::AndThenLayer, BoxError, ServiceBuilder};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -26,8 +27,17 @@ async fn main() {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", post(|| async move { "Hello from `POST /`" }))
|
.route("/", post(|| async move { "Hello from `POST /`" }))
|
||||||
.layer(AsyncFilterLayer::new(map_request))
|
.layer(
|
||||||
.layer(AndThenLayer::new(map_response));
|
ServiceBuilder::new()
|
||||||
|
.layer(HandleErrorLayer::new(|error| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("Unhandled internal error: {}", error),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.layer(AndThenLayer::new(map_response))
|
||||||
|
.layer(AsyncFilterLayer::new(map_request)),
|
||||||
|
);
|
||||||
|
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
tracing::debug!("listening on {}", addr);
|
tracing::debug!("listening on {}", addr);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
error_handling::HandleErrorExt,
|
||||||
extract::TypedHeader,
|
extract::TypedHeader,
|
||||||
handler::get,
|
handler::get,
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
|
@ -28,10 +29,10 @@ async fn main() {
|
||||||
ServeDir::new("examples/sse/assets").append_index_html_on_directories(true),
|
ServeDir::new("examples/sse/assets").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 internal error: {}", error),
|
format!("Unhandled internal error: {}", error),
|
||||||
))
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
// build our application with a route
|
// build our application with a route
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
//! cargo run -p example-static-file-server
|
//! cargo run -p example-static-file-server
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use axum::{http::StatusCode, service, Router};
|
use axum::{error_handling::HandleErrorExt, http::StatusCode, service, Router};
|
||||||
use std::{convert::Infallible, net::SocketAddr};
|
use std::net::SocketAddr;
|
||||||
use tower_http::{services::ServeDir, trace::TraceLayer};
|
use tower_http::{services::ServeDir, trace::TraceLayer};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -23,10 +23,10 @@ async fn main() {
|
||||||
.nest(
|
.nest(
|
||||||
"/static",
|
"/static",
|
||||||
service::get(ServeDir::new(".")).handle_error(|error: std::io::Error| {
|
service::get(ServeDir::new(".")).handle_error(|error: std::io::Error| {
|
||||||
Ok::<_, Infallible>((
|
(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
format!("Unhandled internal error: {}", error),
|
format!("Unhandled internal error: {}", error),
|
||||||
))
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.layer(TraceLayer::new_for_http());
|
.layer(TraceLayer::new_for_http());
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
error_handling::HandleErrorLayer,
|
||||||
extract::{Extension, Path, Query},
|
extract::{Extension, Path, Query},
|
||||||
handler::{get, patch},
|
handler::{get, patch},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
|
@ -23,7 +24,6 @@ use axum::{
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
convert::Infallible,
|
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
sync::{Arc, RwLock},
|
sync::{Arc, RwLock},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
|
@ -49,25 +49,21 @@ async fn main() {
|
||||||
// Add middleware to all routes
|
// Add middleware to all routes
|
||||||
.layer(
|
.layer(
|
||||||
ServiceBuilder::new()
|
ServiceBuilder::new()
|
||||||
|
.layer(HandleErrorLayer::new(|error: BoxError| {
|
||||||
|
if error.is::<tower::timeout::error::Elapsed>() {
|
||||||
|
Ok(StatusCode::REQUEST_TIMEOUT)
|
||||||
|
} else {
|
||||||
|
Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("Unhandled internal error: {}", error),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}))
|
||||||
.timeout(Duration::from_secs(10))
|
.timeout(Duration::from_secs(10))
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
.layer(AddExtensionLayer::new(db))
|
.layer(AddExtensionLayer::new(db))
|
||||||
.into_inner(),
|
.into_inner(),
|
||||||
)
|
);
|
||||||
.handle_error(|error: BoxError| {
|
|
||||||
let result = if error.is::<tower::timeout::error::Elapsed>() {
|
|
||||||
Ok(StatusCode::REQUEST_TIMEOUT)
|
|
||||||
} else {
|
|
||||||
Err((
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
format!("Unhandled internal error: {}", error),
|
|
||||||
))
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok::<_, Infallible>(result)
|
|
||||||
})
|
|
||||||
// Make sure all errors have been handled
|
|
||||||
.check_infallible();
|
|
||||||
|
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
tracing::debug!("listening on {}", addr);
|
tracing::debug!("listening on {}", addr);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
error_handling::HandleErrorExt,
|
||||||
extract::{
|
extract::{
|
||||||
ws::{Message, WebSocket, WebSocketUpgrade},
|
ws::{Message, WebSocket, WebSocketUpgrade},
|
||||||
TypedHeader,
|
TypedHeader,
|
||||||
|
@ -38,10 +39,10 @@ async fn main() {
|
||||||
ServeDir::new("examples/websockets/assets").append_index_html_on_directories(true),
|
ServeDir::new("examples/websockets/assets").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 internal error: {}", error),
|
format!("Unhandled internal error: {}", error),
|
||||||
))
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
// routes are matched from bottom to top, so we have to put `nest` at the
|
// routes are matched from bottom to top, so we have to put `nest` at the
|
||||||
|
|
199
src/error_handling/mod.rs
Normal file
199
src/error_handling/mod.rs
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
//! Error handling utilities
|
||||||
|
//!
|
||||||
|
//! See [error handling](../index.html#error-handling) for more details on how
|
||||||
|
//! error handling works in axum.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
body::{box_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
|
||||||
|
/// that handles errors by converting them into responses.
|
||||||
|
///
|
||||||
|
/// See [error handling](../index.html#error-handling) for more details.
|
||||||
|
pub struct HandleErrorLayer<F, B> {
|
||||||
|
f: F,
|
||||||
|
_marker: PhantomData<fn() -> B>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, B> HandleErrorLayer<F, B> {
|
||||||
|
/// Create a new `HandleErrorLayer`.
|
||||||
|
pub fn new(f: F) -> Self {
|
||||||
|
Self {
|
||||||
|
f,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, B, S> Layer<S> for HandleErrorLayer<F, B>
|
||||||
|
where
|
||||||
|
F: Clone,
|
||||||
|
{
|
||||||
|
type Service = HandleError<S, F, B>;
|
||||||
|
|
||||||
|
fn layer(&self, inner: S) -> Self::Service {
|
||||||
|
HandleError {
|
||||||
|
inner,
|
||||||
|
f: self.f.clone(),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, B> Clone for HandleErrorLayer<F, B>
|
||||||
|
where
|
||||||
|
F: Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
f: self.f.clone(),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, B> fmt::Debug for HandleErrorLayer<F, B> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("HandleErrorLayer")
|
||||||
|
.field("f", &format_args!("{}", std::any::type_name::<F>()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`Service`] adapter that handles errors by converting them into responses.
|
||||||
|
///
|
||||||
|
/// See [error handling](../index.html#error-handling) for more details.
|
||||||
|
pub struct HandleError<S, F, B> {
|
||||||
|
inner: S,
|
||||||
|
f: F,
|
||||||
|
_marker: PhantomData<fn() -> B>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, F, B> Clone for HandleError<S, F, B>
|
||||||
|
where
|
||||||
|
S: Clone,
|
||||||
|
F: Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self::new(self.inner.clone(), self.f.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, F, B> HandleError<S, F, B> {
|
||||||
|
/// Create a new `HandleError`.
|
||||||
|
pub fn new(inner: S, f: F) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
f,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, F, B> fmt::Debug for HandleError<S, F, B>
|
||||||
|
where
|
||||||
|
S: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("HandleError")
|
||||||
|
.field("inner", &self.inner)
|
||||||
|
.field("f", &format_args!("{}", std::any::type_name::<F>()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, F, ReqBody, ResBody, Res> Service<Request<ReqBody>> for HandleError<S, F, ReqBody>
|
||||||
|
where
|
||||||
|
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
|
||||||
|
F: FnOnce(S::Error) -> Res + Clone,
|
||||||
|
Res: IntoResponse,
|
||||||
|
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||||
|
ResBody::Error: Into<BoxError> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
type Response = Response<BoxBody>;
|
||||||
|
type Error = Infallible;
|
||||||
|
type Future = HandleErrorFuture<Oneshot<S, Request<ReqBody>>, F>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
|
||||||
|
HandleErrorFuture {
|
||||||
|
f: Some(self.f.clone()),
|
||||||
|
inner: self.inner.clone().oneshot(req),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle errors this service might produce, by mapping them to responses.
|
||||||
|
///
|
||||||
|
/// See [error handling](../index.html#error-handling) for more details.
|
||||||
|
pub trait HandleErrorExt<B>: Service<Request<B>> + Sized {
|
||||||
|
/// Apply a [`HandleError`] middleware.
|
||||||
|
fn handle_error<F>(self, f: F) -> HandleError<Self, F, B> {
|
||||||
|
HandleError::new(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B, S> HandleErrorExt<B> for S where S: Service<Request<B>> {}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
/// Response future for [`HandleError`](super::HandleError).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HandleErrorFuture<Fut, F> {
|
||||||
|
#[pin]
|
||||||
|
pub(super) inner: Fut,
|
||||||
|
pub(super) f: Option<F>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Fut, F, E, B, Res> Future for HandleErrorFuture<Fut, F>
|
||||||
|
where
|
||||||
|
Fut: Future<Output = Result<Response<B>, E>>,
|
||||||
|
F: FnOnce(E) -> Res,
|
||||||
|
Res: IntoResponse,
|
||||||
|
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||||
|
B::Error: Into<BoxError> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
type Output = Result<Response<BoxBody>, Infallible>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.project();
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn traits() {
|
||||||
|
use crate::tests::*;
|
||||||
|
|
||||||
|
assert_send::<HandleError<(), (), NotSendSync>>();
|
||||||
|
assert_sync::<HandleError<(), (), NotSendSync>>();
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ use crate::{
|
||||||
extract::{FromRequest, RequestParts},
|
extract::{FromRequest, RequestParts},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::{EmptyRouter, MethodFilter},
|
routing::{EmptyRouter, MethodFilter},
|
||||||
service::HandleError,
|
|
||||||
util::Either,
|
util::Either,
|
||||||
BoxError,
|
BoxError,
|
||||||
};
|
};
|
||||||
|
@ -264,9 +263,6 @@ pub trait Handler<B, T>: Clone + Send + Sized + 'static {
|
||||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||||
/// # };
|
/// # };
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// When adding middleware that might fail its recommended to handle those
|
|
||||||
/// errors. See [`Layered::handle_error`] for more details.
|
|
||||||
fn layer<L>(self, layer: L) -> Layered<L::Service, T>
|
fn layer<L>(self, layer: L) -> Layered<L::Service, T>
|
||||||
where
|
where
|
||||||
L: Layer<OnMethod<Self, B, T, EmptyRouter>>,
|
L: Layer<OnMethod<Self, B, T, EmptyRouter>>,
|
||||||
|
@ -426,28 +422,6 @@ impl<S, T> Layered<S, T> {
|
||||||
_input: PhantomData,
|
_input: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`Layered`] handler where errors will be handled using the
|
|
||||||
/// given closure.
|
|
||||||
///
|
|
||||||
/// This is used to convert errors to responses rather than simply
|
|
||||||
/// terminating the connection.
|
|
||||||
///
|
|
||||||
/// It works similarly to [`routing::Router::handle_error`]. See that for more details.
|
|
||||||
///
|
|
||||||
/// [`routing::Router::handle_error`]: crate::routing::Router::handle_error
|
|
||||||
pub fn handle_error<F, ReqBody, ResBody, Res, E>(
|
|
||||||
self,
|
|
||||||
f: F,
|
|
||||||
) -> Layered<HandleError<S, F, ReqBody>, T>
|
|
||||||
where
|
|
||||||
S: Service<Request<ReqBody>, Response = Response<ResBody>>,
|
|
||||||
F: FnOnce(S::Error) -> Result<Res, E>,
|
|
||||||
Res: IntoResponse,
|
|
||||||
{
|
|
||||||
let svc = HandleError::new(self.svc, f);
|
|
||||||
Layered::new(svc)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handler [`Service`] that accepts requests based on a [`MethodFilter`] and
|
/// A handler [`Service`] that accepts requests based on a [`MethodFilter`] and
|
||||||
|
|
163
src/lib.rs
163
src/lib.rs
|
@ -7,7 +7,6 @@
|
||||||
//! - [Handlers](#handlers)
|
//! - [Handlers](#handlers)
|
||||||
//! - [Debugging handler type errors](#debugging-handler-type-errors)
|
//! - [Debugging handler type errors](#debugging-handler-type-errors)
|
||||||
//! - [Routing](#routing)
|
//! - [Routing](#routing)
|
||||||
//! - [Matching multiple methods](#matching-multiple-methods)
|
|
||||||
//! - [Routing to any `Service`](#routing-to-any-service)
|
//! - [Routing to any `Service`](#routing-to-any-service)
|
||||||
//! - [Routing to fallible services](#routing-to-fallible-services)
|
//! - [Routing to fallible services](#routing-to-fallible-services)
|
||||||
//! - [Wildcard routes](#wildcard-routes)
|
//! - [Wildcard routes](#wildcard-routes)
|
||||||
|
@ -18,10 +17,10 @@
|
||||||
//! - [Optional extractors](#optional-extractors)
|
//! - [Optional extractors](#optional-extractors)
|
||||||
//! - [Customizing extractor responses](#customizing-extractor-responses)
|
//! - [Customizing extractor responses](#customizing-extractor-responses)
|
||||||
//! - [Building responses](#building-responses)
|
//! - [Building responses](#building-responses)
|
||||||
|
//! - [Error handling](#error-handling)
|
||||||
//! - [Applying middleware](#applying-middleware)
|
//! - [Applying middleware](#applying-middleware)
|
||||||
//! - [To individual handlers](#to-individual-handlers)
|
//! - [To individual handlers](#to-individual-handlers)
|
||||||
//! - [To groups of routes](#to-groups-of-routes)
|
//! - [To groups of routes](#to-groups-of-routes)
|
||||||
//! - [Error handling](#error-handling)
|
|
||||||
//! - [Applying multiple middleware](#applying-multiple-middleware)
|
//! - [Applying multiple middleware](#applying-multiple-middleware)
|
||||||
//! - [Commonly used middleware](#commonly-used-middleware)
|
//! - [Commonly used middleware](#commonly-used-middleware)
|
||||||
//! - [Writing your own middleware](#writing-your-own-middleware)
|
//! - [Writing your own middleware](#writing-your-own-middleware)
|
||||||
|
@ -186,14 +185,15 @@
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use axum::{
|
//! use axum::{
|
||||||
//! body::Body,
|
|
||||||
//! http::Request,
|
|
||||||
//! Router,
|
//! Router,
|
||||||
//! service
|
//! service,
|
||||||
|
//! body::Body,
|
||||||
|
//! error_handling::HandleErrorExt,
|
||||||
|
//! http::{Request, StatusCode},
|
||||||
//! };
|
//! };
|
||||||
//! use tower_http::services::ServeFile;
|
//! use tower_http::services::ServeFile;
|
||||||
//! use http::Response;
|
//! use http::Response;
|
||||||
//! use std::convert::Infallible;
|
//! use std::{convert::Infallible, io};
|
||||||
//! use tower::service_fn;
|
//! use tower::service_fn;
|
||||||
//!
|
//!
|
||||||
//! let app = Router::new()
|
//! let app = Router::new()
|
||||||
|
@ -205,7 +205,7 @@
|
||||||
//! // to have the response body mapped
|
//! // to have the response body mapped
|
||||||
//! service::any(service_fn(|_: Request<Body>| async {
|
//! service::any(service_fn(|_: Request<Body>| async {
|
||||||
//! let res = Response::new(Body::from("Hi from `GET /`"));
|
//! let res = Response::new(Body::from("Hi from `GET /`"));
|
||||||
//! Ok(res)
|
//! Ok::<_, Infallible>(res)
|
||||||
//! }))
|
//! }))
|
||||||
//! )
|
//! )
|
||||||
//! .route(
|
//! .route(
|
||||||
|
@ -216,13 +216,20 @@
|
||||||
//! let body = Body::from(format!("Hi from `{} /foo`", req.method()));
|
//! let body = Body::from(format!("Hi from `{} /foo`", req.method()));
|
||||||
//! let body = axum::body::box_body(body);
|
//! let body = axum::body::box_body(body);
|
||||||
//! let res = Response::new(body);
|
//! let res = Response::new(body);
|
||||||
//! Ok(res)
|
//! Ok::<_, Infallible>(res)
|
||||||
//! })
|
//! })
|
||||||
//! )
|
//! )
|
||||||
//! .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(ServeFile::new("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 {
|
//! # async {
|
||||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||||
|
@ -270,12 +277,12 @@
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use axum::{
|
//! use axum::{
|
||||||
//! Router,
|
//! Router, service,
|
||||||
//! service,
|
|
||||||
//! handler::get,
|
|
||||||
//! http::{Request, Response},
|
|
||||||
//! response::IntoResponse,
|
|
||||||
//! body::Body,
|
//! body::Body,
|
||||||
|
//! handler::get,
|
||||||
|
//! response::IntoResponse,
|
||||||
|
//! http::{Request, Response},
|
||||||
|
//! error_handling::HandleErrorExt,
|
||||||
//! };
|
//! };
|
||||||
//! use std::{io, convert::Infallible};
|
//! use std::{io, convert::Infallible};
|
||||||
//! use tower::service_fn;
|
//! use tower::service_fn;
|
||||||
|
@ -293,8 +300,7 @@
|
||||||
//! .handle_error(handle_io_error),
|
//! .handle_error(handle_io_error),
|
||||||
//! );
|
//! );
|
||||||
//!
|
//!
|
||||||
//! fn handle_io_error(error: io::Error) -> Result<impl IntoResponse, Infallible> {
|
//! fn handle_io_error(error: io::Error) -> impl IntoResponse {
|
||||||
//! # Ok(())
|
|
||||||
//! // ...
|
//! // ...
|
||||||
//! }
|
//! }
|
||||||
//! # async {
|
//! # async {
|
||||||
|
@ -664,7 +670,7 @@
|
||||||
//! 1. Use `Result<T, T::Rejection>` as your extractor like shown in ["Optional
|
//! 1. Use `Result<T, T::Rejection>` as your extractor like shown in ["Optional
|
||||||
//! extractors"](#optional-extractors). This works well if you're only using
|
//! extractors"](#optional-extractors). This works well if you're only using
|
||||||
//! the extractor in a single handler.
|
//! the extractor in a single handler.
|
||||||
//! 2. Create your own extractor that in its [`FromRequest`] implementing calls
|
//! 2. Create your own extractor that in its [`FromRequest`] implemention calls
|
||||||
//! one of axum's built in extractors but returns a different response for
|
//! one of axum's built in extractors but returns a different response for
|
||||||
//! rejections. See the [customize-extractor-error] example for more details.
|
//! rejections. See the [customize-extractor-error] example for more details.
|
||||||
//!
|
//!
|
||||||
|
@ -781,6 +787,26 @@
|
||||||
//! # };
|
//! # };
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! # Error handling
|
||||||
|
//!
|
||||||
|
//! 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 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.
|
||||||
|
//!
|
||||||
|
//! 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`].
|
||||||
|
//!
|
||||||
|
//! You can find examples here:
|
||||||
|
//! - [Routing to fallible services](#routing-to-fallible-services)
|
||||||
|
//! - [Applying fallible middleware](#applying-multiple-middleware)
|
||||||
|
//!
|
||||||
//! # 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
|
||||||
|
@ -864,69 +890,6 @@
|
||||||
//! # };
|
//! # };
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Error handling
|
|
||||||
//!
|
|
||||||
//! Handlers created from async functions must always produce a response, even
|
|
||||||
//! when returning a `Result<T, E>` the error type must implement
|
|
||||||
//! [`IntoResponse`]. In practice this makes error handling very predictable and
|
|
||||||
//! easier to reason about.
|
|
||||||
//!
|
|
||||||
//! However when applying middleware, or embedding other tower services, errors
|
|
||||||
//! might happen. For example [`Timeout`] will return an error if the timeout
|
|
||||||
//! elapses. By default these errors will be propagated all the way up to hyper
|
|
||||||
//! where the connection will be closed. If that isn't desirable you can call
|
|
||||||
//! [`handle_error`](handler::Layered::handle_error) to handle errors from
|
|
||||||
//! adding a middleware to a handler:
|
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
|
||||||
//! use axum::{
|
|
||||||
//! handler::{get, Handler},
|
|
||||||
//! Router,
|
|
||||||
//! };
|
|
||||||
//! use tower::{
|
|
||||||
//! BoxError, timeout::{TimeoutLayer, error::Elapsed},
|
|
||||||
//! };
|
|
||||||
//! use std::{borrow::Cow, time::Duration, convert::Infallible};
|
|
||||||
//! use http::StatusCode;
|
|
||||||
//!
|
|
||||||
//! let app = Router::new()
|
|
||||||
//! .route(
|
|
||||||
//! "/",
|
|
||||||
//! get(handle
|
|
||||||
//! .layer(TimeoutLayer::new(Duration::from_secs(30)))
|
|
||||||
//! // `Timeout` uses `BoxError` as the error type
|
|
||||||
//! .handle_error(|error: BoxError| {
|
|
||||||
//! // Check if the actual error type is `Elapsed` which
|
|
||||||
//! // `Timeout` returns
|
|
||||||
//! if error.is::<Elapsed>() {
|
|
||||||
//! return Ok::<_, Infallible>((
|
|
||||||
//! StatusCode::REQUEST_TIMEOUT,
|
|
||||||
//! "Request took too long".into(),
|
|
||||||
//! ));
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! // If we encounter some error we don't handle return a generic
|
|
||||||
//! // error
|
|
||||||
//! return Ok::<_, Infallible>((
|
|
||||||
//! StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
//! // `Cow` lets us return either `&str` or `String`
|
|
||||||
//! Cow::from(format!("Unhandled internal error: {}", error)),
|
|
||||||
//! ));
|
|
||||||
//! })),
|
|
||||||
//! );
|
|
||||||
//!
|
|
||||||
//! async fn handle() {}
|
|
||||||
//! # async {
|
|
||||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
|
||||||
//! # };
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! The closure passed to [`handle_error`](handler::Layered::handle_error) must
|
|
||||||
//! return `Result<T, E>` where `T` implements
|
|
||||||
//! [`IntoResponse`](response::IntoResponse).
|
|
||||||
//!
|
|
||||||
//! See [`routing::Router::handle_error`] for more details.
|
|
||||||
//!
|
|
||||||
//! ## Applying multiple middleware
|
//! ## Applying multiple middleware
|
||||||
//!
|
//!
|
||||||
//! [`tower::ServiceBuilder`] can be used to combine multiple middleware:
|
//! [`tower::ServiceBuilder`] can be used to combine multiple middleware:
|
||||||
|
@ -935,14 +898,21 @@
|
||||||
//! use axum::{
|
//! use axum::{
|
||||||
//! body::Body,
|
//! body::Body,
|
||||||
//! handler::get,
|
//! handler::get,
|
||||||
//! http::Request,
|
//! http::{Request, StatusCode},
|
||||||
//! Router,
|
//! error_handling::HandleErrorLayer,
|
||||||
|
//! response::IntoResponse,
|
||||||
|
//! Router, BoxError,
|
||||||
//! };
|
//! };
|
||||||
//! use tower::ServiceBuilder;
|
//! use tower::ServiceBuilder;
|
||||||
//! use tower_http::compression::CompressionLayer;
|
//! use tower_http::compression::CompressionLayer;
|
||||||
//! use std::{borrow::Cow, time::Duration};
|
//! use std::{borrow::Cow, time::Duration};
|
||||||
//!
|
//!
|
||||||
//! let middleware_stack = ServiceBuilder::new()
|
//! 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
|
//! // Return an error after 30 seconds
|
||||||
//! .timeout(Duration::from_secs(30))
|
//! .timeout(Duration::from_secs(30))
|
||||||
//! // Shed load if we're receiving too many requests
|
//! // Shed load if we're receiving too many requests
|
||||||
|
@ -950,17 +920,25 @@
|
||||||
//! // Process at most 100 requests concurrently
|
//! // Process at most 100 requests concurrently
|
||||||
//! .concurrency_limit(100)
|
//! .concurrency_limit(100)
|
||||||
//! // Compress response bodies
|
//! // Compress response bodies
|
||||||
//! .layer(CompressionLayer::new())
|
//! .layer(CompressionLayer::new());
|
||||||
//! .into_inner();
|
|
||||||
//!
|
//!
|
||||||
//! let app = Router::new()
|
//! let app = Router::new()
|
||||||
//! .route("/", get(|_: Request<Body>| async { /* ... */ }))
|
//! .route("/", get(|_: Request<Body>| async { /* ... */ }))
|
||||||
//! .layer(middleware_stack);
|
//! .layer(middleware_stack);
|
||||||
|
//!
|
||||||
|
//! fn handle_error(error: BoxError) -> impl IntoResponse {
|
||||||
|
//! (
|
||||||
|
//! StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
//! format!("Something went wrong: {}", error),
|
||||||
|
//! )
|
||||||
|
//! }
|
||||||
//! # async {
|
//! # async {
|
||||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
//! # 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
|
//! ## Commonly used middleware
|
||||||
//!
|
//!
|
||||||
//! [`tower::util`] and [`tower_http`] have a large collection of middleware that are compatible
|
//! [`tower::util`] and [`tower_http`] have a large collection of middleware that are compatible
|
||||||
|
@ -971,6 +949,7 @@
|
||||||
//! body::{Body, BoxBody},
|
//! body::{Body, BoxBody},
|
||||||
//! handler::get,
|
//! handler::get,
|
||||||
//! http::{Request, Response},
|
//! http::{Request, Response},
|
||||||
|
//! error_handling::HandleErrorLayer,
|
||||||
//! Router,
|
//! Router,
|
||||||
//! };
|
//! };
|
||||||
//! use tower::{
|
//! use tower::{
|
||||||
|
@ -980,15 +959,23 @@
|
||||||
//! };
|
//! };
|
||||||
//! use std::convert::Infallible;
|
//! use std::convert::Infallible;
|
||||||
//! use tower_http::trace::TraceLayer;
|
//! use tower_http::trace::TraceLayer;
|
||||||
|
//! #
|
||||||
|
//! # fn handle_error<T>(error: T) -> axum::http::StatusCode {
|
||||||
|
//! # axum::http::StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
//! # }
|
||||||
//!
|
//!
|
||||||
//! let middleware_stack = ServiceBuilder::new()
|
//! 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
|
//! // `TraceLayer` adds high level tracing and logging
|
||||||
//! .layer(TraceLayer::new_for_http())
|
//! .layer(TraceLayer::new_for_http())
|
||||||
//! // `AsyncFilterLayer` lets you asynchronously transform the request
|
//! // `AsyncFilterLayer` lets you asynchronously transform the request
|
||||||
//! .layer(AsyncFilterLayer::new(map_request))
|
//! .layer(AsyncFilterLayer::new(map_request))
|
||||||
//! // `AndThenLayer` lets you asynchronously transform the response
|
//! // `AndThenLayer` lets you asynchronously transform the response
|
||||||
//! .layer(AndThenLayer::new(map_response))
|
//! .layer(AndThenLayer::new(map_response));
|
||||||
//! .into_inner();
|
|
||||||
//!
|
//!
|
||||||
//! async fn map_request(req: Request<Body>) -> Result<Request<Body>, Infallible> {
|
//! async fn map_request(req: Request<Body>) -> Result<Request<Body>, Infallible> {
|
||||||
//! Ok(req)
|
//! Ok(req)
|
||||||
|
@ -1010,6 +997,8 @@
|
||||||
//! a middleware. Among other things, this can be useful for doing authorization. See
|
//! a middleware. Among other things, this can be useful for doing authorization. See
|
||||||
//! [`extract::extractor_middleware()`] for more details.
|
//! [`extract::extractor_middleware()`] for more details.
|
||||||
//!
|
//!
|
||||||
|
//! See [Error handling](#error-handling) for more details on general error handling in axum.
|
||||||
|
//!
|
||||||
//! ## Writing your own middleware
|
//! ## Writing your own middleware
|
||||||
//!
|
//!
|
||||||
//! You can also write you own middleware by implementing [`tower::Service`]:
|
//! You can also write you own middleware by implementing [`tower::Service`]:
|
||||||
|
@ -1166,7 +1155,7 @@
|
||||||
//! [`Service`]: tower::Service
|
//! [`Service`]: tower::Service
|
||||||
//! [`Service::poll_ready`]: tower::Service::poll_ready
|
//! [`Service::poll_ready`]: tower::Service::poll_ready
|
||||||
//! [`tower::Service`]: tower::Service
|
//! [`tower::Service`]: tower::Service
|
||||||
//! [`handle_error`]: routing::Router::handle_error
|
//! [`handle_error`]: error_handling::HandleErrorExt::handle_error
|
||||||
//! [tower-guides]: https://github.com/tower-rs/tower/tree/master/guides
|
//! [tower-guides]: https://github.com/tower-rs/tower/tree/master/guides
|
||||||
//! [`Uuid`]: https://docs.rs/uuid/latest/uuid/
|
//! [`Uuid`]: https://docs.rs/uuid/latest/uuid/
|
||||||
//! [`FromRequest`]: crate::extract::FromRequest
|
//! [`FromRequest`]: crate::extract::FromRequest
|
||||||
|
@ -1176,6 +1165,7 @@
|
||||||
//! [axum-debug]: https://docs.rs/axum-debug
|
//! [axum-debug]: https://docs.rs/axum-debug
|
||||||
//! [`debug_handler`]: https://docs.rs/axum-debug/latest/axum_debug/attr.debug_handler.html
|
//! [`debug_handler`]: https://docs.rs/axum-debug/latest/axum_debug/attr.debug_handler.html
|
||||||
//! [`Handler`]: crate::handler::Handler
|
//! [`Handler`]: crate::handler::Handler
|
||||||
|
//! [`Infallible`]: std::convert::Infallible
|
||||||
|
|
||||||
#![warn(
|
#![warn(
|
||||||
clippy::all,
|
clippy::all,
|
||||||
|
@ -1227,6 +1217,7 @@ mod json;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub mod body;
|
pub mod body;
|
||||||
|
pub mod error_handling;
|
||||||
pub mod extract;
|
pub mod extract;
|
||||||
pub mod handler;
|
pub mod handler;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
|
@ -4,7 +4,6 @@ use crate::{
|
||||||
body::BoxBody,
|
body::BoxBody,
|
||||||
clone_box_service::CloneBoxService,
|
clone_box_service::CloneBoxService,
|
||||||
routing::{FromEmptyRouter, UriStack},
|
routing::{FromEmptyRouter, UriStack},
|
||||||
BoxError,
|
|
||||||
};
|
};
|
||||||
use http::{Request, Response};
|
use http::{Request, Response};
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
|
@ -28,33 +27,24 @@ opaque_future! {
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
/// The response future for [`BoxRoute`](super::BoxRoute).
|
/// The response future for [`BoxRoute`](super::BoxRoute).
|
||||||
pub struct BoxRouteFuture<B, E>
|
pub struct BoxRouteFuture<B> {
|
||||||
where
|
|
||||||
E: Into<BoxError>,
|
|
||||||
{
|
|
||||||
#[pin]
|
#[pin]
|
||||||
pub(super) inner: Oneshot<
|
pub(super) inner: Oneshot<
|
||||||
CloneBoxService<Request<B>, Response<BoxBody>, E>,
|
CloneBoxService<Request<B>, Response<BoxBody>, Infallible>,
|
||||||
Request<B>,
|
Request<B>,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B, E> Future for BoxRouteFuture<B, E>
|
impl<B> Future for BoxRouteFuture<B> {
|
||||||
where
|
type Output = Result<Response<BoxBody>, Infallible>;
|
||||||
E: Into<BoxError>,
|
|
||||||
{
|
|
||||||
type Output = Result<Response<BoxBody>, E>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
self.project().inner.poll(cx)
|
self.project().inner.poll(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B, E> fmt::Debug for BoxRouteFuture<B, E>
|
impl<B> fmt::Debug for BoxRouteFuture<B> {
|
||||||
where
|
|
||||||
E: Into<BoxError>,
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("BoxRouteFuture").finish()
|
f.debug_struct("BoxRouteFuture").finish()
|
||||||
}
|
}
|
||||||
|
@ -112,11 +102,11 @@ pin_project! {
|
||||||
|
|
||||||
impl<S, F, B> Future for RouteFuture<S, F, B>
|
impl<S, F, B> Future for RouteFuture<S, F, B>
|
||||||
where
|
where
|
||||||
S: Service<Request<B>, Response = Response<BoxBody>>,
|
S: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>,
|
||||||
F: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error>,
|
F: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>,
|
||||||
B: Send + Sync + 'static,
|
B: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
type Output = Result<Response<BoxBody>, S::Error>;
|
type Output = Result<Response<BoxBody>, Infallible>;
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
match self.project().state.project() {
|
match self.project().state.project() {
|
||||||
|
@ -141,11 +131,11 @@ pin_project! {
|
||||||
|
|
||||||
impl<S, F, B> Future for NestedFuture<S, F, B>
|
impl<S, F, B> Future for NestedFuture<S, F, B>
|
||||||
where
|
where
|
||||||
S: Service<Request<B>, Response = Response<BoxBody>>,
|
S: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>,
|
||||||
F: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error>,
|
F: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>,
|
||||||
B: Send + Sync + 'static,
|
B: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
type Output = Result<Response<BoxBody>, S::Error>;
|
type Output = Result<Response<BoxBody>, Infallible>;
|
||||||
|
|
||||||
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 mut res: Response<_> = futures_util::ready!(self.project().inner.poll(cx)?);
|
let mut res: Response<_> = futures_util::ready!(self.project().inner.poll(cx)?);
|
||||||
|
|
|
@ -8,7 +8,6 @@ use crate::{
|
||||||
connect_info::{Connected, IntoMakeServiceWithConnectInfo},
|
connect_info::{Connected, IntoMakeServiceWithConnectInfo},
|
||||||
OriginalUri,
|
OriginalUri,
|
||||||
},
|
},
|
||||||
service::HandleError,
|
|
||||||
util::{ByteStr, PercentDecodedByteStr},
|
util::{ByteStr, PercentDecodedByteStr},
|
||||||
BoxError,
|
BoxError,
|
||||||
};
|
};
|
||||||
|
@ -64,7 +63,7 @@ enum MaybeSharedNode {
|
||||||
Shared(Arc<Node<RouteId>>),
|
Shared(Arc<Node<RouteId>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> Router<EmptyRouter<E>> {
|
impl Router<EmptyRouter> {
|
||||||
/// Create a new `Router`.
|
/// 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 to `404 Not Found` to
|
||||||
|
@ -77,7 +76,7 @@ impl<E> Router<EmptyRouter<E>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> Default for Router<EmptyRouter<E>> {
|
impl Default for Router<EmptyRouter> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
|
@ -176,7 +175,10 @@ impl<S> Router<S> {
|
||||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||||
/// # };
|
/// # };
|
||||||
/// ```
|
/// ```
|
||||||
pub fn route<T>(mut self, path: &str, svc: T) -> Router<Route<T, S>> {
|
pub fn route<T, B>(mut self, path: &str, svc: T) -> Router<Route<T, S>>
|
||||||
|
where
|
||||||
|
T: Service<Request<B>, Error = Infallible>,
|
||||||
|
{
|
||||||
let id = RouteId::next();
|
let id = RouteId::next();
|
||||||
|
|
||||||
if let Err(err) = self.update_node(|node| node.insert(path, id)) {
|
if let Err(err) = self.update_node(|node| node.insert(path, id)) {
|
||||||
|
@ -262,11 +264,20 @@ impl<S> Router<S> {
|
||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// Router,
|
/// Router,
|
||||||
/// service::get,
|
/// service::get,
|
||||||
|
/// error_handling::HandleErrorExt,
|
||||||
|
/// http::StatusCode,
|
||||||
/// };
|
/// };
|
||||||
|
/// use std::{io, convert::Infallible};
|
||||||
/// 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: io::Error| {
|
||||||
|
/// (
|
||||||
|
/// StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
/// format!("Unhandled internal error: {}", error),
|
||||||
|
/// )
|
||||||
|
/// });
|
||||||
///
|
///
|
||||||
/// let app = Router::new().nest("/public", get(serve_dir_service));
|
/// let app = Router::new().nest("/public", get(serve_dir_service));
|
||||||
/// # async {
|
/// # async {
|
||||||
|
@ -305,7 +316,10 @@ impl<S> Router<S> {
|
||||||
/// for more details.
|
/// for more details.
|
||||||
///
|
///
|
||||||
/// [`OriginalUri`]: crate::extract::OriginalUri
|
/// [`OriginalUri`]: crate::extract::OriginalUri
|
||||||
pub fn nest<T>(mut self, path: &str, svc: T) -> Router<Nested<T, S>> {
|
pub fn nest<T, B>(mut self, path: &str, svc: T) -> Router<Nested<T, S>>
|
||||||
|
where
|
||||||
|
T: Service<Request<B>, Error = Infallible>,
|
||||||
|
{
|
||||||
let id = RouteId::next();
|
let id = RouteId::next();
|
||||||
|
|
||||||
if path.contains('*') {
|
if path.contains('*') {
|
||||||
|
@ -361,10 +375,13 @@ impl<S> Router<S> {
|
||||||
///
|
///
|
||||||
/// It also helps with compile times when you have a very large number of
|
/// It also helps with compile times when you have a very large number of
|
||||||
/// routes.
|
/// routes.
|
||||||
pub fn boxed<ReqBody, ResBody>(self) -> Router<BoxRoute<ReqBody, S::Error>>
|
pub fn boxed<ReqBody, ResBody>(self) -> Router<BoxRoute<ReqBody>>
|
||||||
where
|
where
|
||||||
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + Sync + 'static,
|
S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible>
|
||||||
S::Error: Into<BoxError> + Send,
|
+ Clone
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
S::Future: Send,
|
S::Future: Send,
|
||||||
ReqBody: Send + 'static,
|
ReqBody: Send + 'static,
|
||||||
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||||
|
@ -374,8 +391,7 @@ impl<S> Router<S> {
|
||||||
ServiceBuilder::new()
|
ServiceBuilder::new()
|
||||||
.layer_fn(BoxRoute)
|
.layer_fn(BoxRoute)
|
||||||
.layer_fn(CloneBoxService::new)
|
.layer_fn(CloneBoxService::new)
|
||||||
.layer(MapResponseBodyLayer::new(box_body))
|
.layer(MapResponseBodyLayer::new(box_body)),
|
||||||
.into_inner(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,100 +617,19 @@ impl<S> Router<S> {
|
||||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||||
/// # };
|
/// # };
|
||||||
/// ```
|
/// ```
|
||||||
pub fn or<S2>(self, other: S2) -> Router<Or<S, S2>> {
|
pub fn or<T, B>(self, other: T) -> Router<Or<S, T>>
|
||||||
|
where
|
||||||
|
T: Service<Request<B>, Error = Infallible>,
|
||||||
|
{
|
||||||
self.map(|first| Or {
|
self.map(|first| Or {
|
||||||
first,
|
first,
|
||||||
second: other,
|
second: other,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle errors services in this router might produce, by mapping them to
|
fn map<F, T>(self, f: F) -> Router<T>
|
||||||
/// responses.
|
|
||||||
///
|
|
||||||
/// Unhandled errors will close the connection without sending a response.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use axum::{
|
|
||||||
/// handler::get,
|
|
||||||
/// http::StatusCode,
|
|
||||||
/// Router,
|
|
||||||
/// };
|
|
||||||
/// use tower::{BoxError, timeout::TimeoutLayer};
|
|
||||||
/// use std::{time::Duration, convert::Infallible};
|
|
||||||
///
|
|
||||||
/// // This router can never fail, since handlers can never fail.
|
|
||||||
/// let app = Router::new().route("/", get(|| async {}));
|
|
||||||
///
|
|
||||||
/// // Now the router can fail since the `tower::timeout::Timeout`
|
|
||||||
/// // middleware will return an error if the timeout elapses.
|
|
||||||
/// let app = app.layer(TimeoutLayer::new(Duration::from_secs(10)));
|
|
||||||
///
|
|
||||||
/// // With `handle_error` we can handle errors `Timeout` might produce.
|
|
||||||
/// // Our router now cannot fail, that is its error type is `Infallible`.
|
|
||||||
/// let app = app.handle_error(|error: BoxError| {
|
|
||||||
/// if error.is::<tower::timeout::error::Elapsed>() {
|
|
||||||
/// Ok::<_, Infallible>((
|
|
||||||
/// StatusCode::REQUEST_TIMEOUT,
|
|
||||||
/// "request took too long to handle".to_string(),
|
|
||||||
/// ))
|
|
||||||
/// } else {
|
|
||||||
/// Ok::<_, Infallible>((
|
|
||||||
/// StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
/// format!("Unhandled error: {}", error),
|
|
||||||
/// ))
|
|
||||||
/// }
|
|
||||||
/// });
|
|
||||||
/// # async {
|
|
||||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
|
||||||
/// # };
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// You can return `Err(_)` from the closure if you don't wish to handle
|
|
||||||
/// some errors:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use axum::{
|
|
||||||
/// handler::get,
|
|
||||||
/// http::StatusCode,
|
|
||||||
/// Router,
|
|
||||||
/// };
|
|
||||||
/// use tower::{BoxError, timeout::TimeoutLayer};
|
|
||||||
/// use std::time::Duration;
|
|
||||||
///
|
|
||||||
/// let app = Router::new()
|
|
||||||
/// .route("/", get(|| async {}))
|
|
||||||
/// .layer(TimeoutLayer::new(Duration::from_secs(10)))
|
|
||||||
/// .handle_error(|error: BoxError| {
|
|
||||||
/// if error.is::<tower::timeout::error::Elapsed>() {
|
|
||||||
/// Ok((
|
|
||||||
/// StatusCode::REQUEST_TIMEOUT,
|
|
||||||
/// "request took too long to handle".to_string(),
|
|
||||||
/// ))
|
|
||||||
/// } else {
|
|
||||||
/// // return the error as is
|
|
||||||
/// Err(error)
|
|
||||||
/// }
|
|
||||||
/// });
|
|
||||||
/// # async {
|
|
||||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
|
||||||
/// # };
|
|
||||||
/// ```
|
|
||||||
pub fn handle_error<ReqBody, F>(self, f: F) -> Router<HandleError<S, F, ReqBody>> {
|
|
||||||
self.map(|svc| HandleError::new(svc, f))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check that your service cannot fail.
|
|
||||||
///
|
|
||||||
/// That is, its error type is [`Infallible`].
|
|
||||||
pub fn check_infallible(self) -> Router<CheckInfallible<S>> {
|
|
||||||
self.map(CheckInfallible)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map<F, S2>(self, f: F) -> Router<S2>
|
|
||||||
where
|
where
|
||||||
F: FnOnce(S) -> S2,
|
F: FnOnce(S) -> T,
|
||||||
{
|
{
|
||||||
Router {
|
Router {
|
||||||
routes: f(self.routes),
|
routes: f(self.routes),
|
||||||
|
@ -740,11 +675,11 @@ impl<S> Router<S> {
|
||||||
|
|
||||||
impl<ReqBody, ResBody, S> Service<Request<ReqBody>> for Router<S>
|
impl<ReqBody, ResBody, S> Service<Request<ReqBody>> for Router<S>
|
||||||
where
|
where
|
||||||
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
|
S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible> + Clone,
|
||||||
ReqBody: Send + Sync + 'static,
|
ReqBody: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
type Response = Response<ResBody>;
|
type Response = Response<ResBody>;
|
||||||
type Error = S::Error;
|
type Error = Infallible;
|
||||||
type Future = S::Future;
|
type Future = S::Future;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -949,12 +884,12 @@ pub struct Route<S, T> {
|
||||||
|
|
||||||
impl<B, S, T> Service<Request<B>> for Route<S, T>
|
impl<B, S, T> Service<Request<B>> for Route<S, T>
|
||||||
where
|
where
|
||||||
S: Service<Request<B>, Response = Response<BoxBody>> + Clone,
|
S: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible> + Clone,
|
||||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error> + Clone,
|
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible> + Clone,
|
||||||
B: Send + Sync + 'static,
|
B: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
type Response = Response<BoxBody>;
|
type Response = Response<BoxBody>;
|
||||||
type Error = S::Error;
|
type Error = Infallible;
|
||||||
type Future = RouteFuture<S, T, B>;
|
type Future = RouteFuture<S, T, B>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -988,12 +923,12 @@ pub struct Nested<S, T> {
|
||||||
|
|
||||||
impl<B, S, T> Service<Request<B>> for Nested<S, T>
|
impl<B, S, T> Service<Request<B>> for Nested<S, T>
|
||||||
where
|
where
|
||||||
S: Service<Request<B>, Response = Response<BoxBody>> + Clone,
|
S: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible> + Clone,
|
||||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error> + Clone,
|
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible> + Clone,
|
||||||
B: Send + Sync + 'static,
|
B: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
type Response = Response<BoxBody>;
|
type Response = Response<BoxBody>;
|
||||||
type Error = S::Error;
|
type Error = Infallible;
|
||||||
type Future = NestedFuture<S, T, B>;
|
type Future = NestedFuture<S, T, B>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -1020,29 +955,26 @@ where
|
||||||
/// A boxed route trait object.
|
/// A boxed route trait object.
|
||||||
///
|
///
|
||||||
/// See [`Router::boxed`] for more details.
|
/// See [`Router::boxed`] for more details.
|
||||||
pub struct BoxRoute<B = crate::body::Body, E = Infallible>(
|
pub struct BoxRoute<B = crate::body::Body>(
|
||||||
CloneBoxService<Request<B>, Response<BoxBody>, E>,
|
CloneBoxService<Request<B>, Response<BoxBody>, Infallible>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl<B, E> Clone for BoxRoute<B, E> {
|
impl<B> Clone for BoxRoute<B> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self(self.0.clone())
|
Self(self.0.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B, E> fmt::Debug for BoxRoute<B, E> {
|
impl<B> fmt::Debug for BoxRoute<B> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("BoxRoute").finish()
|
f.debug_struct("BoxRoute").finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B, E> Service<Request<B>> for BoxRoute<B, E>
|
impl<B> Service<Request<B>> for BoxRoute<B> {
|
||||||
where
|
|
||||||
E: Into<BoxError>,
|
|
||||||
{
|
|
||||||
type Response = Response<BoxBody>;
|
type Response = Response<BoxBody>;
|
||||||
type Error = E;
|
type Error = Infallible;
|
||||||
type Future = BoxRouteFuture<B, E>;
|
type Future = BoxRouteFuture<B>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
@ -1086,31 +1018,6 @@ fn with_path(uri: &Uri, new_path: &str) -> Uri {
|
||||||
Uri::from_parts(parts).unwrap()
|
Uri::from_parts(parts).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Middleware that statically verifies that a service cannot fail.
|
|
||||||
///
|
|
||||||
/// Created with [`check_infallible`](Router::check_infallible).
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct CheckInfallible<S>(S);
|
|
||||||
|
|
||||||
impl<R, S> Service<R> for CheckInfallible<S>
|
|
||||||
where
|
|
||||||
S: Service<R, Error = Infallible>,
|
|
||||||
{
|
|
||||||
type Response = S::Response;
|
|
||||||
type Error = S::Error;
|
|
||||||
type Future = S::Future;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
self.0.poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn call(&mut self, req: R) -> Self::Future {
|
|
||||||
self.0.call(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [`MakeService`] that produces axum router services.
|
/// A [`MakeService`] that produces axum router services.
|
||||||
///
|
///
|
||||||
/// [`MakeService`]: tower::make::MakeService
|
/// [`MakeService`]: tower::make::MakeService
|
||||||
|
@ -1161,15 +1068,12 @@ mod tests {
|
||||||
assert_send::<EmptyRouter<NotSendSync>>();
|
assert_send::<EmptyRouter<NotSendSync>>();
|
||||||
assert_sync::<EmptyRouter<NotSendSync>>();
|
assert_sync::<EmptyRouter<NotSendSync>>();
|
||||||
|
|
||||||
assert_send::<BoxRoute<(), ()>>();
|
assert_send::<BoxRoute<()>>();
|
||||||
assert_sync::<BoxRoute<(), ()>>();
|
assert_sync::<BoxRoute<()>>();
|
||||||
|
|
||||||
assert_send::<Nested<(), ()>>();
|
assert_send::<Nested<(), ()>>();
|
||||||
assert_sync::<Nested<(), ()>>();
|
assert_sync::<Nested<(), ()>>();
|
||||||
|
|
||||||
assert_send::<CheckInfallible<()>>();
|
|
||||||
assert_sync::<CheckInfallible<()>>();
|
|
||||||
|
|
||||||
assert_send::<IntoMakeService<()>>();
|
assert_send::<IntoMakeService<()>>();
|
||||||
assert_sync::<IntoMakeService<()>>();
|
assert_sync::<IntoMakeService<()>>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use futures_util::ready;
|
||||||
use http::{Request, Response};
|
use http::{Request, Response};
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
future::Future,
|
future::Future,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
|
@ -33,8 +34,8 @@ fn traits() {
|
||||||
|
|
||||||
impl<A, B, ReqBody> Service<Request<ReqBody>> for Or<A, B>
|
impl<A, B, ReqBody> Service<Request<ReqBody>> for Or<A, B>
|
||||||
where
|
where
|
||||||
A: Service<Request<ReqBody>, Response = Response<BoxBody>> + Clone,
|
A: Service<Request<ReqBody>, Response = Response<BoxBody>, Error = Infallible> + Clone,
|
||||||
B: Service<Request<ReqBody>, Response = Response<BoxBody>, Error = A::Error> + Clone,
|
B: Service<Request<ReqBody>, Response = Response<BoxBody>, Error = Infallible> + Clone,
|
||||||
ReqBody: Send + Sync + 'static,
|
ReqBody: Send + Sync + 'static,
|
||||||
A: Send + 'static,
|
A: Send + 'static,
|
||||||
B: Send + 'static,
|
B: Send + 'static,
|
||||||
|
@ -42,7 +43,7 @@ where
|
||||||
B::Future: Send + 'static,
|
B::Future: Send + 'static,
|
||||||
{
|
{
|
||||||
type Response = Response<BoxBody>;
|
type Response = Response<BoxBody>;
|
||||||
type Error = A::Error;
|
type Error = Infallible;
|
||||||
type Future = ResponseFuture<A, B, ReqBody>;
|
type Future = ResponseFuture<A, B, ReqBody>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
@ -89,11 +90,11 @@ pin_project! {
|
||||||
|
|
||||||
impl<A, B, ReqBody> Future for ResponseFuture<A, B, ReqBody>
|
impl<A, B, ReqBody> Future for ResponseFuture<A, B, ReqBody>
|
||||||
where
|
where
|
||||||
A: Service<Request<ReqBody>, Response = Response<BoxBody>>,
|
A: Service<Request<ReqBody>, Response = Response<BoxBody>, Error = Infallible>,
|
||||||
B: Service<Request<ReqBody>, Response = Response<BoxBody>, Error = A::Error>,
|
B: Service<Request<ReqBody>, Response = Response<BoxBody>, Error = Infallible>,
|
||||||
ReqBody: Send + Sync + 'static,
|
ReqBody: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
type Output = Result<Response<BoxBody>, A::Error>;
|
type Output = Result<Response<BoxBody>, Infallible>;
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{box_body, BoxBody},
|
body::{box_body, BoxBody},
|
||||||
response::IntoResponse,
|
|
||||||
util::{Either, EitherProj},
|
util::{Either, EitherProj},
|
||||||
BoxError,
|
BoxError,
|
||||||
};
|
};
|
||||||
|
@ -19,42 +18,6 @@ use std::{
|
||||||
use tower::util::Oneshot;
|
use tower::util::Oneshot;
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
|
|
||||||
pin_project! {
|
|
||||||
/// Response future for [`HandleError`](super::HandleError).
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct HandleErrorFuture<Fut, F> {
|
|
||||||
#[pin]
|
|
||||||
pub(super) inner: Fut,
|
|
||||||
pub(super) f: Option<F>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Fut, F, E, E2, B, Res> Future for HandleErrorFuture<Fut, F>
|
|
||||||
where
|
|
||||||
Fut: Future<Output = Result<Response<B>, E>>,
|
|
||||||
F: FnOnce(E) -> Result<Res, E2>,
|
|
||||||
Res: IntoResponse,
|
|
||||||
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
|
||||||
B::Error: Into<BoxError> + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
type Output = Result<Response<BoxBody>, E2>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let this = self.project();
|
|
||||||
|
|
||||||
match ready!(this.inner.poll(cx)) {
|
|
||||||
Ok(res) => Ok(res.map(box_body)).into(),
|
|
||||||
Err(err) => {
|
|
||||||
let f = this.f.take().unwrap();
|
|
||||||
match f(err) {
|
|
||||||
Ok(res) => Ok(res.into_response().map(box_body)).into(),
|
|
||||||
Err(err) => Err(err).into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
/// The response future for [`OnMethod`](super::OnMethod).
|
/// The response future for [`OnMethod`](super::OnMethod).
|
||||||
pub struct OnMethodFuture<S, F, B>
|
pub struct OnMethodFuture<S, F, B>
|
||||||
|
|
|
@ -99,17 +99,15 @@
|
||||||
use crate::BoxError;
|
use crate::BoxError;
|
||||||
use crate::{
|
use crate::{
|
||||||
body::BoxBody,
|
body::BoxBody,
|
||||||
response::IntoResponse,
|
|
||||||
routing::{EmptyRouter, MethodFilter},
|
routing::{EmptyRouter, MethodFilter},
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::{Request, Response};
|
use http::{Request, Response};
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
use tower::{util::Oneshot, ServiceExt as _};
|
use tower::ServiceExt as _;
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
|
|
||||||
pub mod future;
|
pub mod future;
|
||||||
|
@ -481,18 +479,6 @@ impl<S, F, B> OnMethod<S, F, B> {
|
||||||
_request_body: PhantomData,
|
_request_body: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle errors this service might produce, by mapping them to responses.
|
|
||||||
///
|
|
||||||
/// Unhandled errors will close the connection without sending a response.
|
|
||||||
///
|
|
||||||
/// Works similarly to [`Router::handle_error`]. See that for more
|
|
||||||
/// details.
|
|
||||||
///
|
|
||||||
/// [`Router::handle_error`]: crate::routing::Router::handle_error
|
|
||||||
pub fn handle_error<ReqBody, H>(self, f: H) -> HandleError<Self, H, ReqBody> {
|
|
||||||
HandleError::new(self, f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is identical to `routing::OnMethod`'s implementation. Would be nice to find a way to clean
|
// this is identical to `routing::OnMethod`'s implementation. Would be nice to find a way to clean
|
||||||
|
@ -532,81 +518,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [`Service`] adapter that handles errors with a closure.
|
|
||||||
///
|
|
||||||
/// Created with
|
|
||||||
/// [`handler::Layered::handle_error`](crate::handler::Layered::handle_error) or
|
|
||||||
/// [`routing::Router::handle_error`](crate::routing::Router::handle_error).
|
|
||||||
/// See those methods for more details.
|
|
||||||
pub struct HandleError<S, F, B> {
|
|
||||||
inner: S,
|
|
||||||
f: F,
|
|
||||||
_marker: PhantomData<fn() -> B>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, F, B> Clone for HandleError<S, F, B>
|
|
||||||
where
|
|
||||||
S: Clone,
|
|
||||||
F: Clone,
|
|
||||||
{
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self::new(self.inner.clone(), self.f.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, F, B> HandleError<S, F, B> {
|
|
||||||
pub(crate) fn new(inner: S, f: F) -> Self {
|
|
||||||
Self {
|
|
||||||
inner,
|
|
||||||
f,
|
|
||||||
_marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, F, B> fmt::Debug for HandleError<S, F, B>
|
|
||||||
where
|
|
||||||
S: fmt::Debug,
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("HandleError")
|
|
||||||
.field("inner", &self.inner)
|
|
||||||
.field("f", &format_args!("{}", std::any::type_name::<F>()))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, F, ReqBody, ResBody, Res, E> Service<Request<ReqBody>> for HandleError<S, F, ReqBody>
|
|
||||||
where
|
|
||||||
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
|
|
||||||
F: FnOnce(S::Error) -> Result<Res, E> + Clone,
|
|
||||||
Res: IntoResponse,
|
|
||||||
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
|
||||||
ResBody::Error: Into<BoxError> + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
type Response = Response<BoxBody>;
|
|
||||||
type Error = E;
|
|
||||||
type Future = future::HandleErrorFuture<Oneshot<S, Request<ReqBody>>, F>;
|
|
||||||
|
|
||||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
|
|
||||||
future::HandleErrorFuture {
|
|
||||||
f: Some(self.f.clone()),
|
|
||||||
inner: self.inner.clone().oneshot(req),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn traits() {
|
fn traits() {
|
||||||
use crate::tests::*;
|
use crate::tests::*;
|
||||||
|
|
||||||
assert_send::<OnMethod<(), (), NotSendSync>>();
|
assert_send::<OnMethod<(), (), NotSendSync>>();
|
||||||
assert_sync::<OnMethod<(), (), NotSendSync>>();
|
assert_sync::<OnMethod<(), (), NotSendSync>>();
|
||||||
|
|
||||||
assert_send::<HandleError<(), (), NotSendSync>>();
|
|
||||||
assert_sync::<HandleError<(), (), NotSendSync>>();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::future::{pending, ready};
|
use std::future::{pending, ready};
|
||||||
use tower::{timeout::TimeoutLayer, MakeService};
|
use tower::{timeout::TimeoutLayer, ServiceBuilder};
|
||||||
|
|
||||||
async fn unit() {}
|
async fn unit() {}
|
||||||
|
|
||||||
|
@ -29,23 +29,17 @@ impl<R> Service<R> for Svc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_make_svc<M, R, T, E>(_make_svc: M)
|
|
||||||
where
|
|
||||||
M: MakeService<(), R, Response = T, Error = E>,
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_error<E>(_: E) -> Result<StatusCode, Infallible> {
|
|
||||||
Ok(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn handler() {
|
async fn handler() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/",
|
"/",
|
||||||
get(forever
|
get(forever.layer(
|
||||||
.layer(timeout())
|
ServiceBuilder::new()
|
||||||
.handle_error(|_: BoxError| Ok::<_, Infallible>(StatusCode::REQUEST_TIMEOUT))),
|
.layer(HandleErrorLayer::new(|_: BoxError| {
|
||||||
|
StatusCode::REQUEST_TIMEOUT
|
||||||
|
}))
|
||||||
|
.layer(timeout()),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
@ -58,9 +52,13 @@ async fn handler() {
|
||||||
async fn handler_multiple_methods_first() {
|
async fn handler_multiple_methods_first() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/",
|
"/",
|
||||||
get(forever
|
get(forever.layer(
|
||||||
.layer(timeout())
|
ServiceBuilder::new()
|
||||||
.handle_error(|_: BoxError| Ok::<_, Infallible>(StatusCode::REQUEST_TIMEOUT)))
|
.layer(HandleErrorLayer::new(|_: BoxError| {
|
||||||
|
StatusCode::REQUEST_TIMEOUT
|
||||||
|
}))
|
||||||
|
.layer(timeout()),
|
||||||
|
))
|
||||||
.post(unit),
|
.post(unit),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -76,9 +74,13 @@ async fn handler_multiple_methods_middle() {
|
||||||
"/",
|
"/",
|
||||||
delete(unit)
|
delete(unit)
|
||||||
.get(
|
.get(
|
||||||
forever
|
forever.layer(
|
||||||
.layer(timeout())
|
ServiceBuilder::new()
|
||||||
.handle_error(|_: BoxError| Ok::<_, Infallible>(StatusCode::REQUEST_TIMEOUT)),
|
.layer(HandleErrorLayer::new(|_: BoxError| {
|
||||||
|
StatusCode::REQUEST_TIMEOUT
|
||||||
|
}))
|
||||||
|
.layer(timeout()),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.post(unit),
|
.post(unit),
|
||||||
);
|
);
|
||||||
|
@ -94,9 +96,13 @@ async fn handler_multiple_methods_last() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/",
|
"/",
|
||||||
delete(unit).get(
|
delete(unit).get(
|
||||||
forever
|
forever.layer(
|
||||||
.layer(timeout())
|
ServiceBuilder::new()
|
||||||
.handle_error(|_: BoxError| Ok::<_, Infallible>(StatusCode::REQUEST_TIMEOUT)),
|
.layer(HandleErrorLayer::new(|_: BoxError| {
|
||||||
|
StatusCode::REQUEST_TIMEOUT
|
||||||
|
}))
|
||||||
|
.layer(timeout()),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -105,82 +111,3 @@ async fn handler_multiple_methods_last() {
|
||||||
let res = client.get("/").send().await;
|
let res = client.get("/").send().await;
|
||||||
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
|
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn service_propagates_errors() {
|
|
||||||
let app = Router::new().route("/echo", service::post::<_, Body>(Svc));
|
|
||||||
|
|
||||||
check_make_svc::<_, _, _, hyper::Error>(app.into_make_service());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn service_nested_propagates_errors() {
|
|
||||||
let app = Router::new().route(
|
|
||||||
"/echo",
|
|
||||||
Router::new().nest("/foo", service::post::<_, Body>(Svc)),
|
|
||||||
);
|
|
||||||
|
|
||||||
check_make_svc::<_, _, _, hyper::Error>(app.into_make_service());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn service_handle_on_method() {
|
|
||||||
let app = Router::new().route(
|
|
||||||
"/echo",
|
|
||||||
service::get::<_, Body>(Svc).handle_error(handle_error::<hyper::Error>),
|
|
||||||
);
|
|
||||||
|
|
||||||
check_make_svc::<_, _, _, Infallible>(app.into_make_service());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn service_handle_on_method_multiple() {
|
|
||||||
let app = Router::new().route(
|
|
||||||
"/echo",
|
|
||||||
service::get::<_, Body>(Svc)
|
|
||||||
.post(Svc)
|
|
||||||
.handle_error(handle_error::<hyper::Error>),
|
|
||||||
);
|
|
||||||
|
|
||||||
check_make_svc::<_, _, _, Infallible>(app.into_make_service());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn service_handle_on_router() {
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/echo", service::get::<_, Body>(Svc))
|
|
||||||
.handle_error(handle_error::<hyper::Error>);
|
|
||||||
|
|
||||||
check_make_svc::<_, _, _, Infallible>(app.into_make_service());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn service_handle_on_router_still_impls_routing_dsl() {
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/echo", service::get::<_, Body>(Svc))
|
|
||||||
.handle_error(handle_error::<hyper::Error>)
|
|
||||||
.route("/", get(unit));
|
|
||||||
|
|
||||||
check_make_svc::<_, _, _, Infallible>(app.into_make_service());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn layered() {
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/echo", get::<_, Body, _>(unit))
|
|
||||||
.layer(timeout())
|
|
||||||
.handle_error(handle_error::<BoxError>);
|
|
||||||
|
|
||||||
check_make_svc::<_, _, _, Infallible>(app.into_make_service());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test] // async because of `.boxed()`
|
|
||||||
async fn layered_boxed() {
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/echo", get::<_, Body, _>(unit))
|
|
||||||
.layer(timeout())
|
|
||||||
.boxed()
|
|
||||||
.handle_error(handle_error::<BoxError>);
|
|
||||||
|
|
||||||
check_make_svc::<_, _, _, Infallible>(app.into_make_service());
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#![allow(clippy::blacklisted_name)]
|
#![allow(clippy::blacklisted_name)]
|
||||||
|
|
||||||
|
use crate::error_handling::HandleErrorLayer;
|
||||||
use crate::BoxError;
|
use crate::BoxError;
|
||||||
use crate::{
|
use crate::{
|
||||||
extract::{self, Path},
|
extract::{self, Path},
|
||||||
|
@ -339,7 +340,7 @@ async fn middleware_on_single_route() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn service_in_bottom() {
|
async fn service_in_bottom() {
|
||||||
async fn handler(_req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
|
async fn handler(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||||
Ok(Response::new(hyper::Body::empty()))
|
Ok(Response::new(hyper::Body::empty()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -532,7 +533,9 @@ async fn middleware_applies_to_routes_above() {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/one", get(std::future::pending::<()>))
|
.route("/one", get(std::future::pending::<()>))
|
||||||
.layer(TimeoutLayer::new(Duration::new(0, 0)))
|
.layer(TimeoutLayer::new(Duration::new(0, 0)))
|
||||||
.handle_error(|_: BoxError| Ok::<_, Infallible>(StatusCode::REQUEST_TIMEOUT))
|
.layer(HandleErrorLayer::new(|_: BoxError| {
|
||||||
|
StatusCode::REQUEST_TIMEOUT
|
||||||
|
}))
|
||||||
.route("/two", get(|| async {}));
|
.route("/two", get(|| async {}));
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::body::box_body;
|
use crate::body::box_body;
|
||||||
|
use crate::error_handling::HandleErrorExt;
|
||||||
use crate::routing::EmptyRouter;
|
use crate::routing::EmptyRouter;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -169,10 +170,10 @@ async fn nest_static_file_server() {
|
||||||
let app = Router::new().nest(
|
let app = Router::new().nest(
|
||||||
"/static",
|
"/static",
|
||||||
service::get(tower_http::services::ServeDir::new(".")).handle_error(|error| {
|
service::get(tower_http::services::ServeDir::new(".")).handle_error(|error| {
|
||||||
Ok::<_, Infallible>((
|
(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
format!("Unhandled internal error: {}", error),
|
format!("Unhandled internal error: {}", error),
|
||||||
))
|
)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -255,5 +256,5 @@ async fn multiple_top_level_nests() {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
|
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
|
||||||
async fn nest_cannot_contain_wildcards() {
|
async fn nest_cannot_contain_wildcards() {
|
||||||
Router::<EmptyRouter>::new().nest("/one/*rest", Router::<EmptyRouter>::new());
|
Router::<EmptyRouter>::new().nest::<_, Body>("/one/*rest", Router::<EmptyRouter>::new());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{extract::OriginalUri, response::IntoResponse, Json};
|
use crate::{error_handling::HandleErrorLayer, extract::OriginalUri, response::IntoResponse, Json};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use tower::{limit::ConcurrencyLimitLayer, timeout::TimeoutLayer};
|
use tower::{limit::ConcurrencyLimitLayer, timeout::TimeoutLayer};
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ async fn layer_and_handle_error() {
|
||||||
let two = Router::new()
|
let two = Router::new()
|
||||||
.route("/timeout", get(futures::future::pending::<()>))
|
.route("/timeout", get(futures::future::pending::<()>))
|
||||||
.layer(TimeoutLayer::new(Duration::from_millis(10)))
|
.layer(TimeoutLayer::new(Duration::from_millis(10)))
|
||||||
.handle_error(|_| Ok(StatusCode::REQUEST_TIMEOUT));
|
.layer(HandleErrorLayer::new(|_| StatusCode::REQUEST_TIMEOUT));
|
||||||
let app = one.or(two);
|
let app = one.or(two);
|
||||||
|
|
||||||
let client = TestClient::new(app);
|
let client = TestClient::new(app);
|
||||||
|
|
Loading…
Reference in a new issue