mirror of
https://github.com/tokio-rs/axum.git
synced 2026-09-06 00:00:17 +02:00
Reduce body boxing (#9)
Previously, when routing between one or two requests the two body types would be merged by boxing them. This isn't ideal since it introduces a layer indirection for each route. We can't require the services to be routed between as not all services use the same body type. This changes that so it instead uses an `Either` enum that implements `http_body::Body` if each variant does. Will reduce the overall allocations and hopefully the compiler can optimize things if both variants are the same.
This commit is contained in:
+102
-7
@@ -1,7 +1,8 @@
|
||||
//! HTTP body utilities.
|
||||
|
||||
use bytes::Bytes;
|
||||
use http_body::{Empty, Full};
|
||||
use http_body::{Empty, Full, SizeHint};
|
||||
use pin_project::pin_project;
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
fmt,
|
||||
@@ -35,12 +36,6 @@ impl BoxBody {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BoxBody {
|
||||
fn default() -> Self {
|
||||
BoxBody::new(Empty::<Bytes>::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxBody {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("BoxBody").finish()
|
||||
@@ -101,3 +96,103 @@ 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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user