Remove Sync requirement from response bodies (#440)

As we learned [in Tonic] bodies don't need to be `Sync` because they can
only be polled from one thread at a time.

This changes axum's bodies to no longer require `Sync` and makes
`BoxBody` an alias for `UnsyncBoxBody<Bytes, axum::Error>`.

[in Tonic]: https://github.com/hyperium/tonic/issues/117
This commit is contained in:
David Pedersen 2021-11-01 22:37:16 +01:00 committed by GitHub
parent 9465128f3e
commit 3d07d40e02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 47 additions and 30 deletions

View file

@ -34,6 +34,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
thus wasn't necessary thus wasn't necessary
- **breaking:** Vendor `AddExtensionLayer` and `AddExtension` to reduce public - **breaking:** Vendor `AddExtensionLayer` and `AddExtension` to reduce public
dependencies dependencies
- **breaking:** `body::BoxBody` is now a type alias for
`http_body::combinators::UnsyncBoxBody` and thus is no longer `Sync`. This
is because bodies are streams and requiring streams to be `Sync` is
unnecessary.
- **added:** Implement `IntoResponse` for `http_body::combinators::UnsyncBoxBody`.
- Routing: - Routing:
- Big internal refactoring of routing leading to several improvements ([#363]) - Big internal refactoring of routing leading to several improvements ([#363])
- **added:** Wildcard routes like `.route("/api/users/*rest", service)` are now supported. - **added:** Wildcard routes like `.route("/api/users/*rest", service)` are now supported.

View file

@ -29,8 +29,8 @@ bitflags = "1.0"
bytes = "1.0" bytes = "1.0"
futures-util = { version = "0.3", default-features = false, features = ["alloc"] } futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
http = "0.2" http = "0.2"
http-body = "0.4.3" http-body = "0.4.4"
hyper = { version = "0.14", features = ["server", "tcp", "stream"] } hyper = { version = "0.14.14", features = ["server", "tcp", "stream"] }
matchit = "0.4.4" matchit = "0.4.4"
percent-encoding = "2.1" percent-encoding = "2.1"
pin-project-lite = "0.2.7" pin-project-lite = "0.2.7"

View file

@ -20,15 +20,15 @@ pub use bytes::Bytes;
/// ///
/// This is used in axum as the response body type for applications. Its /// This is used in axum as the response body type for applications. Its
/// necessary to unify multiple response bodies types into one. /// necessary to unify multiple response bodies types into one.
pub type BoxBody = http_body::combinators::BoxBody<Bytes, Error>; pub type BoxBody = http_body::combinators::UnsyncBoxBody<Bytes, Error>;
/// Convert a [`http_body::Body`] into a [`BoxBody`]. /// Convert a [`http_body::Body`] into a [`BoxBody`].
pub fn box_body<B>(body: B) -> BoxBody pub fn box_body<B>(body: B) -> BoxBody
where where
B: http_body::Body<Data = Bytes> + Send + Sync + 'static, B: http_body::Body<Data = Bytes> + Send + 'static,
B::Error: Into<BoxError>, B::Error: Into<BoxError>,
{ {
body.map_err(Error::new).boxed() body.map_err(Error::new).boxed_unsync()
} }
pub(crate) fn empty() -> BoxBody { pub(crate) fn empty() -> BoxBody {

View file

@ -114,8 +114,8 @@ where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone, S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
F: FnOnce(S::Error) -> Res + Clone, F: FnOnce(S::Error) -> Res + Clone,
Res: IntoResponse, Res: IntoResponse,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static, ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError> + Send + Sync + 'static, ResBody::Error: Into<BoxError>,
{ {
type Response = Response<BoxBody>; type Response = Response<BoxBody>;
type Error = Infallible; type Error = Infallible;
@ -181,8 +181,8 @@ pub mod future {
Fut: Future<Output = Result<Response<B>, E>>, Fut: Future<Output = Result<Response<B>, E>>,
F: FnOnce(E) -> Res, F: FnOnce(E) -> Res,
Res: IntoResponse, Res: IntoResponse,
B: http_body::Body<Data = Bytes> + Send + Sync + 'static, B: http_body::Body<Data = Bytes> + Send + 'static,
B::Error: Into<BoxError> + Send + Sync + 'static, B::Error: Into<BoxError>,
{ {
type Output = Result<Response<BoxBody>, Infallible>; type Output = Result<Response<BoxBody>, Infallible>;

View file

@ -154,7 +154,7 @@ where
E: FromRequest<ReqBody> + 'static, E: FromRequest<ReqBody> + 'static,
ReqBody: Default + Send + 'static, ReqBody: Default + Send + 'static,
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone, S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static, ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>, ResBody::Error: Into<BoxError>,
{ {
type Response = Response<BoxBody>; type Response = Response<BoxBody>;
@ -213,7 +213,7 @@ where
E: FromRequest<ReqBody>, E: FromRequest<ReqBody>,
S: Service<Request<ReqBody>, Response = Response<ResBody>>, S: Service<Request<ReqBody>, Response = Response<ResBody>>,
ReqBody: Default, ReqBody: Default,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static, ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>, ResBody::Error: Into<BoxError>,
{ {
type Output = Result<Response<BoxBody>, S::Error>; type Output = Result<Response<BoxBody>, S::Error>;

View file

@ -53,7 +53,7 @@ pub struct Multipart {
impl<B> FromRequest<B> for Multipart impl<B> FromRequest<B> for Multipart
where where
B: http_body::Body<Data = Bytes> + Default + Unpin + Send + 'static, B: http_body::Body<Data = Bytes> + Default + Unpin + Send + 'static,
B::Error: Into<BoxError> + 'static, B::Error: Into<BoxError>,
{ {
type Rejection = MultipartRejection; type Rejection = MultipartRejection;

View file

@ -281,8 +281,8 @@ where
S::Future: Send, S::Future: Send,
T: 'static, T: 'static,
ReqBody: Send + 'static, ReqBody: Send + 'static,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static, ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError> + Send + Sync + 'static, ResBody::Error: Into<BoxError>,
{ {
type Sealed = sealed::Hidden; type Sealed = sealed::Hidden;

View file

@ -115,7 +115,7 @@ where
impl<H, T, K, V> IntoResponse for (Headers<H>, T) impl<H, T, K, V> IntoResponse for (Headers<H>, T)
where where
T: IntoResponse, T: IntoResponse,
T::Body: Body<Data = Bytes> + Send + Sync + 'static, T::Body: Body<Data = Bytes> + Send + 'static,
<T::Body as Body>::Error: Into<BoxError>, <T::Body as Body>::Error: Into<BoxError>,
H: IntoIterator<Item = (K, V)>, H: IntoIterator<Item = (K, V)>,
K: TryInto<HeaderName>, K: TryInto<HeaderName>,
@ -141,7 +141,7 @@ where
impl<H, T, K, V> IntoResponse for (StatusCode, Headers<H>, T) impl<H, T, K, V> IntoResponse for (StatusCode, Headers<H>, T)
where where
T: IntoResponse, T: IntoResponse,
T::Body: Body<Data = Bytes> + Send + Sync + 'static, T::Body: Body<Data = Bytes> + Send + 'static,
<T::Body as Body>::Error: Into<BoxError>, <T::Body as Body>::Error: Into<BoxError>,
H: IntoIterator<Item = (K, V)>, H: IntoIterator<Item = (K, V)>,
K: TryInto<HeaderName>, K: TryInto<HeaderName>,

View file

@ -163,7 +163,7 @@ pub trait IntoResponse {
/// [`axum::body::Empty<Bytes>`]: crate::body::Empty /// [`axum::body::Empty<Bytes>`]: crate::body::Empty
/// [`axum::body::Full<Bytes>`]: crate::body::Full /// [`axum::body::Full<Bytes>`]: crate::body::Full
/// [`axum::body::BoxBody`]: crate::body::BoxBody /// [`axum::body::BoxBody`]: crate::body::BoxBody
type Body: http_body::Body<Data = Bytes, Error = Self::BodyError> + Send + Sync + 'static; type Body: http_body::Body<Data = Bytes, Error = Self::BodyError> + Send + 'static;
/// The error type `Self::Body` might generate. /// The error type `Self::Body` might generate.
/// ///
@ -217,7 +217,7 @@ where
impl<B> IntoResponse for Response<B> impl<B> IntoResponse for Response<B>
where where
B: http_body::Body<Data = Bytes> + Send + Sync + 'static, B: http_body::Body<Data = Bytes> + Send + 'static,
B::Error: Into<BoxError>, B::Error: Into<BoxError>,
{ {
type Body = B; type Body = B;
@ -257,10 +257,22 @@ where
} }
} }
impl<E> IntoResponse for http_body::combinators::UnsyncBoxBody<Bytes, E>
where
E: Into<BoxError> + 'static,
{
type Body = Self;
type BodyError = E;
fn into_response(self) -> Response<Self> {
Response::new(self)
}
}
impl<B, F> IntoResponse for MapData<B, F> impl<B, F> IntoResponse for MapData<B, F>
where where
B: http_body::Body + Send + Sync + 'static, B: http_body::Body + Send + 'static,
F: FnMut(B::Data) -> Bytes + Send + Sync + 'static, F: FnMut(B::Data) -> Bytes + Send + 'static,
B::Error: Into<BoxError>, B::Error: Into<BoxError>,
{ {
type Body = Self; type Body = Self;
@ -273,8 +285,8 @@ where
impl<B, F, E> IntoResponse for MapErr<B, F> impl<B, F, E> IntoResponse for MapErr<B, F>
where where
B: http_body::Body<Data = Bytes> + Send + Sync + 'static, B: http_body::Body<Data = Bytes> + Send + 'static,
F: FnMut(B::Error) -> E + Send + Sync + 'static, F: FnMut(B::Error) -> E + Send + 'static,
E: Into<BoxError>, E: Into<BoxError>,
{ {
type Body = Self; type Body = Self;

View file

@ -41,7 +41,7 @@ impl<E> fmt::Debug for MethodNotAllowed<E> {
impl<B, E> Service<Request<B>> for MethodNotAllowed<E> impl<B, E> Service<Request<B>> for MethodNotAllowed<E>
where where
B: Send + Sync + 'static, B: Send + 'static,
{ {
type Response = Response<BoxBody>; type Response = Response<BoxBody>;
type Error = E; type Error = E;

View file

@ -77,7 +77,7 @@ impl<B> Clone for Router<B> {
impl<B> Default for Router<B> impl<B> Default for Router<B>
where where
B: Send + Sync + 'static, B: Send + 'static,
{ {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
@ -99,7 +99,7 @@ const NEST_TAIL_PARAM_CAPTURE: &str = "/*axum_nest";
impl<B> Router<B> impl<B> Router<B>
where where
B: Send + Sync + 'static, B: Send + 'static,
{ {
/// Create a new `Router`. /// Create a new `Router`.
/// ///
@ -240,7 +240,7 @@ where
+ Send + Send
+ 'static, + 'static,
<L::Service as Service<Request<LayeredReqBody>>>::Future: Send + 'static, <L::Service as Service<Request<LayeredReqBody>>>::Future: Send + 'static,
LayeredResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static, LayeredResBody: http_body::Body<Data = Bytes> + Send + 'static,
LayeredResBody::Error: Into<BoxError>, LayeredResBody::Error: Into<BoxError>,
{ {
let layer = ServiceBuilder::new() let layer = ServiceBuilder::new()
@ -361,7 +361,7 @@ where
impl<B> Service<Request<B>> for Router<B> impl<B> Service<Request<B>> for Router<B>
where where
B: Send + Sync + 'static, B: Send + 'static,
{ {
type Response = Response<BoxBody>; type Response = Response<BoxBody>;
type Error = Infallible; type Error = Infallible;

View file

@ -16,7 +16,7 @@ pub(crate) struct NotFound;
impl<B> Service<Request<B>> for NotFound impl<B> Service<Request<B>> for NotFound
where where
B: Send + Sync + 'static, B: Send + 'static,
{ {
type Response = Response<BoxBody>; type Response = Response<BoxBody>;
type Error = Infallible; type Error = Infallible;

View file

@ -464,7 +464,7 @@ impl<S, F, B> MethodRouter<S, F, B> {
impl<S, F, B, ResBody> Service<Request<B>> for MethodRouter<S, F, B> impl<S, F, B, ResBody> Service<Request<B>> for MethodRouter<S, F, B>
where where
S: Service<Request<B>, Response = Response<ResBody>> + Clone, S: Service<Request<B>, Response = Response<ResBody>> + Clone,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static, ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>, ResBody::Error: Into<BoxError>,
F: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error> + Clone, F: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error> + Clone,
{ {
@ -514,7 +514,7 @@ pin_project! {
impl<S, F, B, ResBody> Future for MethodRouterFuture<S, F, B> impl<S, F, B, ResBody> Future for MethodRouterFuture<S, F, B>
where where
S: Service<Request<B>, Response = Response<ResBody>> + Clone, S: Service<Request<B>, Response = Response<ResBody>> + Clone,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static, ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>, ResBody::Error: Into<BoxError>,
F: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error>, F: Service<Request<B>, Response = Response<BoxBody>, Error = S::Error>,
{ {