Reduce size of response body types (#11)

Wrapping everything in `crate::body::Either` wasn't actually necessary
ans probably causes large body types. You can instead box the bodies in
the leaf services.
This commit is contained in:
David Pedersen 2021-06-13 10:10:37 +02:00 committed by GitHub
parent 7a051eb2d0
commit 59944c231f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 126 additions and 189 deletions

View file

@ -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<A, B>(#[pin] Either<A, B>);
impl<A, B> Or<A, B> {
#[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<A, B> Default for Or<A, B> {
fn default() -> Self {
Self(Either::Empty(Empty::new()))
}
}
#[pin_project(project = EitherProj)]
#[derive(Debug)]
enum Either<A, B> {
Empty(Empty<Bytes>), // required for `Default`
A(#[pin] A),
B(#[pin] B),
}
impl<A, B> http_body::Body for Or<A, B>
where
A: http_body::Body<Data = Bytes>,
A::Error: Into<BoxError>,
B: http_body::Body<Data = Bytes>,
B::Error: Into<BoxError>,
{
type Data = Bytes;
type Error = BoxStdError;
#[inline]
fn poll_data(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
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<Result<Option<http::HeaderMap>, 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<T, E>(opt: Option<Result<T, E>>) -> Option<Result<T, BoxStdError>>
where
E: Into<BoxError>,
{
opt.map(|result| result.map_err(Into::<BoxError>::into).map_err(BoxStdError))
}

View file

@ -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<S, F> OnMethod<S, F> {
}
}
impl<S, F, SB, FB> Service<Request<Body>> for OnMethod<S, F>
impl<S, F> Service<Request<Body>> for OnMethod<S, F>
where
S: Service<Request<Body>, Response = Response<SB>, Error = Infallible> + Clone,
F: Service<Request<Body>, Response = Response<FB>, Error = Infallible> + Clone,
SB: http_body::Body<Data = Bytes>,
SB::Error: Into<BoxError>,
FB: http_body::Body<Data = Bytes>,
FB::Error: Into<BoxError>,
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible> + Clone,
F: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible> + Clone,
{
type Response = Response<body::Or<SB, FB>>;
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = RouteFuture<S, F>;

View file

@ -479,10 +479,10 @@
//! let app = route(
//! // Any request to `/` goes to a service
//! "/",
//! service_fn(|_: Request<Body>| async {
//! service::any(service_fn(|_: Request<Body>| 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",

View file

@ -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<S, F> RoutingDsl for Route<S, F> {}
impl<S, F> crate::sealed::Sealed for Route<S, F> {}
impl<S, F, SB, FB> Service<Request<Body>> for Route<S, F>
impl<S, F> Service<Request<Body>> for Route<S, F>
where
S: Service<Request<Body>, Response = Response<SB>, Error = Infallible> + Clone,
F: Service<Request<Body>, Response = Response<FB>, Error = Infallible> + Clone,
SB: http_body::Body<Data = Bytes>,
SB::Error: Into<BoxError>,
FB: http_body::Body<Data = Bytes>,
FB::Error: Into<BoxError>,
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible> + Clone,
F: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible> + Clone,
{
type Response = Response<body::Or<SB, FB>>;
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = RouteFuture<S, F>;
@ -357,26 +349,17 @@ where
B(#[pin] Oneshot<F, Request<Body>>),
}
impl<S, F, SB, FB> Future for RouteFuture<S, F>
impl<S, F> Future for RouteFuture<S, F>
where
S: Service<Request<Body>, Response = Response<SB>, Error = Infallible>,
F: Service<Request<Body>, Response = Response<FB>, Error = Infallible>,
SB: http_body::Body<Data = Bytes>,
SB::Error: Into<BoxError>,
FB: http_body::Body<Data = Bytes>,
FB::Error: Into<BoxError>,
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible>,
F: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible>,
{
type Output = Result<Response<body::Or<SB, FB>>, Infallible>;
type Output = Result<Response<BoxBody>, Infallible>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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<Request<Body>> for EmptyRouter {
type Response = Response<Body>;
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = EmptyRouterFuture;
@ -415,7 +398,7 @@ impl Service<Request<Body>> for EmptyRouter {
}
fn call(&mut self, _req: Request<Body>) -> 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<Request<Body>> for EmptyRouter {
opaque_future! {
/// Response future for [`EmptyRouter`].
pub type EmptyRouterFuture =
future::Ready<Result<Response<Body>, Infallible>>;
future::Ready<Result<Response<BoxBody>, Infallible>>;
}
#[derive(Debug, Clone)]
@ -786,17 +769,12 @@ impl<S, F> RoutingDsl for Nested<S, F> {}
impl<S, F> crate::sealed::Sealed for Nested<S, F> {}
impl<S, F, SB, FB> Service<Request<Body>> for Nested<S, F>
impl<S, F> Service<Request<Body>> for Nested<S, F>
where
S: Service<Request<Body>, Response = Response<SB>, Error = Infallible> + Clone,
F: Service<Request<Body>, Response = Response<FB>, Error = Infallible> + Clone,
SB: http_body::Body<Data = Bytes>,
SB::Error: Into<BoxError>,
FB: http_body::Body<Data = Bytes>,
FB::Error: Into<BoxError>,
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible> + Clone,
F: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible> + Clone,
{
type Response = Response<body::Or<SB, FB>>;
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = RouteFuture<S, F>;

View file

@ -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<S>(svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, Error = Infallible> + Clone,
{
on(MethodFilter::Any, svc)
}
/// Route `CONNECT` requests to the given service.
///
/// See [`get`] for an example.
pub fn connect<S>(svc: S) -> OnMethod<S, EmptyRouter>
pub fn connect<S>(svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -112,7 +125,7 @@ where
/// Route `DELETE` requests to the given service.
///
/// See [`get`] for an example.
pub fn delete<S>(svc: S) -> OnMethod<S, EmptyRouter>
pub fn delete<S>(svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, 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<S>(svc: S) -> OnMethod<S, EmptyRouter>
pub fn get<S>(svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -149,7 +162,7 @@ where
/// Route `HEAD` requests to the given service.
///
/// See [`get`] for an example.
pub fn head<S>(svc: S) -> OnMethod<S, EmptyRouter>
pub fn head<S>(svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -159,7 +172,7 @@ where
/// Route `OPTIONS` requests to the given service.
///
/// See [`get`] for an example.
pub fn options<S>(svc: S) -> OnMethod<S, EmptyRouter>
pub fn options<S>(svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -169,7 +182,7 @@ where
/// Route `PATCH` requests to the given service.
///
/// See [`get`] for an example.
pub fn patch<S>(svc: S) -> OnMethod<S, EmptyRouter>
pub fn patch<S>(svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -179,7 +192,7 @@ where
/// Route `POST` requests to the given service.
///
/// See [`get`] for an example.
pub fn post<S>(svc: S) -> OnMethod<S, EmptyRouter>
pub fn post<S>(svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -189,7 +202,7 @@ where
/// Route `PUT` requests to the given service.
///
/// See [`get`] for an example.
pub fn put<S>(svc: S) -> OnMethod<S, EmptyRouter>
pub fn put<S>(svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -199,7 +212,7 @@ where
/// Route `TRACE` requests to the given service.
///
/// See [`get`] for an example.
pub fn trace<S>(svc: S) -> OnMethod<S, EmptyRouter>
pub fn trace<S>(svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, 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<S>(method: MethodFilter, svc: S) -> OnMethod<S, EmptyRouter>
pub fn on<S>(method: MethodFilter, svc: S) -> OnMethod<BoxResponseBody<S>, EmptyRouter>
where
S: Service<Request<Body>, Error = Infallible> + Clone,
{
OnMethod {
method,
svc,
svc: BoxResponseBody(svc),
fallback: EmptyRouter,
}
}
@ -248,7 +261,7 @@ impl<S, F> OnMethod<S, F> {
/// its HTTP method.
///
/// See [`OnMethod::get`] for an example.
pub fn any<T>(self, svc: T) -> OnMethod<T, Self>
pub fn any<T>(self, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -258,7 +271,7 @@ impl<S, F> OnMethod<S, F> {
/// Chain an additional service that will only accept `CONNECT` requests.
///
/// See [`OnMethod::get`] for an example.
pub fn connect<T>(self, svc: T) -> OnMethod<T, Self>
pub fn connect<T>(self, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -268,7 +281,7 @@ impl<S, F> OnMethod<S, F> {
/// Chain an additional service that will only accept `DELETE` requests.
///
/// See [`OnMethod::get`] for an example.
pub fn delete<T>(self, svc: T) -> OnMethod<T, Self>
pub fn delete<T>(self, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -301,7 +314,7 @@ impl<S, F> OnMethod<S, F> {
/// 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<T>(self, svc: T) -> OnMethod<T, Self>
pub fn get<T>(self, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -311,7 +324,7 @@ impl<S, F> OnMethod<S, F> {
/// Chain an additional service that will only accept `HEAD` requests.
///
/// See [`OnMethod::get`] for an example.
pub fn head<T>(self, svc: T) -> OnMethod<T, Self>
pub fn head<T>(self, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -321,7 +334,7 @@ impl<S, F> OnMethod<S, F> {
/// Chain an additional service that will only accept `OPTIONS` requests.
///
/// See [`OnMethod::get`] for an example.
pub fn options<T>(self, svc: T) -> OnMethod<T, Self>
pub fn options<T>(self, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -331,7 +344,7 @@ impl<S, F> OnMethod<S, F> {
/// Chain an additional service that will only accept `PATCH` requests.
///
/// See [`OnMethod::get`] for an example.
pub fn patch<T>(self, svc: T) -> OnMethod<T, Self>
pub fn patch<T>(self, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -341,7 +354,7 @@ impl<S, F> OnMethod<S, F> {
/// Chain an additional service that will only accept `POST` requests.
///
/// See [`OnMethod::get`] for an example.
pub fn post<T>(self, svc: T) -> OnMethod<T, Self>
pub fn post<T>(self, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -351,7 +364,7 @@ impl<S, F> OnMethod<S, F> {
/// Chain an additional service that will only accept `PUT` requests.
///
/// See [`OnMethod::get`] for an example.
pub fn put<T>(self, svc: T) -> OnMethod<T, Self>
pub fn put<T>(self, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -361,7 +374,7 @@ impl<S, F> OnMethod<S, F> {
/// Chain an additional service that will only accept `TRACE` requests.
///
/// See [`OnMethod::get`] for an example.
pub fn trace<T>(self, svc: T) -> OnMethod<T, Self>
pub fn trace<T>(self, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
@ -390,13 +403,13 @@ impl<S, F> OnMethod<S, F> {
/// // Requests to `DELETE /` will go to `service`
/// let app = route("/", service::on(MethodFilter::Delete, service));
/// ```
pub fn on<T>(self, method: MethodFilter, svc: T) -> OnMethod<T, Self>
pub fn on<T>(self, method: MethodFilter, svc: T) -> OnMethod<BoxResponseBody<T>, Self>
where
T: Service<Request<Body>, Error = Infallible> + Clone,
{
OnMethod {
method,
svc,
svc: BoxResponseBody(svc),
fallback: self,
}
}
@ -404,17 +417,12 @@ impl<S, F> OnMethod<S, F> {
// 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<S, F, SB, FB> Service<Request<Body>> for OnMethod<S, F>
impl<S, F> Service<Request<Body>> for OnMethod<S, F>
where
S: Service<Request<Body>, Response = Response<SB>, Error = Infallible> + Clone,
F: Service<Request<Body>, Response = Response<FB>, Error = Infallible> + Clone,
SB: http_body::Body<Data = Bytes>,
SB::Error: Into<BoxError>,
FB: http_body::Body<Data = Bytes>,
FB::Error: Into<BoxError>,
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible> + Clone,
F: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible> + Clone,
{
type Response = Response<body::Or<SB, FB>>;
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = RouteFuture<S, F>;
@ -541,3 +549,47 @@ pub trait ServiceExt<B>: Service<Request<Body>, Response = Response<B>> {
}
impl<S, B> ServiceExt<B> for S where S: Service<Request<Body>, Response = Response<B>> {}
/// A [`Service`] that boxes response bodies.
#[derive(Debug, Clone)]
pub struct BoxResponseBody<S>(S);
impl<S, B> Service<Request<Body>> for BoxResponseBody<S>
where
S: Service<Request<Body>, Response = Response<B>, Error = Infallible> + Clone,
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
B::Error: Into<BoxError> + Send + Sync + 'static,
{
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = BoxResponseBodyFuture<Oneshot<S, Request<Body>>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
let fut = self.0.clone().oneshot(req);
BoxResponseBodyFuture(fut)
}
}
/// Response future for [`BoxResponseBody`].
#[pin_project]
#[derive(Debug)]
pub struct BoxResponseBodyFuture<F>(#[pin] F);
impl<F, B> Future for BoxResponseBodyFuture<F>
where
F: Future<Output = Result<Response<B>, Infallible>>,
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: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = ready!(self.project().0.poll(cx))?;
let res = res.map(BoxBody::new);
Poll::Ready(Ok(res))
}
}

View file

@ -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<F, Fut>(callback: F) -> OnMethod<WebSocketUpgrade<F>, EmptyRouter>
pub fn ws<F, Fut>(callback: F) -> OnMethod<BoxResponseBody<WebSocketUpgrade<F>>, EmptyRouter>
where
F: FnOnce(WebSocket) -> Fut + Clone + Send + 'static,
Fut: Future<Output = ()> + Send + 'static,