Change Handler to have an associated Future type (#879)

* Change `Handler` to have an associated `Future` type

This removes `#[async_trait]` from `Handler` and replaces that with an
associated `Future` type.

As hinted at in #878 I'm working on something with types that need to
implement `Handler`. I'm doing that by wrapping other `Handler` types so
I can implement `Handler` by simply delegating and thus don't need to
allocate another box for `#[async_trait]`. This change makes that
possible.

It does make `Handler` less ergonomic to implement but thats a very
niche feature so I'm fine with that. It wouldn't be appropriate for
`FromRequest` IMO.

* changelog
This commit is contained in:
David Pedersen 2022-03-21 14:32:06 +01:00 committed by GitHub
parent 6175f95f41
commit 56e2d57320
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 30 deletions

View File

@ -73,6 +73,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`IntoResponseParts` so `([("x-foo", "foo")], response)` now works ([#797]) `IntoResponseParts` so `([("x-foo", "foo")], response)` now works ([#797])
- **breaking:** `InvalidJsonBody` has been replaced with `JsonDataError` to clearly signal that the - **breaking:** `InvalidJsonBody` has been replaced with `JsonDataError` to clearly signal that the
request body was syntactically valid JSON but couldn't be deserialized into the target type request body was syntactically valid JSON but couldn't be deserialized into the target type
- **breaking:** `Handler` is no longer an `#[async_trait]` but instead has an
associated `Future` type. That allows users to build their own `Handler` types
without paying the cost of `#[async_trait]` ([#879])
- **changed:** New `JsonSyntaxError` variant added to `JsonRejection`. This is returned when the - **changed:** New `JsonSyntaxError` variant added to `JsonRejection`. This is returned when the
request body contains syntactically invalid JSON request body contains syntactically invalid JSON
- **fixed:** Set `Allow` header when responding with `405 Method Not Allowed` ([#733]) - **fixed:** Set `Allow` header when responding with `405 Method Not Allowed` ([#733])
@ -104,6 +107,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#827]: https://github.com/tokio-rs/axum/pull/827 [#827]: https://github.com/tokio-rs/axum/pull/827
[#841]: https://github.com/tokio-rs/axum/pull/841 [#841]: https://github.com/tokio-rs/axum/pull/841
[#842]: https://github.com/tokio-rs/axum/pull/842 [#842]: https://github.com/tokio-rs/axum/pull/842
[#879]: https://github.com/tokio-rs/axum/pull/879
# 0.4.4 (13. January, 2022) # 0.4.4 (13. January, 2022)

View File

@ -1,14 +1,52 @@
//! Handler future types. //! Handler future types.
use crate::response::Response; use crate::response::Response;
use futures_util::future::{BoxFuture, Map}; use futures_util::future::Map;
use std::convert::Infallible; use http::Request;
use pin_project_lite::pin_project;
use std::{convert::Infallible, future::Future, pin::Pin, task::Context};
use tower::util::Oneshot;
use tower_service::Service;
opaque_future! { opaque_future! {
/// The response future for [`IntoService`](super::IntoService). /// The response future for [`IntoService`](super::IntoService).
pub type IntoServiceFuture = pub type IntoServiceFuture<F> =
Map< Map<
BoxFuture<'static, Response>, F,
fn(Response) -> Result<Response, Infallible>, fn(Response) -> Result<Response, Infallible>,
>; >;
} }
pin_project! {
/// The response future for [`Layered`](super::Layered).
pub struct LayeredFuture<S, ReqBody>
where
S: Service<Request<ReqBody>>,
{
#[pin]
inner: Map<Oneshot<S, Request<ReqBody>>, fn(Result<S::Response, S::Error>) -> Response>,
}
}
impl<S, ReqBody> LayeredFuture<S, ReqBody>
where
S: Service<Request<ReqBody>>,
{
pub(super) fn new(
inner: Map<Oneshot<S, Request<ReqBody>>, fn(Result<S::Response, S::Error>) -> Response>,
) -> Self {
Self { inner }
}
}
impl<S, ReqBody> Future for LayeredFuture<S, ReqBody>
where
S: Service<Request<ReqBody>>,
{
type Output = Response;
#[inline]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> std::task::Poll<Self::Output> {
self.project().inner.poll(cx)
}
}

View File

@ -60,7 +60,7 @@ where
{ {
type Response = Response; type Response = Response;
type Error = Infallible; type Error = Infallible;
type Future = super::future::IntoServiceFuture; type Future = super::future::IntoServiceFuture<H::Future>;
#[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>> {
@ -74,7 +74,8 @@ where
use futures_util::future::FutureExt; use futures_util::future::FutureExt;
let handler = self.handler.clone(); let handler = self.handler.clone();
let future = Handler::call(handler, req).map(Ok::<_, Infallible> as _); let future = Handler::call(handler, req);
let future = future.map(Ok as _);
super::future::IntoServiceFuture::new(future) super::future::IntoServiceFuture::new(future)
} }

View File

@ -80,9 +80,8 @@ use crate::{
routing::IntoMakeService, routing::IntoMakeService,
BoxError, BoxError,
}; };
use async_trait::async_trait;
use http::Request; use http::Request;
use std::{fmt, future::Future, marker::PhantomData}; use std::{fmt, future::Future, marker::PhantomData, pin::Pin};
use tower::ServiceExt; use tower::ServiceExt;
use tower_layer::Layer; use tower_layer::Layer;
use tower_service::Service; use tower_service::Service;
@ -98,10 +97,12 @@ pub use self::into_service::IntoService;
/// implemented to closures of the right types. /// implemented to closures of the right types.
/// ///
/// See the [module docs](crate::handler) for more details. /// See the [module docs](crate::handler) for more details.
#[async_trait]
pub trait Handler<T, B = Body>: Clone + Send + Sized + 'static { pub trait Handler<T, B = Body>: Clone + Send + Sized + 'static {
/// The type of future calling this handler returns.
type Future: Future<Output = Response> + Send + 'static;
/// Call the handler with the given request. /// Call the handler with the given request.
async fn call(self, req: Request<B>) -> Response; fn call(self, req: Request<B>) -> Self::Future;
/// Apply a [`tower::Layer`] to the handler. /// Apply a [`tower::Layer`] to the handler.
/// ///
@ -247,7 +248,6 @@ pub trait Handler<T, B = Body>: Clone + Send + Sized + 'static {
} }
} }
#[async_trait]
impl<F, Fut, Res, B> Handler<(), B> for F impl<F, Fut, Res, B> Handler<(), B> for F
where where
F: FnOnce() -> Fut + Clone + Send + 'static, F: FnOnce() -> Fut + Clone + Send + 'static,
@ -255,14 +255,15 @@ where
Res: IntoResponse, Res: IntoResponse,
B: Send + 'static, B: Send + 'static,
{ {
async fn call(self, _req: Request<B>) -> Response { type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
self().await.into_response()
fn call(self, _req: Request<B>) -> Self::Future {
Box::pin(async move { self().await.into_response() })
} }
} }
macro_rules! impl_handler { macro_rules! impl_handler {
( $($ty:ident),* $(,)? ) => { ( $($ty:ident),* $(,)? ) => {
#[async_trait]
#[allow(non_snake_case)] #[allow(non_snake_case)]
impl<F, Fut, B, Res, $($ty,)*> Handler<($($ty,)*), B> for F impl<F, Fut, B, Res, $($ty,)*> Handler<($($ty,)*), B> for F
where where
@ -272,19 +273,23 @@ macro_rules! impl_handler {
Res: IntoResponse, Res: IntoResponse,
$( $ty: FromRequest<B> + Send,)* $( $ty: FromRequest<B> + Send,)*
{ {
async fn call(self, req: Request<B>) -> Response { type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
let mut req = RequestParts::new(req);
$( fn call(self, req: Request<B>) -> Self::Future {
let $ty = match $ty::from_request(&mut req).await { Box::pin(async move {
Ok(value) => value, let mut req = RequestParts::new(req);
Err(rejection) => return rejection.into_response(),
};
)*
let res = self($($ty,)*).await; $(
let $ty = match $ty::from_request(&mut req).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
)*
res.into_response() let res = self($($ty,)*).await;
res.into_response()
})
} }
} }
}; };
@ -318,7 +323,6 @@ where
} }
} }
#[async_trait]
impl<S, T, ReqBody, ResBody> Handler<T, ReqBody> for Layered<S, T> impl<S, T, ReqBody, ResBody> Handler<T, ReqBody> for Layered<S, T>
where where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + 'static, S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + 'static,
@ -329,11 +333,18 @@ where
ResBody: HttpBody<Data = Bytes> + Send + 'static, ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>, ResBody::Error: Into<BoxError>,
{ {
async fn call(self, req: Request<ReqBody>) -> Response { type Future = future::LayeredFuture<S, ReqBody>;
match self.svc.oneshot(req).await {
Ok(res) => res.map(boxed), fn call(self, req: Request<ReqBody>) -> Self::Future {
Err(res) => res.into_response(), use futures_util::future::{FutureExt, Map};
}
let future: Map<_, fn(Result<S::Response, S::Error>) -> _> =
self.svc.oneshot(req).map(|result| match result {
Ok(res) => res.map(boxed),
Err(res) => res.into_response(),
});
future::LayeredFuture::new(future)
} }
} }