diff --git a/src/body.rs b/src/body.rs index ca8acb69..33ac1960 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,8 +1,7 @@ //! HTTP body utilities. use bytes::Bytes; -use http_body::{Empty, Full, SizeHint}; -use pin_project::pin_project; +use http_body::{Empty, Full}; use std::{ error::Error as StdError, fmt, @@ -34,6 +33,16 @@ impl BoxBody { inner: Box::pin(body.map_err(|error| BoxStdError(error.into()))), } } + + pub(crate) fn empty() -> Self { + Self::new(Empty::new()) + } +} + +impl Default for BoxBody { + fn default() -> Self { + BoxBody::empty() + } } impl fmt::Debug for BoxBody { @@ -96,103 +105,3 @@ impl fmt::Display for BoxStdError { self.0.fmt(f) } } - -/// Type that combines two body types into one. -#[pin_project] -#[derive(Debug)] -pub struct Or(#[pin] Either); - -impl Or { - #[inline] - pub(crate) fn a(a: A) -> Self { - Or(Either::A(a)) - } - - #[inline] - pub(crate) fn b(b: B) -> Self { - Or(Either::B(b)) - } -} - -impl Default for Or { - fn default() -> Self { - Self(Either::Empty(Empty::new())) - } -} - -#[pin_project(project = EitherProj)] -#[derive(Debug)] -enum Either { - Empty(Empty), // required for `Default` - A(#[pin] A), - B(#[pin] B), -} - -impl http_body::Body for Or -where - A: http_body::Body, - A::Error: Into, - B: http_body::Body, - B::Error: Into, -{ - type Data = Bytes; - type Error = BoxStdError; - - #[inline] - fn poll_data( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - match self.project().0.project() { - EitherProj::Empty(inner) => Pin::new(inner).poll_data(cx).map(map_option_error), - EitherProj::A(inner) => inner.poll_data(cx).map(map_option_error), - EitherProj::B(inner) => inner.poll_data(cx).map(map_option_error), - } - } - - #[inline] - fn poll_trailers( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll, Self::Error>> { - match self.project().0.project() { - EitherProj::Empty(inner) => Pin::new(inner) - .poll_trailers(cx) - .map_err(Into::into) - .map_err(BoxStdError), - EitherProj::A(inner) => inner - .poll_trailers(cx) - .map_err(Into::into) - .map_err(BoxStdError), - EitherProj::B(inner) => inner - .poll_trailers(cx) - .map_err(Into::into) - .map_err(BoxStdError), - } - } - - #[inline] - fn size_hint(&self) -> SizeHint { - match &self.0 { - Either::Empty(inner) => inner.size_hint(), - Either::A(inner) => inner.size_hint(), - Either::B(inner) => inner.size_hint(), - } - } - - #[inline] - fn is_end_stream(&self) -> bool { - match &self.0 { - Either::Empty(inner) => inner.is_end_stream(), - Either::A(inner) => inner.is_end_stream(), - Either::B(inner) => inner.is_end_stream(), - } - } -} - -fn map_option_error(opt: Option>) -> Option> -where - E: Into, -{ - opt.map(|result| result.map_err(Into::::into).map_err(BoxStdError)) -} diff --git a/src/handler/mod.rs b/src/handler/mod.rs index c48a2180..1b1f0161 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -39,7 +39,7 @@ //! the [`extract`](crate::extract) module. use crate::{ - body::{self, Body, BoxBody}, + body::{Body, BoxBody}, extract::FromRequest, response::IntoResponse, routing::{EmptyRouter, MethodFilter, RouteFuture}, @@ -643,17 +643,12 @@ impl OnMethod { } } -impl Service> for OnMethod +impl Service> for OnMethod where - S: Service, Response = Response, Error = Infallible> + Clone, - F: Service, Response = Response, Error = Infallible> + Clone, - - SB: http_body::Body, - SB::Error: Into, - FB: http_body::Body, - FB::Error: Into, + S: Service, Response = Response, Error = Infallible> + Clone, + F: Service, Response = Response, Error = Infallible> + Clone, { - type Response = Response>; + type Response = Response; type Error = Infallible; type Future = RouteFuture; diff --git a/src/lib.rs b/src/lib.rs index 8eb8e471..0b1f3c9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -479,10 +479,10 @@ //! let app = route( //! // Any request to `/` goes to a service //! "/", -//! service_fn(|_: Request| async { +//! service::any(service_fn(|_: Request| async { //! let res = Response::new(Body::from("Hi from `GET /`")); //! Ok::<_, Infallible>(res) -//! }) +//! })) //! ).route( //! // GET `/static/Cargo.toml` goes to a service from tower-http //! "/static/Cargo.toml", diff --git a/src/routing.rs b/src/routing.rs index e189f839..897ad7cf 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -1,9 +1,6 @@ //! Routing between [`Service`]s. -use crate::{ - body::{self, BoxBody}, - response::IntoResponse, -}; +use crate::{body::BoxBody, response::IntoResponse}; use async_trait::async_trait; use bytes::Bytes; use futures_util::{future, ready}; @@ -294,17 +291,12 @@ impl RoutingDsl for Route {} impl crate::sealed::Sealed for Route {} -impl Service> for Route +impl Service> for Route where - S: Service, Response = Response, Error = Infallible> + Clone, - F: Service, Response = Response, Error = Infallible> + Clone, - - SB: http_body::Body, - SB::Error: Into, - FB: http_body::Body, - FB::Error: Into, + S: Service, Response = Response, Error = Infallible> + Clone, + F: Service, Response = Response, Error = Infallible> + Clone, { - type Response = Response>; + type Response = Response; type Error = Infallible; type Future = RouteFuture; @@ -357,26 +349,17 @@ where B(#[pin] Oneshot>), } -impl Future for RouteFuture +impl Future for RouteFuture where - S: Service, Response = Response, Error = Infallible>, - F: Service, Response = Response, Error = Infallible>, - - SB: http_body::Body, - SB::Error: Into, - FB: http_body::Body, - FB::Error: Into, + S: Service, Response = Response, Error = Infallible>, + F: Service, Response = Response, Error = Infallible>, { - type Output = Result>, Infallible>; + type Output = Result, Infallible>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project().0.project() { - RouteFutureInnerProj::A(inner) => inner - .poll(cx) - .map(|result| result.map(|res| res.map(body::Or::a))), - RouteFutureInnerProj::B(inner) => inner - .poll(cx) - .map(|result| result.map(|res| res.map(body::Or::b))), + RouteFutureInnerProj::A(inner) => inner.poll(cx), + RouteFutureInnerProj::B(inner) => inner.poll(cx), } } } @@ -406,7 +389,7 @@ impl RoutingDsl for EmptyRouter {} impl crate::sealed::Sealed for EmptyRouter {} impl Service> for EmptyRouter { - type Response = Response; + type Response = Response; type Error = Infallible; type Future = EmptyRouterFuture; @@ -415,7 +398,7 @@ impl Service> for EmptyRouter { } fn call(&mut self, _req: Request) -> Self::Future { - let mut res = Response::new(Body::empty()); + let mut res = Response::new(BoxBody::empty()); *res.status_mut() = StatusCode::NOT_FOUND; EmptyRouterFuture(future::ok(res)) } @@ -424,7 +407,7 @@ impl Service> for EmptyRouter { opaque_future! { /// Response future for [`EmptyRouter`]. pub type EmptyRouterFuture = - future::Ready, Infallible>>; + future::Ready, Infallible>>; } #[derive(Debug, Clone)] @@ -786,17 +769,12 @@ impl RoutingDsl for Nested {} impl crate::sealed::Sealed for Nested {} -impl Service> for Nested +impl Service> for Nested where - S: Service, Response = Response, Error = Infallible> + Clone, - F: Service, Response = Response, Error = Infallible> + Clone, - - SB: http_body::Body, - SB::Error: Into, - FB: http_body::Body, - FB::Error: Into, + S: Service, Response = Response, Error = Infallible> + Clone, + F: Service, Response = Response, Error = Infallible> + Clone, { - type Response = Response>; + type Response = Response; type Error = Infallible; type Future = RouteFuture; diff --git a/src/service/mod.rs b/src/service/mod.rs index 33f3a3bf..04f0b05e 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -84,25 +84,38 @@ //! [load shed]: tower::load_shed use crate::{ - body::{self, Body, BoxBody}, + body::{Body, BoxBody}, response::IntoResponse, routing::{EmptyRouter, MethodFilter, RouteFuture}, }; use bytes::Bytes; +use futures_util::ready; use http::{Request, Response}; +use pin_project::pin_project; use std::{ convert::Infallible, fmt, + future::Future, task::{Context, Poll}, }; use tower::{util::Oneshot, BoxError, Service, ServiceExt as _}; pub mod future; +/// Route requests to the given service regardless of the HTTP method. +/// +/// See [`get`] for an example. +pub fn any(svc: S) -> OnMethod, EmptyRouter> +where + S: Service, Error = Infallible> + Clone, +{ + on(MethodFilter::Any, svc) +} + /// Route `CONNECT` requests to the given service. /// /// See [`get`] for an example. -pub fn connect(svc: S) -> OnMethod +pub fn connect(svc: S) -> OnMethod, EmptyRouter> where S: Service, Error = Infallible> + Clone, { @@ -112,7 +125,7 @@ where /// Route `DELETE` requests to the given service. /// /// See [`get`] for an example. -pub fn delete(svc: S) -> OnMethod +pub fn delete(svc: S) -> OnMethod, EmptyRouter> where S: Service, Error = Infallible> + Clone, { @@ -139,7 +152,7 @@ where /// /// You can only add services who cannot fail (their error type must be /// [`Infallible`]). To gracefully handle errors see [`ServiceExt::handle_error`]. -pub fn get(svc: S) -> OnMethod +pub fn get(svc: S) -> OnMethod, EmptyRouter> where S: Service, Error = Infallible> + Clone, { @@ -149,7 +162,7 @@ where /// Route `HEAD` requests to the given service. /// /// See [`get`] for an example. -pub fn head(svc: S) -> OnMethod +pub fn head(svc: S) -> OnMethod, EmptyRouter> where S: Service, Error = Infallible> + Clone, { @@ -159,7 +172,7 @@ where /// Route `OPTIONS` requests to the given service. /// /// See [`get`] for an example. -pub fn options(svc: S) -> OnMethod +pub fn options(svc: S) -> OnMethod, EmptyRouter> where S: Service, Error = Infallible> + Clone, { @@ -169,7 +182,7 @@ where /// Route `PATCH` requests to the given service. /// /// See [`get`] for an example. -pub fn patch(svc: S) -> OnMethod +pub fn patch(svc: S) -> OnMethod, EmptyRouter> where S: Service, Error = Infallible> + Clone, { @@ -179,7 +192,7 @@ where /// Route `POST` requests to the given service. /// /// See [`get`] for an example. -pub fn post(svc: S) -> OnMethod +pub fn post(svc: S) -> OnMethod, EmptyRouter> where S: Service, Error = Infallible> + Clone, { @@ -189,7 +202,7 @@ where /// Route `PUT` requests to the given service. /// /// See [`get`] for an example. -pub fn put(svc: S) -> OnMethod +pub fn put(svc: S) -> OnMethod, EmptyRouter> where S: Service, Error = Infallible> + Clone, { @@ -199,7 +212,7 @@ where /// Route `TRACE` requests to the given service. /// /// See [`get`] for an example. -pub fn trace(svc: S) -> OnMethod +pub fn trace(svc: S) -> OnMethod, EmptyRouter> where S: Service, Error = Infallible> + Clone, { @@ -223,13 +236,13 @@ where /// // Requests to `POST /` will go to `service`. /// let app = route("/", service::on(MethodFilter::Post, service)); /// ``` -pub fn on(method: MethodFilter, svc: S) -> OnMethod +pub fn on(method: MethodFilter, svc: S) -> OnMethod, EmptyRouter> where S: Service, Error = Infallible> + Clone, { OnMethod { method, - svc, + svc: BoxResponseBody(svc), fallback: EmptyRouter, } } @@ -248,7 +261,7 @@ impl OnMethod { /// its HTTP method. /// /// See [`OnMethod::get`] for an example. - pub fn any(self, svc: T) -> OnMethod + pub fn any(self, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { @@ -258,7 +271,7 @@ impl OnMethod { /// Chain an additional service that will only accept `CONNECT` requests. /// /// See [`OnMethod::get`] for an example. - pub fn connect(self, svc: T) -> OnMethod + pub fn connect(self, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { @@ -268,7 +281,7 @@ impl OnMethod { /// Chain an additional service that will only accept `DELETE` requests. /// /// See [`OnMethod::get`] for an example. - pub fn delete(self, svc: T) -> OnMethod + pub fn delete(self, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { @@ -301,7 +314,7 @@ impl OnMethod { /// You can only add services who cannot fail (their error type must be /// [`Infallible`]). To gracefully handle errors see /// [`ServiceExt::handle_error`]. - pub fn get(self, svc: T) -> OnMethod + pub fn get(self, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { @@ -311,7 +324,7 @@ impl OnMethod { /// Chain an additional service that will only accept `HEAD` requests. /// /// See [`OnMethod::get`] for an example. - pub fn head(self, svc: T) -> OnMethod + pub fn head(self, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { @@ -321,7 +334,7 @@ impl OnMethod { /// Chain an additional service that will only accept `OPTIONS` requests. /// /// See [`OnMethod::get`] for an example. - pub fn options(self, svc: T) -> OnMethod + pub fn options(self, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { @@ -331,7 +344,7 @@ impl OnMethod { /// Chain an additional service that will only accept `PATCH` requests. /// /// See [`OnMethod::get`] for an example. - pub fn patch(self, svc: T) -> OnMethod + pub fn patch(self, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { @@ -341,7 +354,7 @@ impl OnMethod { /// Chain an additional service that will only accept `POST` requests. /// /// See [`OnMethod::get`] for an example. - pub fn post(self, svc: T) -> OnMethod + pub fn post(self, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { @@ -351,7 +364,7 @@ impl OnMethod { /// Chain an additional service that will only accept `PUT` requests. /// /// See [`OnMethod::get`] for an example. - pub fn put(self, svc: T) -> OnMethod + pub fn put(self, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { @@ -361,7 +374,7 @@ impl OnMethod { /// Chain an additional service that will only accept `TRACE` requests. /// /// See [`OnMethod::get`] for an example. - pub fn trace(self, svc: T) -> OnMethod + pub fn trace(self, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { @@ -390,13 +403,13 @@ impl OnMethod { /// // Requests to `DELETE /` will go to `service` /// let app = route("/", service::on(MethodFilter::Delete, service)); /// ``` - pub fn on(self, method: MethodFilter, svc: T) -> OnMethod + pub fn on(self, method: MethodFilter, svc: T) -> OnMethod, Self> where T: Service, Error = Infallible> + Clone, { OnMethod { method, - svc, + svc: BoxResponseBody(svc), fallback: self, } } @@ -404,17 +417,12 @@ impl OnMethod { // this is identical to `routing::OnMethod`'s implementation. Would be nice to find a way to clean // that up, but not sure its possible. -impl Service> for OnMethod +impl Service> for OnMethod where - S: Service, Response = Response, Error = Infallible> + Clone, - F: Service, Response = Response, Error = Infallible> + Clone, - - SB: http_body::Body, - SB::Error: Into, - FB: http_body::Body, - FB::Error: Into, + S: Service, Response = Response, Error = Infallible> + Clone, + F: Service, Response = Response, Error = Infallible> + Clone, { - type Response = Response>; + type Response = Response; type Error = Infallible; type Future = RouteFuture; @@ -541,3 +549,47 @@ pub trait ServiceExt: Service, Response = Response> { } impl ServiceExt for S where S: Service, Response = Response> {} + +/// A [`Service`] that boxes response bodies. +#[derive(Debug, Clone)] +pub struct BoxResponseBody(S); + +impl Service> for BoxResponseBody +where + S: Service, Response = Response, Error = Infallible> + Clone, + B: http_body::Body + Send + Sync + 'static, + B::Error: Into + Send + Sync + 'static, +{ + type Response = Response; + type Error = Infallible; + type Future = BoxResponseBodyFuture>>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Request) -> Self::Future { + let fut = self.0.clone().oneshot(req); + BoxResponseBodyFuture(fut) + } +} + +/// Response future for [`BoxResponseBody`]. +#[pin_project] +#[derive(Debug)] +pub struct BoxResponseBodyFuture(#[pin] F); + +impl Future for BoxResponseBodyFuture +where + F: Future, Infallible>>, + B: http_body::Body + Send + Sync + 'static, + B::Error: Into + Send + Sync + 'static, +{ + type Output = Result, Infallible>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let res = ready!(self.project().0.poll(cx))?; + let res = res.map(BoxBody::new); + Poll::Ready(Ok(res)) + } +} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 32d685fb..bd11663b 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -15,7 +15,10 @@ //! } //! ``` -use crate::{routing::EmptyRouter, service::OnMethod}; +use crate::{ + routing::EmptyRouter, + service::{BoxResponseBody, OnMethod}, +}; use bytes::Bytes; use future::ResponseFuture; use futures_util::{sink::SinkExt, stream::StreamExt}; @@ -38,7 +41,7 @@ pub mod future; /// each connection. /// /// See the [module docs](crate::ws) for more details. -pub fn ws(callback: F) -> OnMethod, EmptyRouter> +pub fn ws(callback: F) -> OnMethod>, EmptyRouter> where F: FnOnce(WebSocket) -> Fut + Clone + Send + 'static, Fut: Future + Send + 'static,