Skip to content

Commit

Permalink
Add bytes convenience methods (#352)
Browse files Browse the repository at this point in the history
Add a `bytes` method to `ReadResponseExt` and `AsyncReadResponseExt`. These convenience methods are similar to the `text` methods without text decoding involved. These can make some uses more concise, and also take care of reserving space in the buffer based on content length for optimal performance.
  • Loading branch information
sagebind committed Oct 14, 2021
1 parent 81a1913 commit 77fcf7f
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 0 deletions.
66 changes: 66 additions & 0 deletions src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,18 @@ pub trait ReadResponseExt<R: Read> {
File::create(path).and_then(|f| self.copy_to(f))
}

/// Read the entire response body into memory.
///
/// # Examples
///
/// ```no_run
/// use isahc::prelude::*;
///
/// let image_bytes = isahc::get("https://httpbin.org/image/jpeg")?.bytes()?;
/// # Ok::<(), isahc::Error>(())
/// ```
fn bytes(&mut self) -> io::Result<Vec<u8>>;

/// Read the response body as a string.
///
/// The encoding used to decode the response body into a string depends on
Expand Down Expand Up @@ -272,6 +284,18 @@ impl<R: Read> ReadResponseExt<R> for Response<R> {
io::copy(self.body_mut(), &mut writer)
}

fn bytes(&mut self) -> io::Result<Vec<u8>> {
let mut buf = Vec::new();

if let Some(length) = get_content_length(self) {
buf.reserve(length as usize);
}

self.copy_to(&mut buf)?;

Ok(buf)
}

#[cfg(feature = "text-decoding")]
fn text(&mut self) -> io::Result<String> {
crate::text::Decoder::for_response(self).decode_reader(self.body_mut())
Expand Down Expand Up @@ -355,6 +379,22 @@ pub trait AsyncReadResponseExt<R: AsyncRead + Unpin> {
where
W: AsyncWrite + Unpin + 'a;

/// Read the entire response body into memory.
///
/// # Examples
///
/// ```no_run
/// use isahc::prelude::*;
///
/// # async fn run() -> Result<(), isahc::Error> {
/// let image_bytes = isahc::get_async("https://httpbin.org/image/jpeg")
/// .await?
/// .bytes()
/// .await?;
/// # Ok(()) }
/// ```
fn bytes(&mut self) -> BytesFuture<'_, &mut R>;

/// Read the response body as a string asynchronously.
///
/// This method consumes the entire response body stream and can only be
Expand Down Expand Up @@ -428,6 +468,20 @@ impl<R: AsyncRead + Unpin> AsyncReadResponseExt<R> for Response<R> {
CopyFuture::new(async move { copy_async(self.body_mut(), writer).await })
}

fn bytes(&mut self) -> BytesFuture<'_, &mut R> {
BytesFuture::new(async move {
let mut buf = Vec::new();

if let Some(length) = get_content_length(self) {
buf.reserve(length as usize);
}

copy_async(self.body_mut(), &mut buf).await?;

Ok(buf)
})
}

#[cfg(feature = "text-decoding")]
fn text(&mut self) -> crate::text::TextFuture<'_, &mut R> {
crate::text::Decoder::for_response(self).decode_reader_async(self.body_mut())
Expand Down Expand Up @@ -464,6 +518,15 @@ impl<R: AsyncRead + Unpin> AsyncReadResponseExt<R> for Response<R> {
}
}

fn get_content_length<T>(response: &Response<T>) -> Option<u64> {
response.headers()
.get(http::header::CONTENT_LENGTH)?
.to_str()
.ok()?
.parse()
.ok()
}

decl_future! {
/// A future which reads any remaining bytes from the response body stream
/// and discard them.
Expand All @@ -472,6 +535,9 @@ decl_future! {
/// A future which copies all the response body bytes into a sink.
pub type CopyFuture<R, W> = impl Future<Output = io::Result<u64>> + SendIf<R, W>;

/// A future which reads the entire response body into memory.
pub type BytesFuture<R> = impl Future<Output = io::Result<Vec<u8>>> + SendIf<R>;

/// A future which deserializes the response body as JSON.
#[cfg(feature = "json")]
pub type JsonFuture<R, T> = impl Future<Output = Result<T, serde_json::Error>> + SendIf<R, T>;
Expand Down
26 changes: 26 additions & 0 deletions tests/response_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,32 @@ fn simple_response_body() {
assert_eq!(response_text, "hello world");
}

#[test]
fn response_body_bytes() {
let m = mock! {
body: "hello world",
};

let mut response = isahc::get(m.url()).unwrap();
let bytes = response.bytes().unwrap();

assert_eq!(bytes, "hello world".as_bytes());
}

#[test]
fn response_body_bytes_async() {
let m = mock! {
body: "hello world",
};

block_on(async move {
let mut response = isahc::get_async(m.url()).await.unwrap();
let bytes = response.bytes().await.unwrap();

assert_eq!(bytes, "hello world".as_bytes());
});
}

#[test]
fn zero_length_response_body() {
let m = mock! {
Expand Down

0 comments on commit 77fcf7f

Please sign in to comment.