diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3e62ac2d1..7081361e4 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -18,6 +18,7 @@ * `Request::take_conn_data()`. [#2491] * `Request::take_req_data()`. [#2487] * `impl Clone` for `RequestHead`. [#2487] +* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimisations on body types that are done in exactly one poll/chunk. [#2497] ### Changed * Rename `body::BoxBody::{from_body => new}`. [#2468] @@ -45,6 +46,7 @@ [#2487]: https://github.com/actix/actix-web/pull/2487 [#2488]: https://github.com/actix/actix-web/pull/2488 [#2491]: https://github.com/actix/actix-web/pull/2491 +[#2497]: https://github.com/actix/actix-web/pull/2497 ## 3.0.0-beta.14 - 2021-11-30 diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs index 0c3473d7a..d2469e986 100644 --- a/actix-http/src/body/boxed.rs +++ b/actix-http/src/body/boxed.rs @@ -53,14 +53,10 @@ impl MessageBody for BoxBody { } fn is_complete_body(&self) -> bool { - let a = self.0.is_complete_body(); - eprintln!("BoxBody is complete?: {}", a); - a + self.0.is_complete_body() } fn take_complete_body(&mut self) -> Bytes { - eprintln!("taking box body contents"); - debug_assert!( self.is_complete_body(), "boxed type does not allow taking complete body; caller should make sure to \ diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index 72271f73e..47d68231b 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -25,18 +25,52 @@ pub trait MessageBody { fn size(&self) -> BodySize; /// Attempt to pull out the next chunk of body bytes. + // TODO: expand documentation fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>>; + /// Returns true if entire body bytes chunk is obtainable in one call to `poll_next`. + /// + /// This method's implementation should agree with [`take_complete_body`] and should always be + /// checked before taking the body. + /// + /// The default implementation returns `false. + /// + /// [`take_complete_body`]: MessageBody::take_complete_body fn is_complete_body(&self) -> bool { false } + /// Returns the complete chunk of body bytes. + /// + /// Implementors of this method should note the following: + /// - It is acceptable to skip the omit checks of [`is_complete_body`]. The responsibility of + /// performing this check is delegated to the caller. + /// - If the result of [`is_complete_body`] is conditional, that condition should be given + /// equivalent attention here. + /// - A second call call to [`take_complete_body`] should return an empty `Bytes` or panic. + /// - A call to [`poll_next`] after calling [`take_complete_body`] should return `None` unless + /// the chunk is guaranteed to be empty. + /// + /// # Panics + /// With a correct implementation, panics if called without first checking [`is_complete_body`]. + /// + /// [`is_complete_body`]: MessageBody::is_complete_body + /// [`take_complete_body`]: MessageBody::take_complete_body + /// [`poll_next`]: MessageBody::poll_next fn take_complete_body(&mut self) -> Bytes { + assert!( + self.is_complete_body(), + "type ({}) allows taking complete body but did not provide an implementation \ + of `take_complete_body`", + std::any::type_name::() + ); + unimplemented!( - "type ({}) allows taking complete body but did not provide an implementation", + "type ({}) does not allow taking complete body; caller should make sure to \ + check `is_complete_body` first", std::any::type_name::() ); } @@ -86,10 +120,12 @@ mod foreign_impls { Poll::Ready(None) } + #[inline] fn is_complete_body(&self) -> bool { true } + #[inline] fn take_complete_body(&mut self) -> Bytes { Bytes::new() } @@ -114,10 +150,12 @@ mod foreign_impls { Pin::new(self.get_mut().as_mut()).poll_next(cx) } + #[inline] fn is_complete_body(&self) -> bool { self.as_ref().is_complete_body() } + #[inline] fn take_complete_body(&mut self) -> Bytes { self.as_mut().take_complete_body() } @@ -142,10 +180,12 @@ mod foreign_impls { self.as_mut().poll_next(cx) } + #[inline] fn is_complete_body(&self) -> bool { self.as_ref().is_complete_body() } + #[inline] fn take_complete_body(&mut self) -> Bytes { debug_assert!( self.is_complete_body(), diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 8dc4a6886..fa294ab0d 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -65,24 +65,16 @@ impl Encoder { return Self::none(); } - eprintln!("body type: {}", std::any::type_name::()); - let body = if body.is_complete_body() { - eprintln!("reducing allocation"); let body = body.take_complete_body(); EncoderBody::Full { body } } else { - eprintln!("using stream type"); EncoderBody::Stream { body } }; if can_encode { - eprintln!("I CAN ENCODE WOO"); - // Modify response body only if encoder is set if let Some(enc) = ContentEncoder::encoder(encoding) { - eprintln!("AND i have an encoder - lucky day"); - update_head(encoding, head); return Encoder { @@ -92,12 +84,8 @@ impl Encoder { eof: false, }; } - - eprintln!("but i DONT have an encoder :("); } - eprintln!("rip, no compression for you"); - Encoder { body, encoder: None,