refine async API
- Converted `Response::text` and `Response::json` to `async fn` - Added `Response::bytes` async fn as a counterpat to `text`. - Added `Response::chunk` async fn to stream chunks of the response body. - Added `From<Response> for Body` to allow piping a response as a request body. - Removed `Decoder` from public API - Removed body accessor methods from `Response` - Removed `Chunk` type, replaced with `bytes::Bytes`. - Removed public `impl Stream for Body`.
This commit is contained in:
		| @@ -2,7 +2,7 @@ | |||||||
|  |  | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() -> Result<(), reqwest::Error> { | async fn main() -> Result<(), reqwest::Error> { | ||||||
|     let mut res = reqwest::Client::new() |     let res = reqwest::Client::new() | ||||||
|         .get("https://hyper.rs") |         .get("https://hyper.rs") | ||||||
|         .send() |         .send() | ||||||
|         .await?; |         .await?; | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| use bytes::{Buf, Bytes}; | use bytes::Bytes; | ||||||
| use futures::Stream; | use futures::Stream; | ||||||
| use hyper::body::Payload; | use hyper::body::Payload; | ||||||
| use std::fmt; | use std::fmt; | ||||||
| @@ -7,11 +7,14 @@ use std::pin::Pin; | |||||||
| use std::task::{Context, Poll}; | use std::task::{Context, Poll}; | ||||||
| use tokio::timer::Delay; | use tokio::timer::Delay; | ||||||
|  |  | ||||||
| /// An asynchronous `Stream`. | /// An asynchronous request body. | ||||||
| pub struct Body { | pub struct Body { | ||||||
|     inner: Inner, |     inner: Inner, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // The `Stream` trait isn't stable, so the impl isn't public. | ||||||
|  | pub(crate) struct ImplStream(Body); | ||||||
|  |  | ||||||
| enum Inner { | enum Inner { | ||||||
|     Reusable(Bytes), |     Reusable(Bytes), | ||||||
|     Hyper { |     Hyper { | ||||||
| @@ -21,13 +24,6 @@ enum Inner { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Body { | impl Body { | ||||||
|     pub(crate) fn content_length(&self) -> Option<u64> { |  | ||||||
|         match self.inner { |  | ||||||
|             Inner::Reusable(ref bytes) => Some(bytes.len() as u64), |  | ||||||
|             Inner::Hyper { ref body, .. } => body.size_hint().exact(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Wrap a futures `Stream` in a box inside `Body`. |     /// Wrap a futures `Stream` in a box inside `Body`. | ||||||
|     /// |     /// | ||||||
|     /// # Example |     /// # Example | ||||||
| @@ -56,14 +52,12 @@ impl Body { | |||||||
|         Body::wrap(hyper::body::Body::wrap_stream(stream)) |         Body::wrap(hyper::body::Body::wrap_stream(stream)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[inline] |  | ||||||
|     pub(crate) fn response(body: hyper::Body, timeout: Option<Delay>) -> Body { |     pub(crate) fn response(body: hyper::Body, timeout: Option<Delay>) -> Body { | ||||||
|         Body { |         Body { | ||||||
|             inner: Inner::Hyper { body, timeout }, |             inner: Inner::Hyper { body, timeout }, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[inline] |  | ||||||
|     pub(crate) fn wrap(body: hyper::Body) -> Body { |     pub(crate) fn wrap(body: hyper::Body) -> Body { | ||||||
|         Body { |         Body { | ||||||
|             inner: Inner::Hyper { |             inner: Inner::Hyper { | ||||||
| @@ -73,19 +67,16 @@ impl Body { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[inline] |  | ||||||
|     pub(crate) fn empty() -> Body { |     pub(crate) fn empty() -> Body { | ||||||
|         Body::wrap(hyper::Body::empty()) |         Body::wrap(hyper::Body::empty()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[inline] |  | ||||||
|     pub(crate) fn reusable(chunk: Bytes) -> Body { |     pub(crate) fn reusable(chunk: Bytes) -> Body { | ||||||
|         Body { |         Body { | ||||||
|             inner: Inner::Reusable(chunk), |             inner: Inner::Reusable(chunk), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[inline] |  | ||||||
|     pub(crate) fn into_hyper(self) -> (Option<Bytes>, hyper::Body) { |     pub(crate) fn into_hyper(self) -> (Option<Bytes>, hyper::Body) { | ||||||
|         match self.inner { |         match self.inner { | ||||||
|             Inner::Reusable(chunk) => (Some(chunk.clone()), chunk.into()), |             Inner::Reusable(chunk) => (Some(chunk.clone()), chunk.into()), | ||||||
| @@ -96,44 +87,15 @@ impl Body { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn inner(self: Pin<&mut Self>) -> Pin<&mut Inner> { |     pub(crate) fn into_stream(self) -> ImplStream { | ||||||
|         unsafe { Pin::map_unchecked_mut(self, |x| &mut x.inner) } |         ImplStream(self) | ||||||
|     } |     } | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Stream for Body { |     pub(crate) fn content_length(&self) -> Option<u64> { | ||||||
|     type Item = Result<Chunk, crate::Error>; |         match self.inner { | ||||||
|  |             Inner::Reusable(ref bytes) => Some(bytes.len() as u64), | ||||||
|     #[inline] |             Inner::Hyper { ref body, .. } => body.size_hint().exact(), | ||||||
|     fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { |         } | ||||||
|         let opt_try_chunk = match self.inner().get_mut() { |  | ||||||
|             Inner::Hyper { |  | ||||||
|                 ref mut body, |  | ||||||
|                 ref mut timeout, |  | ||||||
|             } => { |  | ||||||
|                 if let Some(ref mut timeout) = timeout { |  | ||||||
|                     if let Poll::Ready(()) = Pin::new(timeout).poll(cx) { |  | ||||||
|                         return Poll::Ready(Some(Err(crate::error::timedout(None)))); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 futures::ready!(Pin::new(body).poll_data(cx)).map(|opt_chunk| { |  | ||||||
|                     opt_chunk |  | ||||||
|                         .map(|c| Chunk { inner: c }) |  | ||||||
|                         .map_err(crate::error::from) |  | ||||||
|                 }) |  | ||||||
|             } |  | ||||||
|             Inner::Reusable(ref mut bytes) => { |  | ||||||
|                 if bytes.is_empty() { |  | ||||||
|                     None |  | ||||||
|                 } else { |  | ||||||
|                     let chunk = Chunk::from_chunk(bytes.clone()); |  | ||||||
|                     *bytes = Bytes::new(); |  | ||||||
|                     Some(Ok(chunk)) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         Poll::Ready(opt_try_chunk) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -172,126 +134,41 @@ impl From<&'static str> for Body { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /// A chunk of bytes for a `Body`. |  | ||||||
| /// |  | ||||||
| /// A `Chunk` can be treated like `&[u8]`. |  | ||||||
| #[derive(Default)] |  | ||||||
| pub struct Chunk { |  | ||||||
|     inner: hyper::Chunk, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Chunk { |  | ||||||
|     #[inline] |  | ||||||
|     pub(crate) fn from_chunk(chunk: Bytes) -> Chunk { |  | ||||||
|         Chunk { |  | ||||||
|             inner: hyper::Chunk::from(chunk), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Buf for Chunk { |  | ||||||
|     fn bytes(&self) -> &[u8] { |  | ||||||
|         self.inner.bytes() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn remaining(&self) -> usize { |  | ||||||
|         self.inner.remaining() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn advance(&mut self, n: usize) { |  | ||||||
|         self.inner.advance(n); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl AsRef<[u8]> for Chunk { |  | ||||||
|     #[inline] |  | ||||||
|     fn as_ref(&self) -> &[u8] { |  | ||||||
|         &*self |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl std::ops::Deref for Chunk { |  | ||||||
|     type Target = [u8]; |  | ||||||
|     #[inline] |  | ||||||
|     fn deref(&self) -> &Self::Target { |  | ||||||
|         self.inner.as_ref() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Extend<u8> for Chunk { |  | ||||||
|     fn extend<T>(&mut self, iter: T) |  | ||||||
|     where |  | ||||||
|         T: IntoIterator<Item = u8>, |  | ||||||
|     { |  | ||||||
|         self.inner.extend(iter) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl IntoIterator for Chunk { |  | ||||||
|     type Item = u8; |  | ||||||
|     //XXX: exposing type from hyper! |  | ||||||
|     type IntoIter = <hyper::Chunk as IntoIterator>::IntoIter; |  | ||||||
|     fn into_iter(self) -> Self::IntoIter { |  | ||||||
|         self.inner.into_iter() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Vec<u8>> for Chunk { |  | ||||||
|     fn from(v: Vec<u8>) -> Chunk { |  | ||||||
|         Chunk { inner: v.into() } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<&'static [u8]> for Chunk { |  | ||||||
|     fn from(slice: &'static [u8]) -> Chunk { |  | ||||||
|         Chunk { |  | ||||||
|             inner: slice.into(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<String> for Chunk { |  | ||||||
|     fn from(s: String) -> Chunk { |  | ||||||
|         Chunk { inner: s.into() } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<&'static str> for Chunk { |  | ||||||
|     fn from(slice: &'static str) -> Chunk { |  | ||||||
|         Chunk { |  | ||||||
|             inner: slice.into(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Bytes> for Chunk { |  | ||||||
|     fn from(bytes: Bytes) -> Chunk { |  | ||||||
|         Chunk { |  | ||||||
|             inner: bytes.into(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Chunk> for Bytes { |  | ||||||
|     fn from(chunk: Chunk) -> Bytes { |  | ||||||
|         chunk.inner.into() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Chunk> for hyper::Chunk { |  | ||||||
|     fn from(val: Chunk) -> hyper::Chunk { |  | ||||||
|         val.inner |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl fmt::Debug for Body { | impl fmt::Debug for Body { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         f.debug_struct("Body").finish() |         f.debug_struct("Body").finish() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl fmt::Debug for Chunk { | // ===== impl ImplStream ===== | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |  | ||||||
|         fmt::Debug::fmt(&self.inner, f) | impl Stream for ImplStream { | ||||||
|  |     type Item = Result<Bytes, crate::Error>; | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { | ||||||
|  |         let opt_try_chunk = match self.0.inner { | ||||||
|  |             Inner::Hyper { | ||||||
|  |                 ref mut body, | ||||||
|  |                 ref mut timeout, | ||||||
|  |             } => { | ||||||
|  |                 if let Some(ref mut timeout) = timeout { | ||||||
|  |                     if let Poll::Ready(()) = Pin::new(timeout).poll(cx) { | ||||||
|  |                         return Poll::Ready(Some(Err(crate::error::timedout(None)))); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 futures::ready!(Pin::new(body).poll_data(cx)) | ||||||
|  |                     .map(|opt_chunk| opt_chunk.map(Into::into).map_err(crate::error::from)) | ||||||
|  |             } | ||||||
|  |             Inner::Reusable(ref mut bytes) => { | ||||||
|  |                 if bytes.is_empty() { | ||||||
|  |                     None | ||||||
|  |                 } else { | ||||||
|  |                     Some(Ok(std::mem::replace(bytes, Bytes::new()))) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         Poll::Ready(opt_try_chunk) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,36 +17,38 @@ use std::mem; | |||||||
| use std::pin::Pin; | use std::pin::Pin; | ||||||
| use std::task::{Context, Poll}; | use std::task::{Context, Poll}; | ||||||
|  |  | ||||||
|  | use async_compression::stream::GzipDecoder; | ||||||
| use bytes::Bytes; | use bytes::Bytes; | ||||||
|  | use futures::stream::Peekable; | ||||||
| use futures::Stream; | use futures::Stream; | ||||||
| use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; | use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; | ||||||
| use hyper::HeaderMap; | use hyper::HeaderMap; | ||||||
|  |  | ||||||
| use log::warn; | use log::warn; | ||||||
|  |  | ||||||
| use super::{Body, Chunk}; | use super::Body; | ||||||
| use crate::error; | use crate::error; | ||||||
|  |  | ||||||
| /// A response decompressor over a non-blocking stream of chunks. | /// A response decompressor over a non-blocking stream of chunks. | ||||||
| /// | /// | ||||||
| /// The inner decoder may be constructed asynchronously. | /// The inner decoder may be constructed asynchronously. | ||||||
| pub struct Decoder { | pub(crate) struct Decoder { | ||||||
|     inner: Inner, |     inner: Inner, | ||||||
| } | } | ||||||
|  |  | ||||||
| enum Inner { | enum Inner { | ||||||
|     /// A `PlainText` decoder just returns the response content as is. |     /// A `PlainText` decoder just returns the response content as is. | ||||||
|     PlainText(Body), |     PlainText(super::body::ImplStream), | ||||||
|     /// A `Gzip` decoder will uncompress the gzipped response content before returning it. |     /// A `Gzip` decoder will uncompress the gzipped response content before returning it. | ||||||
|     Gzip(async_compression::stream::GzipDecoder<futures::stream::Peekable<BodyBytes>>), |     Gzip(GzipDecoder<Peekable<IoStream>>), | ||||||
|     /// A decoder that doesn't have a value yet. |     /// A decoder that doesn't have a value yet. | ||||||
|     Pending(Pending), |     Pending(Pending), | ||||||
| } | } | ||||||
|  |  | ||||||
| /// A future attempt to poll the response body for EOF so we know whether to use gzip or not. | /// A future attempt to poll the response body for EOF so we know whether to use gzip or not. | ||||||
| struct Pending(futures::stream::Peekable<BodyBytes>); | struct Pending(Peekable<IoStream>); | ||||||
|  |  | ||||||
| struct BodyBytes(Body); | struct IoStream(super::body::ImplStream); | ||||||
|  |  | ||||||
| impl fmt::Debug for Decoder { | impl fmt::Debug for Decoder { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
| @@ -59,9 +61,9 @@ impl Decoder { | |||||||
|     /// |     /// | ||||||
|     /// This decoder will produce a single 0 byte chunk. |     /// This decoder will produce a single 0 byte chunk. | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub fn empty() -> Decoder { |     pub(crate) fn empty() -> Decoder { | ||||||
|         Decoder { |         Decoder { | ||||||
|             inner: Inner::PlainText(Body::empty()), |             inner: Inner::PlainText(Body::empty().into_stream()), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -70,7 +72,7 @@ impl Decoder { | |||||||
|     /// This decoder will emit the underlying chunks as-is. |     /// This decoder will emit the underlying chunks as-is. | ||||||
|     fn plain_text(body: Body) -> Decoder { |     fn plain_text(body: Body) -> Decoder { | ||||||
|         Decoder { |         Decoder { | ||||||
|             inner: Inner::PlainText(body), |             inner: Inner::PlainText(body.into_stream()), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -81,7 +83,7 @@ impl Decoder { | |||||||
|         use futures::stream::StreamExt; |         use futures::stream::StreamExt; | ||||||
|  |  | ||||||
|         Decoder { |         Decoder { | ||||||
|             inner: Inner::Pending(Pending(BodyBytes(body).peekable())), |             inner: Inner::Pending(Pending(IoStream(body.into_stream()).peekable())), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -128,7 +130,7 @@ impl Decoder { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Stream for Decoder { | impl Stream for Decoder { | ||||||
|     type Item = Result<Chunk, error::Error>; |     type Item = Result<Bytes, error::Error>; | ||||||
|  |  | ||||||
|     fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { |     fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { | ||||||
|         // Do a read or poll for a pending decoder value. |         // Do a read or poll for a pending decoder value. | ||||||
| @@ -141,7 +143,7 @@ impl Stream for Decoder { | |||||||
|             Inner::PlainText(ref mut body) => return Pin::new(body).poll_next(cx), |             Inner::PlainText(ref mut body) => return Pin::new(body).poll_next(cx), | ||||||
|             Inner::Gzip(ref mut decoder) => { |             Inner::Gzip(ref mut decoder) => { | ||||||
|                 return match futures::ready!(Pin::new(decoder).poll_next(cx)) { |                 return match futures::ready!(Pin::new(decoder).poll_next(cx)) { | ||||||
|                     Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.into()))), |                     Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes))), | ||||||
|                     Some(Err(err)) => Poll::Ready(Some(Err(crate::error::from_io(err)))), |                     Some(Err(err)) => Poll::Ready(Some(Err(crate::error::from_io(err)))), | ||||||
|                     None => Poll::Ready(None), |                     None => Poll::Ready(None), | ||||||
|                 } |                 } | ||||||
| @@ -169,22 +171,23 @@ impl Future for Pending { | |||||||
|                     .expect("just peeked Some") |                     .expect("just peeked Some") | ||||||
|                     .unwrap_err())); |                     .unwrap_err())); | ||||||
|             } |             } | ||||||
|             None => return Poll::Ready(Ok(Inner::PlainText(Body::empty()))), |             None => return Poll::Ready(Ok(Inner::PlainText(Body::empty().into_stream()))), | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let body = mem::replace(&mut self.0, BodyBytes(Body::empty()).peekable()); |         let body = mem::replace( | ||||||
|         Poll::Ready(Ok(Inner::Gzip( |             &mut self.0, | ||||||
|             async_compression::stream::GzipDecoder::new(body), |             IoStream(Body::empty().into_stream()).peekable(), | ||||||
|         ))) |         ); | ||||||
|  |         Poll::Ready(Ok(Inner::Gzip(GzipDecoder::new(body)))) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Stream for BodyBytes { | impl Stream for IoStream { | ||||||
|     type Item = Result<Bytes, std::io::Error>; |     type Item = Result<Bytes, std::io::Error>; | ||||||
|  |  | ||||||
|     fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { |     fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { | ||||||
|         match futures::ready!(Pin::new(&mut self.0).poll_next(cx)) { |         match futures::ready!(Pin::new(&mut self.0).poll_next(cx)) { | ||||||
|             Some(Ok(chunk)) => Poll::Ready(Some(Ok(chunk.into()))), |             Some(Ok(chunk)) => Poll::Ready(Some(Ok(chunk))), | ||||||
|             Some(Err(err)) => Poll::Ready(Some(Err(err.into_io()))), |             Some(Err(err)) => Poll::Ready(Some(Err(err.into_io()))), | ||||||
|             None => Poll::Ready(None), |             None => Poll::Ready(None), | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| pub use self::body::{Body, Chunk}; | pub use self::body::Body; | ||||||
| pub use self::client::{Client, ClientBuilder}; | pub use self::client::{Client, ClientBuilder}; | ||||||
| pub use self::decoder::Decoder; | pub(crate) use self::decoder::Decoder; | ||||||
| pub use self::request::{Request, RequestBuilder}; | pub use self::request::{Request, RequestBuilder}; | ||||||
| pub use self::response::{Response, ResponseBuilderExt}; | pub use self::response::{Response, ResponseBuilderExt}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ use mime_guess::Mime; | |||||||
| use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC}; | use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC}; | ||||||
| use uuid::Uuid; | use uuid::Uuid; | ||||||
|  |  | ||||||
| use futures::{Stream, StreamExt}; | use futures::StreamExt; | ||||||
|  |  | ||||||
| use super::Body; | use super::Body; | ||||||
|  |  | ||||||
| @@ -137,7 +137,7 @@ impl Form { | |||||||
|         hyper::Body::wrap_stream( |         hyper::Body::wrap_stream( | ||||||
|             boundary |             boundary | ||||||
|                 .chain(header) |                 .chain(header) | ||||||
|                 .chain(hyper::Body::wrap_stream(part.value)) |                 .chain(hyper::Body::wrap_stream(part.value.into_stream())) | ||||||
|                 .chain(hyper::Body::from("\r\n".to_owned())), |                 .chain(hyper::Body::from("\r\n".to_owned())), | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| @@ -190,15 +190,8 @@ impl Part { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Makes a new parameter from an arbitrary stream. |     /// Makes a new parameter from an arbitrary stream. | ||||||
|     pub fn stream<T, I, E>(value: T) -> Part |     pub fn stream<T: Into<Body>>(value: T) -> Part { | ||||||
|     where |         Part::new(value.into()) | ||||||
|         T: Stream<Item = Result<I, E>> + Send + Sync + 'static, |  | ||||||
|         E: std::error::Error + Send + Sync + 'static, |  | ||||||
|         hyper::Chunk: std::convert::From<I>, |  | ||||||
|     { |  | ||||||
|         Part::new(Body::wrap(hyper::Body::wrap_stream( |  | ||||||
|             value.map(|chunk| chunk.into()), |  | ||||||
|         ))) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn new(value: Body) -> Part { |     fn new(value: Body) -> Part { | ||||||
| @@ -500,21 +493,17 @@ mod tests { | |||||||
|         let mut form = Form::new() |         let mut form = Form::new() | ||||||
|             .part( |             .part( | ||||||
|                 "reader1", |                 "reader1", | ||||||
|                 Part::stream(futures::stream::once(futures::future::ready::< |                 Part::stream(Body::wrap_stream(futures::stream::once( | ||||||
|                     Result<hyper::Chunk, hyper::Error>, |                     futures::future::ready::<Result<String, crate::Error>>(Ok("part1".to_owned())), | ||||||
|                 >(Ok( |                 ))), | ||||||
|                     hyper::Chunk::from("part1".to_owned()), |  | ||||||
|                 )))), |  | ||||||
|             ) |             ) | ||||||
|             .part("key1", Part::text("value1")) |             .part("key1", Part::text("value1")) | ||||||
|             .part("key2", Part::text("value2").mime(mime::IMAGE_BMP)) |             .part("key2", Part::text("value2").mime(mime::IMAGE_BMP)) | ||||||
|             .part( |             .part( | ||||||
|                 "reader2", |                 "reader2", | ||||||
|                 Part::stream(futures::stream::once(futures::future::ready::< |                 Part::stream(Body::wrap_stream(futures::stream::once( | ||||||
|                     Result<hyper::Chunk, hyper::Error>, |                     futures::future::ready::<Result<String, crate::Error>>(Ok("part2".to_owned())), | ||||||
|                 >(Ok( |                 ))), | ||||||
|                     hyper::Chunk::from("part2".to_owned()), |  | ||||||
|                 )))), |  | ||||||
|             ) |             ) | ||||||
|             .part("key3", Part::text("value3").file_name("filename")); |             .part("key3", Part::text("value3").file_name("filename")); | ||||||
|         form.inner.boundary = "boundary".to_string(); |         form.inner.boundary = "boundary".to_string(); | ||||||
|   | |||||||
| @@ -1,13 +1,10 @@ | |||||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||||
| use std::fmt; | use std::fmt; | ||||||
| use std::marker::PhantomData; |  | ||||||
| use std::mem; |  | ||||||
| use std::net::SocketAddr; | use std::net::SocketAddr; | ||||||
| use std::pin::Pin; |  | ||||||
| use std::task::{Context, Poll}; |  | ||||||
|  |  | ||||||
|  | use bytes::Bytes; | ||||||
| use encoding_rs::{Encoding, UTF_8}; | use encoding_rs::{Encoding, UTF_8}; | ||||||
| use futures::{Future, FutureExt, TryStreamExt}; | use futures::{StreamExt, TryStreamExt}; | ||||||
| use http; | use http; | ||||||
| use hyper::client::connect::HttpInfo; | use hyper::client::connect::HttpInfo; | ||||||
| use hyper::header::CONTENT_LENGTH; | use hyper::header::CONTENT_LENGTH; | ||||||
| @@ -21,12 +18,8 @@ use url::Url; | |||||||
|  |  | ||||||
| use super::body::Body; | use super::body::Body; | ||||||
| use super::Decoder; | use super::Decoder; | ||||||
| use crate::async_impl::Chunk; |  | ||||||
| use crate::cookie; | use crate::cookie; | ||||||
|  |  | ||||||
| /// https://github.com/rust-lang-nursery/futures-rs/issues/1812 |  | ||||||
| type ConcatDecoder = Pin<Box<dyn Future<Output = Result<Chunk, crate::Error>> + Send>>; |  | ||||||
|  |  | ||||||
| /// A Response to a submitted `Request`. | /// A Response to a submitted `Request`. | ||||||
| pub struct Response { | pub struct Response { | ||||||
|     status: StatusCode, |     status: StatusCode, | ||||||
| @@ -71,6 +64,12 @@ impl Response { | |||||||
|         self.status |         self.status | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Get the HTTP `Version` of this `Response`. | ||||||
|  |     #[inline] | ||||||
|  |     pub fn version(&self) -> Version { | ||||||
|  |         self.version | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Get the `Headers` of this `Response`. |     /// Get the `Headers` of this `Response`. | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub fn headers(&self) -> &HeaderMap { |     pub fn headers(&self) -> &HeaderMap { | ||||||
| @@ -83,6 +82,20 @@ impl Response { | |||||||
|         &mut self.headers |         &mut self.headers | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Get the content-length of this response, if known. | ||||||
|  |     /// | ||||||
|  |     /// Reasons it may not be known: | ||||||
|  |     /// | ||||||
|  |     /// - The server didn't send a `content-length` header. | ||||||
|  |     /// - The response is gzipped and automatically decoded (thus changing | ||||||
|  |     ///   the actual decoded length). | ||||||
|  |     pub fn content_length(&self) -> Option<u64> { | ||||||
|  |         self.headers() | ||||||
|  |             .get(CONTENT_LENGTH) | ||||||
|  |             .and_then(|ct_len| ct_len.to_str().ok()) | ||||||
|  |             .and_then(|ct_len| ct_len.parse().ok()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Retrieve the cookies contained in the response. |     /// Retrieve the cookies contained in the response. | ||||||
|     /// |     /// | ||||||
|     /// Note that invalid 'Set-Cookie' headers will be ignored. |     /// Note that invalid 'Set-Cookie' headers will be ignored. | ||||||
| @@ -103,57 +116,55 @@ impl Response { | |||||||
|             .map(|info| info.remote_addr()) |             .map(|info| info.remote_addr()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Get the content-length of this response, if known. |     // body methods | ||||||
|  |  | ||||||
|  |     /// Get the full response text. | ||||||
|     /// |     /// | ||||||
|     /// Reasons it may not be known: |     /// This method decodes the response body with BOM sniffing | ||||||
|  |     /// and with malformed sequences replaced with the REPLACEMENT CHARACTER. | ||||||
|  |     /// Encoding is determinated from the `charset` parameter of `Content-Type` header, | ||||||
|  |     /// and defaults to `utf-8` if not presented. | ||||||
|     /// |     /// | ||||||
|     /// - The server didn't send a `content-length` header. |     /// # Example | ||||||
|     /// - The response is gzipped and automatically decoded (thus changing |  | ||||||
|     ///   the actual decoded length). |  | ||||||
|     pub fn content_length(&self) -> Option<u64> { |  | ||||||
|         self.headers() |  | ||||||
|             .get(CONTENT_LENGTH) |  | ||||||
|             .and_then(|ct_len| ct_len.to_str().ok()) |  | ||||||
|             .and_then(|ct_len| ct_len.parse().ok()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Consumes the response, returning the body |  | ||||||
|     pub fn into_body(self) -> Decoder { |  | ||||||
|         self.body |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Get a reference to the response body. |  | ||||||
|     #[inline] |  | ||||||
|     pub fn body(&self) -> &Decoder { |  | ||||||
|         &self.body |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Get a mutable reference to the response body. |  | ||||||
|     /// |     /// | ||||||
|     /// The chunks from the body may be decoded, depending on the `gzip` |     /// ``` | ||||||
|     /// option on the `ClientBuilder`. |     /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|     #[inline] |     /// let content = reqwest::get("http://httpbin.org/range/26") | ||||||
|     pub fn body_mut(&mut self) -> &mut Decoder { |     ///     .await? | ||||||
|         &mut self.body |     ///     .text() | ||||||
|  |     ///     .await?; | ||||||
|  |     /// | ||||||
|  |     /// println!("text: {:?}", content); | ||||||
|  |     /// # Ok(()) | ||||||
|  |     /// # } | ||||||
|  |     /// ``` | ||||||
|  |     pub async fn text(self) -> crate::Result<String> { | ||||||
|  |         self.text_with_charset("utf-8").await | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Get the HTTP `Version` of this `Response`. |     /// Get the full response text given a specific encoding. | ||||||
|     #[inline] |     /// | ||||||
|     pub fn version(&self) -> Version { |     /// This method decodes the response body with BOM sniffing | ||||||
|         self.version |     /// and with malformed sequences replaced with the REPLACEMENT CHARACTER. | ||||||
|     } |     /// You can provide a default encoding for decoding the raw message, while the | ||||||
|  |     /// `charset` parameter of `Content-Type` header is still prioritized. For more information | ||||||
|     /// Get the response text |     /// about the possible encoding name, please go to | ||||||
|     pub fn text(&mut self) -> impl Future<Output = Result<String, crate::Error>> { |     /// https://docs.rs/encoding_rs/0.8.17/encoding_rs/#relationship-with-windows-code-pages | ||||||
|         self.text_with_charset("utf-8") |     /// | ||||||
|     } |     /// # Example | ||||||
|  |     /// | ||||||
|     /// Get the response text given a specific encoding |     /// ``` | ||||||
|     pub fn text_with_charset( |     /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|         &mut self, |     /// let content = reqwest::get("http://httpbin.org/range/26") | ||||||
|         default_encoding: &str, |     ///     .await? | ||||||
|     ) -> impl Future<Output = Result<String, crate::Error>> { |     ///     .text_with_charset("utf-8") | ||||||
|         let body = mem::replace(&mut self.body, Decoder::empty()); |     ///     .await?; | ||||||
|  |     /// | ||||||
|  |     /// println!("text: {:?}", content); | ||||||
|  |     /// # Ok(()) | ||||||
|  |     /// # } | ||||||
|  |     /// ``` | ||||||
|  |     pub async fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> { | ||||||
|         let content_type = self |         let content_type = self | ||||||
|             .headers |             .headers | ||||||
|             .get(crate::header::CONTENT_TYPE) |             .get(crate::header::CONTENT_TYPE) | ||||||
| @@ -164,23 +175,106 @@ impl Response { | |||||||
|             .and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str())) |             .and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str())) | ||||||
|             .unwrap_or(default_encoding); |             .unwrap_or(default_encoding); | ||||||
|         let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8); |         let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8); | ||||||
|         Text { |  | ||||||
|             concat: body.try_concat().boxed(), |         let full = self.bytes().await?; | ||||||
|             encoding, |  | ||||||
|  |         let (text, _, _) = encoding.decode(&full); | ||||||
|  |         if let Cow::Owned(s) = text { | ||||||
|  |             return Ok(s); | ||||||
|  |         } | ||||||
|  |         unsafe { | ||||||
|  |             // decoding returned Cow::Borrowed, meaning these bytes | ||||||
|  |             // are already valid utf8 | ||||||
|  |             Ok(String::from_utf8_unchecked(full.to_vec())) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Try to deserialize the response body as JSON using `serde`. |     /// Try to deserialize the response body as JSON. | ||||||
|     #[inline] |     /// | ||||||
|     pub fn json<T: DeserializeOwned>(&mut self) -> impl Future<Output = Result<T, crate::Error>> { |     /// # Examples | ||||||
|         let body = mem::replace(&mut self.body, Decoder::empty()); |     /// | ||||||
|  |     /// ``` | ||||||
|  |     /// # extern crate reqwest; | ||||||
|  |     /// # extern crate serde; | ||||||
|  |     /// # | ||||||
|  |     /// # use reqwest::Error; | ||||||
|  |     /// # use serde::Deserialize; | ||||||
|  |     /// # | ||||||
|  |     /// #[derive(Deserialize)] | ||||||
|  |     /// struct Ip { | ||||||
|  |     ///     origin: String, | ||||||
|  |     /// } | ||||||
|  |     /// | ||||||
|  |     /// # async fn run() -> Result<(), Error> { | ||||||
|  |     /// let ip = reqwest::get("http://httpbin.org/ip") | ||||||
|  |     ///     .await? | ||||||
|  |     ///     .json::<Ip>() | ||||||
|  |     ///     .await?; | ||||||
|  |     /// | ||||||
|  |     /// println!("ip: {}", ip.origin); | ||||||
|  |     /// # Ok(()) | ||||||
|  |     /// # } | ||||||
|  |     /// # | ||||||
|  |     /// # fn main() { } | ||||||
|  |     /// ``` | ||||||
|  |     /// | ||||||
|  |     /// # Errors | ||||||
|  |     /// | ||||||
|  |     /// This method fails whenever the response body is not in JSON format | ||||||
|  |     /// or it cannot be properly deserialized to target type `T`. For more | ||||||
|  |     /// details please see [`serde_json::from_reader`]. | ||||||
|  |     /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html | ||||||
|  |     pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> { | ||||||
|  |         let full = self.bytes().await?; | ||||||
|  |  | ||||||
|         Json { |         serde_json::from_slice(&full).map_err(crate::error::from) | ||||||
|             concat: body.try_concat().boxed(), |     } | ||||||
|             _marker: PhantomData, |  | ||||||
|  |     /// Get the full response body as `Bytes`. | ||||||
|  |     /// | ||||||
|  |     /// # Example | ||||||
|  |     /// | ||||||
|  |     /// ``` | ||||||
|  |     /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     /// let bytes = reqwest::get("http://httpbin.org/ip") | ||||||
|  |     ///     .await? | ||||||
|  |     ///     .bytes() | ||||||
|  |     ///     .await?; | ||||||
|  |     /// | ||||||
|  |     /// println!("bytes: {:?}", bytes); | ||||||
|  |     /// # Ok(()) | ||||||
|  |     /// # } | ||||||
|  |     /// ``` | ||||||
|  |     pub async fn bytes(self) -> crate::Result<Bytes> { | ||||||
|  |         self.body.try_concat().await | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Stream a chunk of the response body. | ||||||
|  |     /// | ||||||
|  |     /// When the response body has been exhausted, this will return `None`. | ||||||
|  |     /// | ||||||
|  |     /// # Example | ||||||
|  |     /// | ||||||
|  |     /// ``` | ||||||
|  |     /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     /// let mut res = reqwest::get("https://hyper.rs").await?; | ||||||
|  |     /// | ||||||
|  |     /// while let Some(chunk) = res.chunk().await? { | ||||||
|  |     ///     println!("Chunk: {:?}", chunk); | ||||||
|  |     /// } | ||||||
|  |     /// # Ok(()) | ||||||
|  |     /// # } | ||||||
|  |     /// ``` | ||||||
|  |     pub async fn chunk(&mut self) -> crate::Result<Option<Bytes>> { | ||||||
|  |         if let Some(item) = self.body.next().await { | ||||||
|  |             Ok(Some(item?)) | ||||||
|  |         } else { | ||||||
|  |             Ok(None) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // util methods | ||||||
|  |  | ||||||
|     /// Turn a response into an error if the server returned an error. |     /// Turn a response into an error if the server returned an error. | ||||||
|     /// |     /// | ||||||
|     /// # Example |     /// # Example | ||||||
| @@ -202,7 +296,6 @@ impl Response { | |||||||
|     /// } |     /// } | ||||||
|     /// # fn main() {} |     /// # fn main() {} | ||||||
|     /// ``` |     /// ``` | ||||||
|     #[inline] |  | ||||||
|     pub fn error_for_status(self) -> crate::Result<Self> { |     pub fn error_for_status(self) -> crate::Result<Self> { | ||||||
|         if self.status.is_client_error() || self.status.is_server_error() { |         if self.status.is_client_error() || self.status.is_server_error() { | ||||||
|             Err(crate::error::status_code(*self.url, self.status)) |             Err(crate::error::status_code(*self.url, self.status)) | ||||||
| @@ -232,7 +325,6 @@ impl Response { | |||||||
|     /// } |     /// } | ||||||
|     /// # fn main() {} |     /// # fn main() {} | ||||||
|     /// ``` |     /// ``` | ||||||
|     #[inline] |  | ||||||
|     pub fn error_for_status_ref(&self) -> crate::Result<&Self> { |     pub fn error_for_status_ref(&self) -> crate::Result<&Self> { | ||||||
|         if self.status.is_client_error() || self.status.is_server_error() { |         if self.status.is_client_error() || self.status.is_server_error() { | ||||||
|             Err(crate::error::status_code(*self.url.clone(), self.status)) |             Err(crate::error::status_code(*self.url.clone(), self.status)) | ||||||
| @@ -240,6 +332,17 @@ impl Response { | |||||||
|             Ok(self) |             Ok(self) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // private | ||||||
|  |  | ||||||
|  |     // The Response's body is an implementation detail. | ||||||
|  |     // You no longer need to get a reference to it, there are async methods | ||||||
|  |     // on the `Response` itself. | ||||||
|  |     // | ||||||
|  |     // This method is just used by the blocking API. | ||||||
|  |     pub(crate) fn body_mut(&mut self) -> &mut Decoder { | ||||||
|  |         &mut self.body | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl fmt::Debug for Response { | impl fmt::Debug for Response { | ||||||
| @@ -273,68 +376,10 @@ impl<T: Into<Body>> From<http::Response<T>> for Response { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /// A JSON object. | /// A `Response` can be piped as the `Body` of another request. | ||||||
| struct Json<T> { | impl From<Response> for Body { | ||||||
|     concat: ConcatDecoder, |     fn from(r: Response) -> Body { | ||||||
|     _marker: PhantomData<T>, |         Body::wrap_stream(r.body) | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<T> Json<T> { |  | ||||||
|     fn concat(self: Pin<&mut Self>) -> Pin<&mut ConcatDecoder> { |  | ||||||
|         unsafe { Pin::map_unchecked_mut(self, |x| &mut x.concat) } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<T: DeserializeOwned> Future for Json<T> { |  | ||||||
|     type Output = Result<T, crate::Error>; |  | ||||||
|  |  | ||||||
|     fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |  | ||||||
|         match futures::ready!(self.concat().as_mut().poll(cx)) { |  | ||||||
|             Err(e) => Poll::Ready(Err(e)), |  | ||||||
|             Ok(chunk) => { |  | ||||||
|                 let t = serde_json::from_slice(&chunk).map_err(crate::error::from); |  | ||||||
|                 Poll::Ready(t) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<T> fmt::Debug for Json<T> { |  | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |  | ||||||
|         f.debug_struct("Json").finish() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| //#[derive(Debug)] |  | ||||||
| struct Text { |  | ||||||
|     concat: ConcatDecoder, |  | ||||||
|     encoding: &'static Encoding, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Text { |  | ||||||
|     fn concat(self: Pin<&mut Self>) -> Pin<&mut ConcatDecoder> { |  | ||||||
|         unsafe { Pin::map_unchecked_mut(self, |x| &mut x.concat) } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Future for Text { |  | ||||||
|     type Output = Result<String, crate::Error>; |  | ||||||
|  |  | ||||||
|     fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |  | ||||||
|         match futures::ready!(self.as_mut().concat().as_mut().poll(cx)) { |  | ||||||
|             Err(e) => Poll::Ready(Err(e)), |  | ||||||
|             Ok(chunk) => { |  | ||||||
|                 let (text, _, _) = self.as_mut().encoding.decode(&chunk); |  | ||||||
|                 if let Cow::Owned(s) = text { |  | ||||||
|                     return Poll::Ready(Ok(s)); |  | ||||||
|                 } |  | ||||||
|                 unsafe { |  | ||||||
|                     // decoding returned Cow::Borrowed, meaning these bytes |  | ||||||
|                     // are already valid utf8 |  | ||||||
|                     Poll::Ready(Ok(String::from_utf8_unchecked(chunk.to_vec()))) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -203,8 +203,7 @@ impl Response { | |||||||
|     /// or it cannot be properly deserialized to target type `T`. For more |     /// or it cannot be properly deserialized to target type `T`. For more | ||||||
|     /// details please see [`serde_json::from_reader`]. |     /// details please see [`serde_json::from_reader`]. | ||||||
|     /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html |     /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html | ||||||
|     #[inline] |     pub fn json<T: DeserializeOwned>(self) -> crate::Result<T> { | ||||||
|     pub fn json<T: DeserializeOwned>(&mut self) -> crate::Result<T> { |  | ||||||
|         wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e { |         wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e { | ||||||
|             wait::Waited::TimedOut => crate::error::timedout(None), |             wait::Waited::TimedOut => crate::error::timedout(None), | ||||||
|             wait::Waited::Executor(e) => crate::error::from(e), |             wait::Waited::Executor(e) => crate::error::from(e), | ||||||
| @@ -228,12 +227,7 @@ impl Response { | |||||||
|     /// # Ok(()) |     /// # Ok(()) | ||||||
|     /// # } |     /// # } | ||||||
|     /// ``` |     /// ``` | ||||||
|     /// |     pub fn text(self) -> crate::Result<String> { | ||||||
|     /// # Note |  | ||||||
|     /// |  | ||||||
|     /// This consumes the body. Trying to read more, or use of `response.json()` |  | ||||||
|     /// will return empty values. |  | ||||||
|     pub fn text(&mut self) -> crate::Result<String> { |  | ||||||
|         self.text_with_charset("utf-8") |         self.text_with_charset("utf-8") | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -256,12 +250,7 @@ impl Response { | |||||||
|     /// # Ok(()) |     /// # Ok(()) | ||||||
|     /// # } |     /// # } | ||||||
|     /// ``` |     /// ``` | ||||||
|     /// |     pub fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> { | ||||||
|     /// # Note |  | ||||||
|     /// |  | ||||||
|     /// This consumes the body. Trying to read more, or use of `response.json()` |  | ||||||
|     /// will return empty values. |  | ||||||
|     pub fn text_with_charset(&mut self, default_encoding: &str) -> crate::Result<String> { |  | ||||||
|         wait::timeout(self.inner.text_with_charset(default_encoding), self.timeout).map_err(|e| { |         wait::timeout(self.inner.text_with_charset(default_encoding), self.timeout).map_err(|e| { | ||||||
|             match e { |             match e { | ||||||
|                 wait::Waited::TimedOut => crate::error::timedout(None), |                 wait::Waited::TimedOut => crate::error::timedout(None), | ||||||
|   | |||||||
| @@ -187,8 +187,8 @@ pub use hyper::{StatusCode, Version}; | |||||||
| pub use url::ParseError as UrlError; | pub use url::ParseError as UrlError; | ||||||
| pub use url::Url; | pub use url::Url; | ||||||
|  |  | ||||||
| pub use self::r#async::{ | pub use self::async_impl::{ | ||||||
|     multipart, Body, Client, ClientBuilder, Decoder, Request, RequestBuilder, Response, |     multipart, Body, Client, ClientBuilder, Request, RequestBuilder, Response, | ||||||
| }; | }; | ||||||
| //pub use self::body::Body; | //pub use self::body::Body; | ||||||
| //pub use self::client::{Client, ClientBuilder}; | //pub use self::client::{Client, ClientBuilder}; | ||||||
| @@ -223,7 +223,7 @@ mod tls; | |||||||
| #[deprecated(note = "types moved to top of crate")] | #[deprecated(note = "types moved to top of crate")] | ||||||
| pub mod r#async { | pub mod r#async { | ||||||
|     pub use crate::async_impl::{ |     pub use crate::async_impl::{ | ||||||
|         multipart, Body, Chunk, Client, ClientBuilder, Decoder, Request, RequestBuilder, Response, |         multipart, Body, Client, ClientBuilder, Request, RequestBuilder, Response, | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ fn test_response_text() { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let url = format!("http://{}/text", server.addr()); |     let url = format!("http://{}/text", server.addr()); | ||||||
|     let mut res = reqwest::blocking::get(&url).unwrap(); |     let res = reqwest::blocking::get(&url).unwrap(); | ||||||
|     assert_eq!(res.url().as_str(), &url); |     assert_eq!(res.url().as_str(), &url); | ||||||
|     assert_eq!(res.status(), reqwest::StatusCode::OK); |     assert_eq!(res.status(), reqwest::StatusCode::OK); | ||||||
|     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); |     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); | ||||||
| @@ -57,7 +57,7 @@ fn test_response_non_utf_8_text() { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let url = format!("http://{}/text", server.addr()); |     let url = format!("http://{}/text", server.addr()); | ||||||
|     let mut res = reqwest::blocking::get(&url).unwrap(); |     let res = reqwest::blocking::get(&url).unwrap(); | ||||||
|     assert_eq!(res.url().as_str(), &url); |     assert_eq!(res.url().as_str(), &url); | ||||||
|     assert_eq!(res.status(), reqwest::StatusCode::OK); |     assert_eq!(res.status(), reqwest::StatusCode::OK); | ||||||
|     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); |     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); | ||||||
| @@ -92,7 +92,7 @@ fn test_response_json() { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let url = format!("http://{}/json", server.addr()); |     let url = format!("http://{}/json", server.addr()); | ||||||
|     let mut res = reqwest::blocking::get(&url).unwrap(); |     let res = reqwest::blocking::get(&url).unwrap(); | ||||||
|     assert_eq!(res.url().as_str(), &url); |     assert_eq!(res.url().as_str(), &url); | ||||||
|     assert_eq!(res.status(), reqwest::StatusCode::OK); |     assert_eq!(res.status(), reqwest::StatusCode::OK); | ||||||
|     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); |     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); | ||||||
| @@ -126,7 +126,7 @@ fn test_response_copy_to() { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let url = format!("http://{}/1", server.addr()); |     let url = format!("http://{}/1", server.addr()); | ||||||
|     let mut res = reqwest::blocking::get(&url).unwrap(); |     let res = reqwest::blocking::get(&url).unwrap(); | ||||||
|     assert_eq!(res.url().as_str(), &url); |     assert_eq!(res.url().as_str(), &url); | ||||||
|     assert_eq!(res.status(), reqwest::StatusCode::OK); |     assert_eq!(res.status(), reqwest::StatusCode::OK); | ||||||
|     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); |     assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); | ||||||
| @@ -158,7 +158,7 @@ fn test_get() { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let url = format!("http://{}/1", server.addr()); |     let url = format!("http://{}/1", server.addr()); | ||||||
|     let mut res = reqwest::blocking::get(&url).unwrap(); |     let res = reqwest::blocking::get(&url).unwrap(); | ||||||
|  |  | ||||||
|     assert_eq!(res.url().as_str(), &url); |     assert_eq!(res.url().as_str(), &url); | ||||||
|     assert_eq!(res.status(), reqwest::StatusCode::OK); |     assert_eq!(res.status(), reqwest::StatusCode::OK); | ||||||
| @@ -194,7 +194,7 @@ fn test_post() { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let url = format!("http://{}/2", server.addr()); |     let url = format!("http://{}/2", server.addr()); | ||||||
|     let mut res = reqwest::blocking::Client::new() |     let res = reqwest::blocking::Client::new() | ||||||
|         .post(&url) |         .post(&url) | ||||||
|         .body("Hello") |         .body("Hello") | ||||||
|         .send() |         .send() | ||||||
|   | |||||||
| @@ -4,8 +4,6 @@ mod support; | |||||||
| use std::io::Write; | use std::io::Write; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
|  |  | ||||||
| use futures::TryStreamExt; |  | ||||||
|  |  | ||||||
| use reqwest::multipart::{Form, Part}; | use reqwest::multipart::{Form, Part}; | ||||||
| use reqwest::{Body, Client}; | use reqwest::{Body, Client}; | ||||||
|  |  | ||||||
| @@ -44,7 +42,7 @@ async fn response_text() { | |||||||
|  |  | ||||||
|     let client = Client::new(); |     let client = Client::new(); | ||||||
|  |  | ||||||
|     let mut res = client |     let res = client | ||||||
|         .get(&format!("http://{}/text", server.addr())) |         .get(&format!("http://{}/text", server.addr())) | ||||||
|         .send() |         .send() | ||||||
|         .await |         .await | ||||||
| @@ -76,7 +74,7 @@ async fn response_json() { | |||||||
|  |  | ||||||
|     let client = Client::new(); |     let client = Client::new(); | ||||||
|  |  | ||||||
|     let mut res = client |     let res = client | ||||||
|         .get(&format!("http://{}/json", server.addr())) |         .get(&format!("http://{}/json", server.addr())) | ||||||
|         .send() |         .send() | ||||||
|         .await |         .await | ||||||
| @@ -89,9 +87,12 @@ async fn response_json() { | |||||||
| async fn multipart() { | async fn multipart() { | ||||||
|     let _ = env_logger::try_init(); |     let _ = env_logger::try_init(); | ||||||
|  |  | ||||||
|     let stream = futures::stream::once(futures::future::ready::<Result<_, hyper::Error>>(Ok( |     let stream = reqwest::Body::wrap_stream(futures::stream::once(futures::future::ready(Ok::< | ||||||
|         hyper::Chunk::from("part1 part2".to_owned()), |         _, | ||||||
|     ))); |         reqwest::Error, | ||||||
|  |     >( | ||||||
|  |         "part1 part2".to_owned(), | ||||||
|  |     )))); | ||||||
|     let part = Part::stream(stream); |     let part = Part::stream(stream); | ||||||
|  |  | ||||||
|     let form = Form::new().text("foo", "bar").part("part_stream", part); |     let form = Form::new().text("foo", "bar").part("part_stream", part); | ||||||
| @@ -221,7 +222,7 @@ async fn response_timeout() { | |||||||
|  |  | ||||||
|     let url = format!("http://{}/slow", server.addr()); |     let url = format!("http://{}/slow", server.addr()); | ||||||
|     let res = client.get(&url).send().await.expect("Failed to get"); |     let res = client.get(&url).send().await.expect("Failed to get"); | ||||||
|     let body: Result<_, _> = res.into_body().try_concat().await; |     let body = res.text().await; | ||||||
|  |  | ||||||
|     let err = body.unwrap_err(); |     let err = body.unwrap_err(); | ||||||
|  |  | ||||||
| @@ -269,7 +270,7 @@ async fn gzip_case(response_size: usize, chunk_size: usize) { | |||||||
|  |  | ||||||
|     let client = Client::new(); |     let client = Client::new(); | ||||||
|  |  | ||||||
|     let mut res = client |     let res = client | ||||||
|         .get(&format!("http://{}/gzip", server.addr())) |         .get(&format!("http://{}/gzip", server.addr())) | ||||||
|         .send() |         .send() | ||||||
|         .await |         .await | ||||||
| @@ -323,3 +324,66 @@ async fn body_stream() { | |||||||
|     assert_eq!(res.url().as_str(), &url); |     assert_eq!(res.url().as_str(), &url); | ||||||
|     assert_eq!(res.status(), reqwest::StatusCode::OK); |     assert_eq!(res.status(), reqwest::StatusCode::OK); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[tokio::test] | ||||||
|  | async fn body_pipe_response() { | ||||||
|  |     let _ = env_logger::try_init(); | ||||||
|  |  | ||||||
|  |     let server = server! { | ||||||
|  |         request: b"\ | ||||||
|  |             GET /get HTTP/1.1\r\n\ | ||||||
|  |             user-agent: $USERAGENT\r\n\ | ||||||
|  |             accept: */*\r\n\ | ||||||
|  |             accept-encoding: gzip\r\n\ | ||||||
|  |             host: $HOST\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |             ", | ||||||
|  |         response: b"\ | ||||||
|  |             HTTP/1.1 200 OK\r\n\ | ||||||
|  |             Server: pipe\r\n\ | ||||||
|  |             Content-Length: 7\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |             pipe me\ | ||||||
|  |             "; | ||||||
|  |  | ||||||
|  |         request: b"\ | ||||||
|  |             POST /pipe HTTP/1.1\r\n\ | ||||||
|  |             user-agent: $USERAGENT\r\n\ | ||||||
|  |             accept: */*\r\n\ | ||||||
|  |             accept-encoding: gzip\r\n\ | ||||||
|  |             host: $HOST\r\n\ | ||||||
|  |             transfer-encoding: chunked\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |             7\r\n\ | ||||||
|  |             pipe me\r\n\ | ||||||
|  |             0\r\n\r\n\ | ||||||
|  |             ", | ||||||
|  |         response: b"\ | ||||||
|  |             HTTP/1.1 200 OK\r\n\ | ||||||
|  |             Server: pipe\r\n\ | ||||||
|  |             Content-Length: 0\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |             " | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let client = Client::new(); | ||||||
|  |  | ||||||
|  |     let res1 = client | ||||||
|  |         .get(&format!("http://{}/get", server.addr())) | ||||||
|  |         .send() | ||||||
|  |         .await | ||||||
|  |         .expect("get1"); | ||||||
|  |  | ||||||
|  |     assert_eq!(res1.status(), reqwest::StatusCode::OK); | ||||||
|  |     assert_eq!(res1.content_length(), Some(7)); | ||||||
|  |  | ||||||
|  |     // and now ensure we can "pipe" the response to another request | ||||||
|  |     let res2 = client | ||||||
|  |         .post(&format!("http://{}/pipe", server.addr())) | ||||||
|  |         .body(res1) | ||||||
|  |         .send() | ||||||
|  |         .await | ||||||
|  |         .expect("res2"); | ||||||
|  |  | ||||||
|  |     assert_eq!(res2.status(), reqwest::StatusCode::OK); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -142,7 +142,7 @@ fn test_read_timeout() { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let url = format!("http://{}/read-timeout", server.addr()); |     let url = format!("http://{}/read-timeout", server.addr()); | ||||||
|     let mut res = reqwest::blocking::Client::builder() |     let res = reqwest::blocking::Client::builder() | ||||||
|         .timeout(Duration::from_millis(500)) |         .timeout(Duration::from_millis(500)) | ||||||
|         .build() |         .build() | ||||||
|         .unwrap() |         .unwrap() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user