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])
- **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
- **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
request body contains syntactically invalid JSON
- **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
[#841]: https://github.com/tokio-rs/axum/pull/841
[#842]: https://github.com/tokio-rs/axum/pull/842
[#879]: https://github.com/tokio-rs/axum/pull/879
# 0.4.4 (13. January, 2022)

View file

@ -1,14 +1,52 @@
//! Handler future types.
use crate::response::Response;
use futures_util::future::{BoxFuture, Map};
use std::convert::Infallible;
use futures_util::future::Map;
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! {
/// The response future for [`IntoService`](super::IntoService).
pub type IntoServiceFuture =
pub type IntoServiceFuture<F> =
Map<
BoxFuture<'static, Response>,
F,
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 Error = Infallible;
type Future = super::future::IntoServiceFuture;
type Future = super::future::IntoServiceFuture<H::Future>;
#[inline]
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@ -74,7 +74,8 @@ where
use futures_util::future::FutureExt;
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)
}

View file

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