From 1b2119655b8f6ad35ff650f7bb63d30e9f6ef73e Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Tue, 24 Nov 2020 01:27:51 -0600 Subject: [PATCH 1/2] Remove bytes dependency & update Body constructors Remove bytes as a direct dependency, as our small usage does not really justify its inclusion. In addition, update the constructor methods for `Body` to reflect this change. These changes are minor but breaking, but encourage using the `From` traits more so as to reduce copying when possible. --- Cargo.toml | 1 - src/body.rs | 66 ++++++++++++++++++++++++++-------------------- tests/redirects.rs | 2 +- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 35b95597..c9ae0ff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,6 @@ text-decoding = ["encoding_rs", "mime"] unstable-interceptors = [] [dependencies] -bytes = "0.5" crossbeam-utils = "0.8" curl = "0.4.34" curl-sys = "0.4.37" diff --git a/src/body.rs b/src/body.rs index 18e7aa15..0647b263 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,8 +1,8 @@ //! Provides types for working with request and response bodies. -use bytes::Bytes; use futures_lite::{future::block_on, io::{AsyncRead, AsyncReadExt}}; use std::{ + borrow::Cow, fmt, io::{self, Cursor, Read}, pin::Pin, @@ -28,7 +28,7 @@ enum Inner { Empty, /// A body stored in memory. - Bytes(Cursor), + Buffer(Cursor>), /// An asynchronous reader. AsyncRead(Pin>, Option), @@ -40,35 +40,45 @@ impl Body { /// An empty body represents the *absence* of a body, which is semantically /// different than the presence of a body of zero length. pub const fn empty() -> Self { - Body(Inner::Empty) + Self(Inner::Empty) } - /// Create a new body from bytes stored in memory. + /// Create a new body from a potentially static byte buffer. /// /// The body will have a known length equal to the number of bytes given. /// + /// This will try to prevent a copy if the type passed in can be re-used, + /// otherwise the buffer will be copied first. This method guarantees to not + /// require a copy for the following types: + /// + /// - `&'static [u8]` + /// - `&'static str` + /// /// # Examples /// /// ``` /// use isahc::Body; /// /// // Create a body from a string. - /// let body = Body::from_bytes("hello world"); + /// let body = Body::from_static_bytes("hello world"); /// ``` - pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Self { - bytes.as_ref().to_vec().into() + #[inline] + pub fn from_static_bytes(bytes: B) -> Self + where + B: AsRef<[u8]> + 'static + { + match_type! { + => Self::from_static_impl(bytes), + => Self::from_static_impl(bytes.as_bytes()), + > => Self::from(bytes), + => Self::from(bytes.into_bytes()), + bytes => Self::from(bytes.as_ref().to_vec()), + } } - /// Attempt to create a new body from a shared [`Bytes`] buffer. - /// - /// This will try to prevent a copy if the type passed is the type used - /// internally, and will copy the data if it is not. - pub fn from_maybe_shared(bytes: impl AsRef<[u8]> + 'static) -> Self { - Body(Inner::Bytes(Cursor::new(match_type! { - => bytes, - => Bytes::from_static(bytes), - bytes => bytes.as_ref().to_vec().into(), - }))) + #[inline] + fn from_static_impl(bytes: &'static [u8]) -> Self { + Self(Inner::Buffer(Cursor::new(Cow::Borrowed(bytes)))) } /// Create a streaming body that reads from the given reader. @@ -116,7 +126,7 @@ impl Body { pub fn len(&self) -> Option { match &self.0 { Inner::Empty => Some(0), - Inner::Bytes(bytes) => Some(bytes.get_ref().len() as u64), + Inner::Buffer(bytes) => Some(bytes.get_ref().len() as u64), Inner::AsyncRead(_, len) => *len, } } @@ -126,7 +136,7 @@ impl Body { pub fn reset(&mut self) -> bool { match &mut self.0 { Inner::Empty => true, - Inner::Bytes(cursor) => { + Inner::Buffer(cursor) => { cursor.set_position(0); true } @@ -139,7 +149,7 @@ impl Read for Body { fn read(&mut self, buf: &mut [u8]) -> io::Result { match &mut self.0 { Inner::Empty => Ok(0), - Inner::Bytes(cursor) => cursor.read(buf), + Inner::Buffer(cursor) => cursor.read(buf), Inner::AsyncRead(reader, _) => block_on(reader.read(buf)), } } @@ -153,7 +163,7 @@ impl AsyncRead for Body { ) -> Poll> { match &mut self.0 { Inner::Empty => Poll::Ready(Ok(0)), - Inner::Bytes(cursor) => Poll::Ready(cursor.read(buf)), + Inner::Buffer(cursor) => Poll::Ready(cursor.read(buf)), Inner::AsyncRead(read, _) => AsyncRead::poll_read(read.as_mut(), cx, buf), } } @@ -173,13 +183,13 @@ impl From<()> for Body { impl From> for Body { fn from(body: Vec) -> Self { - Self::from_maybe_shared(Bytes::from(body)) + Self(Inner::Buffer(Cursor::new(Cow::Owned(body)))) } } -impl From<&'static [u8]> for Body { - fn from(body: &'static [u8]) -> Self { - Self::from_maybe_shared(Bytes::from(body)) +impl From<&'_ [u8]> for Body { + fn from(body: &[u8]) -> Self { + body.to_vec().into() } } @@ -189,8 +199,8 @@ impl From for Body { } } -impl From<&'static str> for Body { - fn from(body: &'static str) -> Self { +impl From<&'_ str> for Body { + fn from(body: &str) -> Self { body.as_bytes().into() } } @@ -229,7 +239,7 @@ mod tests { #[test] fn zero_length_body() { - let body = Body::from_bytes(vec![]); + let body = Body::from(vec![]); assert!(!body.is_empty()); assert_eq!(body.len(), Some(0)); diff --git a/tests/redirects.rs b/tests/redirects.rs index 81b81d6f..d1afa29d 100644 --- a/tests/redirects.rs +++ b/tests/redirects.rs @@ -217,7 +217,7 @@ fn redirect_non_rewindable_body_returns_error() { }; // Create a streaming body of unknown size. - let upload_stream = Body::from_reader(Body::from_bytes(b"hello world")); + let upload_stream = Body::from_reader(Body::from_static_bytes(b"hello world")); let result = Request::post(m1.url()) .redirect_policy(RedirectPolicy::Follow) From 8251f5d51847eb62f56eae8591c5b798b67d14fa Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Tue, 24 Nov 2020 11:02:37 -0600 Subject: [PATCH 2/2] Rename static method --- src/body.rs | 6 +++--- tests/redirects.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/body.rs b/src/body.rs index 0647b263..49c6de6c 100644 --- a/src/body.rs +++ b/src/body.rs @@ -59,11 +59,11 @@ impl Body { /// ``` /// use isahc::Body; /// - /// // Create a body from a string. - /// let body = Body::from_static_bytes("hello world"); + /// // Create a body from a static string. + /// let body = Body::from_bytes_static("hello world"); /// ``` #[inline] - pub fn from_static_bytes(bytes: B) -> Self + pub fn from_bytes_static(bytes: B) -> Self where B: AsRef<[u8]> + 'static { diff --git a/tests/redirects.rs b/tests/redirects.rs index d1afa29d..195fb5ee 100644 --- a/tests/redirects.rs +++ b/tests/redirects.rs @@ -217,7 +217,7 @@ fn redirect_non_rewindable_body_returns_error() { }; // Create a streaming body of unknown size. - let upload_stream = Body::from_reader(Body::from_static_bytes(b"hello world")); + let upload_stream = Body::from_reader(Body::from_bytes_static(b"hello world")); let result = Request::post(m1.url()) .redirect_policy(RedirectPolicy::Follow)