refactor all to async/await (#617)
Co-authored-by: Danny Browning <danny.browning@protectwise.com> Co-authored-by: Daniel Eades <danieleades@hotmail.com>
This commit is contained in:
		| @@ -1,8 +1,10 @@ | ||||
| use std::fmt; | ||||
|  | ||||
| use bytes::{Buf, Bytes}; | ||||
| use futures::{try_ready, Async, Future, Poll, Stream}; | ||||
| use futures::Stream; | ||||
| use hyper::body::Payload; | ||||
| use std::fmt; | ||||
| use std::future::Future; | ||||
| use std::pin::Pin; | ||||
| use std::task::{Context, Poll}; | ||||
| use tokio::timer::Delay; | ||||
|  | ||||
| /// An asynchronous `Stream`. | ||||
| @@ -22,10 +24,38 @@ 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.content_length(), | ||||
|             Inner::Hyper { ref body, .. } => body.size_hint().exact(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Wrap a futures `Stream` in a box inside `Body`. | ||||
|     /// | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ``` | ||||
|     /// # use reqwest::r#async::Body; | ||||
|     /// # use futures; | ||||
|     /// # fn main() { | ||||
|     /// let chunks: Vec<Result<_, ::std::io::Error>> = vec![ | ||||
|     ///     Ok("hello"), | ||||
|     ///     Ok(" "), | ||||
|     ///     Ok("world"), | ||||
|     /// ]; | ||||
|     /// | ||||
|     /// let stream = futures::stream::iter(chunks); | ||||
|     /// | ||||
|     /// let body = Body::wrap_stream(stream); | ||||
|     /// # } | ||||
|     /// ``` | ||||
|     pub fn wrap_stream<S>(stream: S) -> Body | ||||
|     where | ||||
|         S: futures::TryStream + Send + Sync + 'static, | ||||
|         S::Error: Into<Box<dyn std::error::Error + Send + Sync>>, | ||||
|         hyper::Chunk: From<S::Ok>, | ||||
|     { | ||||
|         Body::wrap(hyper::body::Body::wrap_stream(stream)) | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     pub(crate) fn response(body: hyper::Body, timeout: Option<Delay>) -> Body { | ||||
|         Body { | ||||
| @@ -65,38 +95,45 @@ impl Body { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn inner(self: Pin<&mut Self>) -> Pin<&mut Inner> { | ||||
|         unsafe { Pin::map_unchecked_mut(self, |x| &mut x.inner) } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Stream for Body { | ||||
|     type Item = Chunk; | ||||
|     type Error = crate::Error; | ||||
|     type Item = Result<Chunk, crate::Error>; | ||||
|  | ||||
|     #[inline] | ||||
|     fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { | ||||
|         let opt = match self.inner { | ||||
|     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 Async::Ready(()) = try_!(timeout.poll()) { | ||||
|                         return Err(crate::error::timedout(None)); | ||||
|                     if let Poll::Ready(()) = Pin::new(timeout).poll(cx) { | ||||
|                         return Poll::Ready(Some(Err(crate::error::timedout(None)))); | ||||
|                     } | ||||
|                 } | ||||
|                 try_ready!(body.poll_data().map_err(crate::error::from)) | ||||
|                 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) => { | ||||
|                 return if bytes.is_empty() { | ||||
|                     Ok(Async::Ready(None)) | ||||
|                 if bytes.is_empty() { | ||||
|                     None | ||||
|                 } else { | ||||
|                     let chunk = Chunk::from_chunk(bytes.clone()); | ||||
|                     *bytes = Bytes::new(); | ||||
|                     Ok(Async::Ready(Some(chunk))) | ||||
|                 }; | ||||
|                     Some(Ok(chunk)) | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         Ok(Async::Ready(opt.map(|chunk| Chunk { inner: chunk }))) | ||||
|         Poll::Ready(opt_try_chunk) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -135,18 +172,6 @@ impl From<&'static str> for Body { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<I, E> From<Box<dyn Stream<Item = I, Error = E> + Send>> for Body | ||||
| where | ||||
|     hyper::Chunk: From<I>, | ||||
|     I: 'static, | ||||
|     E: std::error::Error + Send + Sync + 'static, | ||||
| { | ||||
|     #[inline] | ||||
|     fn from(s: Box<dyn Stream<Item = I, Error = E> + Send>) -> Body { | ||||
|         Body::wrap(hyper::Body::wrap_stream(s)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A chunk of bytes for a `Body`. | ||||
| /// | ||||
| /// A `Chunk` can be treated like `&[u8]`. | ||||
| @@ -247,6 +272,12 @@ impl From<Bytes> for Chunk { | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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 | ||||
|   | ||||
| @@ -8,12 +8,14 @@ use crate::header::{ | ||||
|     CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT, | ||||
| }; | ||||
| use bytes::Bytes; | ||||
| use futures::{Async, Future, Poll}; | ||||
| use http::Uri; | ||||
| use hyper::client::ResponseFuture; | ||||
| use mime; | ||||
| #[cfg(feature = "default-tls")] | ||||
| use native_tls::TlsConnector; | ||||
| use std::future::Future; | ||||
| use std::pin::Pin; | ||||
| use std::task::{Context, Poll}; | ||||
| use tokio::{clock, timer::Delay}; | ||||
|  | ||||
| use log::debug; | ||||
| @@ -540,7 +542,10 @@ impl Client { | ||||
|     /// | ||||
|     /// This method fails if there was an error while sending request, | ||||
|     /// redirect loop was detected or redirect limit was exhausted. | ||||
|     pub fn execute(&self, request: Request) -> impl Future<Item = Response, Error = crate::Error> { | ||||
|     pub fn execute( | ||||
|         &self, | ||||
|         request: Request, | ||||
|     ) -> impl Future<Output = Result<Response, crate::Error>> { | ||||
|         self.execute_request(request) | ||||
|     } | ||||
|  | ||||
| @@ -593,7 +598,7 @@ impl Client { | ||||
|         let timeout = self | ||||
|             .inner | ||||
|             .request_timeout | ||||
|             .map(|dur| Delay::new(clock::now() + dur)); | ||||
|             .map(|dur| tokio::timer::delay(clock::now() + dur)); | ||||
|  | ||||
|         Pending { | ||||
|             inner: PendingInner::Request(PendingRequest { | ||||
| @@ -691,43 +696,65 @@ struct PendingRequest { | ||||
|     timeout: Option<Delay>, | ||||
| } | ||||
|  | ||||
| impl PendingRequest { | ||||
|     fn in_flight(self: Pin<&mut Self>) -> Pin<&mut ResponseFuture> { | ||||
|         unsafe { Pin::map_unchecked_mut(self, |x| &mut x.in_flight) } | ||||
|     } | ||||
|  | ||||
|     fn timeout(self: Pin<&mut Self>) -> Pin<&mut Option<Delay>> { | ||||
|         unsafe { Pin::map_unchecked_mut(self, |x| &mut x.timeout) } | ||||
|     } | ||||
|  | ||||
|     fn urls(self: Pin<&mut Self>) -> &mut Vec<Url> { | ||||
|         unsafe { &mut Pin::get_unchecked_mut(self).urls } | ||||
|     } | ||||
|  | ||||
|     fn headers(self: Pin<&mut Self>) -> &mut HeaderMap { | ||||
|         unsafe { &mut Pin::get_unchecked_mut(self).headers } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Pending { | ||||
|     pub(super) fn new_err(err: crate::Error) -> Pending { | ||||
|         Pending { | ||||
|             inner: PendingInner::Error(Some(err)), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn inner(self: Pin<&mut Self>) -> Pin<&mut PendingInner> { | ||||
|         unsafe { Pin::map_unchecked_mut(self, |x| &mut x.inner) } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Future for Pending { | ||||
|     type Item = Response; | ||||
|     type Error = crate::Error; | ||||
|     type Output = Result<Response, crate::Error>; | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         match self.inner { | ||||
|             PendingInner::Request(ref mut req) => req.poll(), | ||||
|             PendingInner::Error(ref mut err) => { | ||||
|                 Err(err.take().expect("Pending error polled more than once")) | ||||
|             } | ||||
|     fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | ||||
|         let inner = self.inner(); | ||||
|         match inner.get_mut() { | ||||
|             PendingInner::Request(ref mut req) => Pin::new(req).poll(cx), | ||||
|             PendingInner::Error(ref mut err) => Poll::Ready(Err(err | ||||
|                 .take() | ||||
|                 .expect("Pending error polled more than once"))), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Future for PendingRequest { | ||||
|     type Item = Response; | ||||
|     type Error = crate::Error; | ||||
|     type Output = Result<Response, crate::Error>; | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         if let Some(ref mut delay) = self.timeout { | ||||
|             if let Async::Ready(()) = try_!(delay.poll(), &self.url) { | ||||
|                 return Err(crate::error::timedout(Some(self.url.clone()))); | ||||
|     fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | ||||
|         if let Some(delay) = self.as_mut().timeout().as_mut().as_pin_mut() { | ||||
|             if let Poll::Ready(()) = delay.poll(cx) { | ||||
|                 return Poll::Ready(Err(crate::error::timedout(Some(self.url.clone())))); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         loop { | ||||
|             let res = match try_!(self.in_flight.poll(), &self.url) { | ||||
|                 Async::Ready(res) => res, | ||||
|                 Async::NotReady => return Ok(Async::NotReady), | ||||
|             let res = match self.as_mut().in_flight().as_mut().poll(cx) { | ||||
|                 Poll::Ready(Err(e)) => return Poll::Ready(url_error!(e, &self.url)), | ||||
|                 Poll::Ready(Ok(res)) => res, | ||||
|                 Poll::Pending => return Poll::Pending, | ||||
|             }; | ||||
|             if let Some(store_wrapper) = self.client.cookie_store.as_ref() { | ||||
|                 let mut store = store_wrapper.write().unwrap(); | ||||
| @@ -795,7 +822,8 @@ impl Future for PendingRequest { | ||||
|                             self.headers.insert(REFERER, referer); | ||||
|                         } | ||||
|                     } | ||||
|                     self.urls.push(self.url.clone()); | ||||
|                     let url = self.url.clone(); | ||||
|                     self.as_mut().urls().push(url); | ||||
|                     let action = self | ||||
|                         .client | ||||
|                         .redirect_policy | ||||
| @@ -805,7 +833,10 @@ impl Future for PendingRequest { | ||||
|                         redirect::Action::Follow => { | ||||
|                             self.url = loc; | ||||
|  | ||||
|                             remove_sensitive_headers(&mut self.headers, &self.url, &self.urls); | ||||
|                             let mut headers = | ||||
|                                 std::mem::replace(self.as_mut().headers(), HeaderMap::new()); | ||||
|  | ||||
|                             remove_sensitive_headers(&mut headers, &self.url, &self.urls); | ||||
|                             debug!("redirecting to {:?} '{}'", self.method, self.url); | ||||
|                             let uri = expect_uri(&self.url); | ||||
|                             let body = match self.body { | ||||
| @@ -821,27 +852,30 @@ impl Future for PendingRequest { | ||||
|                             // Add cookies from the cookie store. | ||||
|                             if let Some(cookie_store_wrapper) = self.client.cookie_store.as_ref() { | ||||
|                                 let cookie_store = cookie_store_wrapper.read().unwrap(); | ||||
|                                 add_cookie_header(&mut self.headers, &cookie_store, &self.url); | ||||
|                                 add_cookie_header(&mut headers, &cookie_store, &self.url); | ||||
|                             } | ||||
|  | ||||
|                             *req.headers_mut() = self.headers.clone(); | ||||
|                             self.in_flight = self.client.hyper.request(req); | ||||
|                             *req.headers_mut() = headers.clone(); | ||||
|                             std::mem::swap(self.as_mut().headers(), &mut headers); | ||||
|                             *self.as_mut().in_flight().get_mut() = self.client.hyper.request(req); | ||||
|                             continue; | ||||
|                         } | ||||
|                         redirect::Action::Stop => { | ||||
|                             debug!("redirect_policy disallowed redirection to '{}'", loc); | ||||
|                         } | ||||
|                         redirect::Action::LoopDetected => { | ||||
|                             return Err(crate::error::loop_detected(self.url.clone())); | ||||
|                             return Poll::Ready(Err(crate::error::loop_detected(self.url.clone()))); | ||||
|                         } | ||||
|                         redirect::Action::TooManyRedirects => { | ||||
|                             return Err(crate::error::too_many_redirects(self.url.clone())); | ||||
|                             return Poll::Ready(Err(crate::error::too_many_redirects( | ||||
|                                 self.url.clone(), | ||||
|                             ))); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             let res = Response::new(res, self.url.clone(), self.client.gzip, self.timeout.take()); | ||||
|             return Ok(Async::Ready(res)); | ||||
|             return Poll::Ready(Ok(res)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -9,25 +9,16 @@ Chunks are just passed along. | ||||
|  | ||||
| If the response is gzip, then the chunks are decompressed into a buffer. | ||||
| Slices of that buffer are emitted as new chunks. | ||||
|  | ||||
| This module consists of a few main types: | ||||
|  | ||||
| - `ReadableChunks` is a `Read`-like wrapper around a stream | ||||
| - `Decoder` is a layer over `ReadableChunks` that applies the right decompression | ||||
|  | ||||
| The following types directly support the gzip compression case: | ||||
|  | ||||
| - `Pending` is a non-blocking constructor for a `Decoder` in case the body needs to be checked for EOF | ||||
| */ | ||||
|  | ||||
| use std::cmp; | ||||
| use std::fmt; | ||||
| use std::io::{self, Read}; | ||||
| use std::future::Future; | ||||
| use std::mem; | ||||
| use std::pin::Pin; | ||||
| use std::task::{Context, Poll}; | ||||
|  | ||||
| use bytes::{Buf, BufMut, BytesMut}; | ||||
| use flate2::read::GzDecoder; | ||||
| use futures::{Async, Future, Poll, Stream}; | ||||
| use bytes::Bytes; | ||||
| use futures::Stream; | ||||
| use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; | ||||
| use hyper::HeaderMap; | ||||
|  | ||||
| @@ -36,8 +27,6 @@ use log::warn; | ||||
| use super::{Body, Chunk}; | ||||
| use crate::error; | ||||
|  | ||||
| const INIT_BUFFER_SIZE: usize = 8192; | ||||
|  | ||||
| /// A response decompressor over a non-blocking stream of chunks. | ||||
| /// | ||||
| /// The inner decoder may be constructed asynchronously. | ||||
| @@ -49,22 +38,15 @@ enum Inner { | ||||
|     /// A `PlainText` decoder just returns the response content as is. | ||||
|     PlainText(Body), | ||||
|     /// A `Gzip` decoder will uncompress the gzipped response content before returning it. | ||||
|     Gzip(Gzip), | ||||
|     Gzip(async_compression::stream::GzipDecoder<futures::stream::Peekable<BodyBytes>>), | ||||
|     /// A decoder that doesn't have a value yet. | ||||
|     Pending(Pending), | ||||
| } | ||||
|  | ||||
| /// A future attempt to poll the response body for EOF so we know whether to use gzip or not. | ||||
| struct Pending { | ||||
|     body: ReadableChunks<Body>, | ||||
| } | ||||
| struct Pending(futures::stream::Peekable<BodyBytes>); | ||||
|  | ||||
| /// A gzip decoder that reads from a `flate2::read::GzDecoder` into a `BytesMut` and emits the results | ||||
| /// as a `Chunk`. | ||||
| struct Gzip { | ||||
|     inner: Box<GzDecoder<ReadableChunks<Body>>>, | ||||
|     buf: BytesMut, | ||||
| } | ||||
| struct BodyBytes(Body); | ||||
|  | ||||
| impl fmt::Debug for Decoder { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
| @@ -86,7 +68,6 @@ impl Decoder { | ||||
|     /// A plain text decoder. | ||||
|     /// | ||||
|     /// This decoder will emit the underlying chunks as-is. | ||||
|     #[inline] | ||||
|     fn plain_text(body: Body) -> Decoder { | ||||
|         Decoder { | ||||
|             inner: Inner::PlainText(body), | ||||
| @@ -96,12 +77,11 @@ impl Decoder { | ||||
|     /// A gzip decoder. | ||||
|     /// | ||||
|     /// This decoder will buffer and decompress chunks that are gzipped. | ||||
|     #[inline] | ||||
|     fn gzip(body: Body) -> Decoder { | ||||
|         use futures::stream::StreamExt; | ||||
|  | ||||
|         Decoder { | ||||
|             inner: Inner::Pending(Pending { | ||||
|                 body: ReadableChunks::new(body), | ||||
|             }), | ||||
|             inner: Inner::Pending(Pending(BodyBytes(body).peekable())), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -148,189 +128,65 @@ impl Decoder { | ||||
| } | ||||
|  | ||||
| impl Stream for Decoder { | ||||
|     type Item = Chunk; | ||||
|     type Error = error::Error; | ||||
|     type Item = Result<Chunk, error::Error>; | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { | ||||
|         // Do a read or poll for a pendidng decoder value. | ||||
|     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. | ||||
|         let new_value = match self.inner { | ||||
|             Inner::Pending(ref mut future) => match future.poll() { | ||||
|                 Ok(Async::Ready(inner)) => inner, | ||||
|                 Ok(Async::NotReady) => return Ok(Async::NotReady), | ||||
|                 Err(e) => return Err(e), | ||||
|             Inner::Pending(ref mut future) => match Pin::new(future).poll(cx) { | ||||
|                 Poll::Ready(Ok(inner)) => inner, | ||||
|                 Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(crate::error::from_io(e)))), | ||||
|                 Poll::Pending => return Poll::Pending, | ||||
|             }, | ||||
|             Inner::PlainText(ref mut body) => return body.poll(), | ||||
|             Inner::Gzip(ref mut decoder) => return decoder.poll(), | ||||
|             Inner::PlainText(ref mut body) => return Pin::new(body).poll_next(cx), | ||||
|             Inner::Gzip(ref mut decoder) => { | ||||
|                 return match futures::ready!(Pin::new(decoder).poll_next(cx)) { | ||||
|                     Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.into()))), | ||||
|                     Some(Err(err)) => Poll::Ready(Some(Err(crate::error::from_io(err)))), | ||||
|                     None => Poll::Ready(None), | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         self.inner = new_value; | ||||
|         self.poll() | ||||
|         self.poll_next(cx) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Future for Pending { | ||||
|     type Item = Inner; | ||||
|     type Error = error::Error; | ||||
|     type Output = Result<Inner, std::io::Error>; | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         let body_state = match self.body.poll_stream() { | ||||
|             Ok(Async::Ready(state)) => state, | ||||
|             Ok(Async::NotReady) => return Ok(Async::NotReady), | ||||
|             Err(e) => return Err(e), | ||||
|     fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | ||||
|         use futures::stream::StreamExt; | ||||
|  | ||||
|         match futures::ready!(Pin::new(&mut self.0).peek(cx)) { | ||||
|             Some(Ok(_)) => { | ||||
|                 // fallthrough | ||||
|             } | ||||
|             Some(Err(_e)) => { | ||||
|                 // error was just a ref, so we need to really poll to move it | ||||
|                 return Poll::Ready(Err(futures::ready!(Pin::new(&mut self.0).poll_next(cx)) | ||||
|                     .expect("just peeked Some") | ||||
|                     .unwrap_err())); | ||||
|             } | ||||
|             None => return Poll::Ready(Ok(Inner::PlainText(Body::empty()))), | ||||
|         }; | ||||
|  | ||||
|         let body = mem::replace(&mut self.body, ReadableChunks::new(Body::empty())); | ||||
|         match body_state { | ||||
|             StreamState::Eof => Ok(Async::Ready(Inner::PlainText(Body::empty()))), | ||||
|             StreamState::HasMore => Ok(Async::Ready(Inner::Gzip(Gzip::new(body)))), | ||||
|         } | ||||
|         let body = mem::replace(&mut self.0, BodyBytes(Body::empty()).peekable()); | ||||
|         Poll::Ready(Ok(Inner::Gzip( | ||||
|             async_compression::stream::GzipDecoder::new(body), | ||||
|         ))) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Gzip { | ||||
|     fn new(stream: ReadableChunks<Body>) -> Self { | ||||
|         Gzip { | ||||
|             buf: BytesMut::with_capacity(INIT_BUFFER_SIZE), | ||||
|             inner: Box::new(GzDecoder::new(stream)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Stream for Gzip { | ||||
|     type Item = Chunk; | ||||
|     type Error = error::Error; | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { | ||||
|         if self.buf.remaining_mut() == 0 { | ||||
|             self.buf.reserve(INIT_BUFFER_SIZE); | ||||
|         } | ||||
|  | ||||
|         // The buffer contains uninitialised memory so getting a readable slice is unsafe. | ||||
|         // We trust the `flate2` and `miniz` writer not to read from the memory given. | ||||
|         // | ||||
|         // To be safe, this memory could be zeroed before passing to `flate2`. | ||||
|         // Otherwise we might need to deal with the case where `flate2` panics. | ||||
|         let read = try_io!(self.inner.read(unsafe { self.buf.bytes_mut() })); | ||||
|  | ||||
|         if read == 0 { | ||||
|             // If GzDecoder reports EOF, it doesn't necessarily mean the | ||||
|             // underlying stream reached EOF (such as the `0\r\n\r\n` | ||||
|             // header meaning a chunked transfer has completed). If it | ||||
|             // isn't polled till EOF, the connection may not be able | ||||
|             // to be re-used. | ||||
|             // | ||||
|             // See https://github.com/seanmonstar/reqwest/issues/508. | ||||
|             let inner_read = try_io!(self.inner.get_mut().read(&mut [0])); | ||||
|             if inner_read == 0 { | ||||
|                 Ok(Async::Ready(None)) | ||||
|             } else { | ||||
|                 Err(error::from(io::Error::new( | ||||
|                     io::ErrorKind::InvalidData, | ||||
|                     "unexpected data after gzip decoder signaled end-of-file", | ||||
|                 ))) | ||||
|             } | ||||
|         } else { | ||||
|             unsafe { self.buf.advance_mut(read) }; | ||||
|             let chunk = Chunk::from_chunk(self.buf.split_to(read).freeze()); | ||||
|  | ||||
|             Ok(Async::Ready(Some(chunk))) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A `Read`able wrapper over a stream of chunks. | ||||
| pub struct ReadableChunks<S> { | ||||
|     state: ReadState, | ||||
|     stream: S, | ||||
| } | ||||
|  | ||||
| enum ReadState { | ||||
|     /// A chunk is ready to be read from. | ||||
|     Ready(Chunk), | ||||
|     /// The next chunk isn't ready yet. | ||||
|     NotReady, | ||||
|     /// The stream has finished. | ||||
|     Eof, | ||||
| } | ||||
|  | ||||
| enum StreamState { | ||||
|     /// More bytes can be read from the stream. | ||||
|     HasMore, | ||||
|     /// No more bytes can be read from the stream. | ||||
|     Eof, | ||||
| } | ||||
|  | ||||
| impl<S> ReadableChunks<S> { | ||||
|     #[inline] | ||||
|     pub(crate) fn new(stream: S) -> Self { | ||||
|         ReadableChunks { | ||||
|             state: ReadState::NotReady, | ||||
|             stream, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S> fmt::Debug for ReadableChunks<S> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("ReadableChunks").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S> Read for ReadableChunks<S> | ||||
| where | ||||
|     S: Stream<Item = Chunk, Error = error::Error>, | ||||
| { | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         loop { | ||||
|             let ret; | ||||
|             match self.state { | ||||
|                 ReadState::Ready(ref mut chunk) => { | ||||
|                     let len = cmp::min(buf.len(), chunk.remaining()); | ||||
|  | ||||
|                     buf[..len].copy_from_slice(&chunk[..len]); | ||||
|                     chunk.advance(len); | ||||
|                     if chunk.is_empty() { | ||||
|                         ret = len; | ||||
|                     } else { | ||||
|                         return Ok(len); | ||||
|                     } | ||||
|                 } | ||||
|                 ReadState::NotReady => match self.poll_stream() { | ||||
|                     Ok(Async::Ready(StreamState::HasMore)) => continue, | ||||
|                     Ok(Async::Ready(StreamState::Eof)) => return Ok(0), | ||||
|                     Ok(Async::NotReady) => return Err(io::ErrorKind::WouldBlock.into()), | ||||
|                     Err(e) => return Err(error::into_io(e)), | ||||
|                 }, | ||||
|                 ReadState::Eof => return Ok(0), | ||||
|             } | ||||
|             self.state = ReadState::NotReady; | ||||
|             return Ok(ret); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S> ReadableChunks<S> | ||||
| where | ||||
|     S: Stream<Item = Chunk, Error = error::Error>, | ||||
| { | ||||
|     /// Poll the readiness of the inner reader. | ||||
|     /// | ||||
|     /// This function will update the internal state and return a simplified | ||||
|     /// version of the `ReadState`. | ||||
|     fn poll_stream(&mut self) -> Poll<StreamState, error::Error> { | ||||
|         match self.stream.poll() { | ||||
|             Ok(Async::Ready(Some(chunk))) => { | ||||
|                 self.state = ReadState::Ready(chunk); | ||||
|  | ||||
|                 Ok(Async::Ready(StreamState::HasMore)) | ||||
|             } | ||||
|             Ok(Async::Ready(None)) => { | ||||
|                 self.state = ReadState::Eof; | ||||
|  | ||||
|                 Ok(Async::Ready(StreamState::Eof)) | ||||
|             } | ||||
|             Ok(Async::NotReady) => Ok(Async::NotReady), | ||||
|             Err(e) => Err(e), | ||||
| impl Stream for BodyBytes { | ||||
|     type Item = Result<Bytes, std::io::Error>; | ||||
|  | ||||
|     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)) { | ||||
|             Some(Ok(chunk)) => Poll::Ready(Some(Ok(chunk.into()))), | ||||
|             Some(Err(err)) => Poll::Ready(Some(Err(err.into_io()))), | ||||
|             None => Poll::Ready(None), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										12
									
								
								src/async_impl/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/async_impl/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| pub use self::body::{Body, Chunk}; | ||||
| pub use self::client::{Client, ClientBuilder}; | ||||
| pub use self::decoder::Decoder; | ||||
| pub use self::request::{Request, RequestBuilder}; | ||||
| pub use self::response::{Response, ResponseBuilderExt}; | ||||
|  | ||||
| pub mod body; | ||||
| pub mod client; | ||||
| pub mod decoder; | ||||
| pub mod multipart; | ||||
| pub(crate) mod request; | ||||
| mod response; | ||||
| @@ -7,9 +7,9 @@ use mime_guess::Mime; | ||||
| use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC}; | ||||
| use uuid::Uuid; | ||||
|  | ||||
| use futures::Stream; | ||||
| use futures::{Stream, StreamExt}; | ||||
|  | ||||
| use super::{Body, Chunk}; | ||||
| use super::Body; | ||||
|  | ||||
| /// An async multipart/form-data request. | ||||
| pub struct Form { | ||||
| @@ -190,11 +190,11 @@ impl Part { | ||||
|     } | ||||
|  | ||||
|     /// Makes a new parameter from an arbitrary stream. | ||||
|     pub fn stream<T>(value: T) -> Part | ||||
|     pub fn stream<T, I, E>(value: T) -> Part | ||||
|     where | ||||
|         T: Stream + Send + 'static, | ||||
|         T::Item: Into<Chunk>, | ||||
|         T::Error: std::error::Error + Send + Sync, | ||||
|         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()), | ||||
| @@ -210,7 +210,7 @@ impl Part { | ||||
|  | ||||
|     /// Tries to set the mime of this part. | ||||
|     pub fn mime_str(self, mime: &str) -> crate::Result<Part> { | ||||
|         Ok(self.mime(try_!(mime.parse()))) | ||||
|         Ok(self.mime(mime.parse().map_err(crate::error::from)?)) | ||||
|     } | ||||
|  | ||||
|     // Re-export when mime 0.4 is available, with split MediaType/MediaRange. | ||||
| @@ -480,6 +480,7 @@ impl PercentEncoding { | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     use futures::TryStreamExt; | ||||
|     use tokio; | ||||
|  | ||||
|     #[test] | ||||
| @@ -487,9 +488,10 @@ mod tests { | ||||
|         let form = Form::new(); | ||||
|  | ||||
|         let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); | ||||
|         let body_ft = form.stream(); | ||||
|         let body = form.stream(); | ||||
|         let s = body.map(|try_c| try_c.map(|c| c.into_bytes())).try_concat(); | ||||
|  | ||||
|         let out = rt.block_on(body_ft.map(|c| c.into_bytes()).concat2()); | ||||
|         let out = rt.block_on(s); | ||||
|         assert_eq!(out.unwrap(), Vec::new()); | ||||
|     } | ||||
|  | ||||
| @@ -498,16 +500,20 @@ mod tests { | ||||
|         let mut form = Form::new() | ||||
|             .part( | ||||
|                 "reader1", | ||||
|                 Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from( | ||||
|                     "part1".to_owned(), | ||||
|                 Part::stream(futures::stream::once(futures::future::ready::< | ||||
|                     Result<hyper::Chunk, hyper::Error>, | ||||
|                 >(Ok( | ||||
|                     hyper::Chunk::from("part1".to_owned()), | ||||
|                 )))), | ||||
|             ) | ||||
|             .part("key1", Part::text("value1")) | ||||
|             .part("key2", Part::text("value2").mime(mime::IMAGE_BMP)) | ||||
|             .part( | ||||
|                 "reader2", | ||||
|                 Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from( | ||||
|                     "part2".to_owned(), | ||||
|                 Part::stream(futures::stream::once(futures::future::ready::< | ||||
|                     Result<hyper::Chunk, hyper::Error>, | ||||
|                 >(Ok( | ||||
|                     hyper::Chunk::from("part2".to_owned()), | ||||
|                 )))), | ||||
|             ) | ||||
|             .part("key3", Part::text("value3").file_name("filename")); | ||||
| @@ -530,11 +536,10 @@ mod tests { | ||||
|              Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ | ||||
|              value3\r\n--boundary--\r\n"; | ||||
|         let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); | ||||
|         let body_ft = form.stream(); | ||||
|         let body = form.stream(); | ||||
|         let s = body.map(|try_c| try_c.map(|c| c.into_bytes())).try_concat(); | ||||
|  | ||||
|         let out = rt | ||||
|             .block_on(body_ft.map(|c| c.into_bytes()).concat2()) | ||||
|             .unwrap(); | ||||
|         let out = rt.block_on(s).unwrap(); | ||||
|         // These prints are for debug purposes in case the test fails | ||||
|         println!( | ||||
|             "START REAL\n{}\nEND REAL", | ||||
| @@ -558,11 +563,10 @@ mod tests { | ||||
|                         value2\r\n\ | ||||
|                         --boundary--\r\n"; | ||||
|         let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); | ||||
|         let body_ft = form.stream(); | ||||
|         let body = form.stream(); | ||||
|         let s = body.map(|try_c| try_c.map(|c| c.into_bytes())).try_concat(); | ||||
|  | ||||
|         let out = rt | ||||
|             .block_on(body_ft.map(|c| c.into_bytes()).concat2()) | ||||
|             .unwrap(); | ||||
|         let out = rt.block_on(s).unwrap(); | ||||
|         // These prints are for debug purposes in case the test fails | ||||
|         println!( | ||||
|             "START REAL\n{}\nEND REAL", | ||||
|   | ||||
| @@ -191,28 +191,20 @@ impl RequestBuilder { | ||||
|     /// Sends a multipart/form-data body. | ||||
|     /// | ||||
|     /// ``` | ||||
|     /// # extern crate futures; | ||||
|     /// # extern crate reqwest; | ||||
|     /// | ||||
|     /// # use reqwest::Error; | ||||
|     /// # use futures::future::Future; | ||||
|     /// | ||||
|     /// # fn run() -> Result<(), Error> { | ||||
|     /// # async fn run() -> Result<(), Error> { | ||||
|     /// let client = reqwest::r#async::Client::new(); | ||||
|     /// let form = reqwest::r#async::multipart::Form::new() | ||||
|     ///     .text("key3", "value3") | ||||
|     ///     .text("key4", "value4"); | ||||
|     /// | ||||
|     /// let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); | ||||
|     /// | ||||
|     /// let response = client.post("your url") | ||||
|     ///     .multipart(form) | ||||
|     ///     .send() | ||||
|     ///     .and_then(|_| { | ||||
|     ///       Ok(()) | ||||
|     ///     }); | ||||
|     /// | ||||
|     /// rt.block_on(response) | ||||
|     ///     .await?; | ||||
|     /// # Ok(()) | ||||
|     /// # } | ||||
|     /// ``` | ||||
|     pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder { | ||||
| @@ -334,23 +326,17 @@ impl RequestBuilder { | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ```no_run | ||||
|     /// # extern crate futures; | ||||
|     /// # extern crate reqwest; | ||||
|     /// # | ||||
|     /// # use reqwest::Error; | ||||
|     /// # use futures::future::Future; | ||||
|     /// # | ||||
|     /// # fn run() -> Result<(), Error> { | ||||
|     /// # async fn run() -> Result<(), Error> { | ||||
|     /// let response = reqwest::r#async::Client::new() | ||||
|     ///     .get("https://hyper.rs") | ||||
|     ///     .send() | ||||
|     ///     .map(|resp| println!("status: {}", resp.status())); | ||||
|     /// | ||||
|     /// let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); | ||||
|     /// rt.block_on(response) | ||||
|     ///     .await?; | ||||
|     /// # Ok(()) | ||||
|     /// # } | ||||
|     /// ``` | ||||
|     pub fn send(self) -> impl Future<Item = Response, Error = crate::Error> { | ||||
|     pub fn send(self) -> impl Future<Output = Result<Response, crate::Error>> { | ||||
|         match self.request { | ||||
|             Ok(req) => self.client.execute_request(req), | ||||
|             Err(err) => Pending::new_err(err), | ||||
|   | ||||
| @@ -3,10 +3,11 @@ use std::fmt; | ||||
| use std::marker::PhantomData; | ||||
| use std::mem; | ||||
| use std::net::SocketAddr; | ||||
| use std::pin::Pin; | ||||
| use std::task::{Context, Poll}; | ||||
|  | ||||
| use encoding_rs::{Encoding, UTF_8}; | ||||
| use futures::stream::Concat2; | ||||
| use futures::{try_ready, Async, Future, Poll, Stream}; | ||||
| use futures::{Future, FutureExt, TryStreamExt}; | ||||
| use http; | ||||
| use hyper::client::connect::HttpInfo; | ||||
| use hyper::header::CONTENT_LENGTH; | ||||
| @@ -20,8 +21,12 @@ use url::Url; | ||||
|  | ||||
| use super::body::Body; | ||||
| use super::Decoder; | ||||
| use crate::async_impl::Chunk; | ||||
| 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`. | ||||
| pub struct Response { | ||||
|     status: StatusCode, | ||||
| @@ -139,7 +144,7 @@ impl Response { | ||||
|     } | ||||
|  | ||||
|     /// Get the response text | ||||
|     pub fn text(&mut self) -> impl Future<Item = String, Error = crate::Error> { | ||||
|     pub fn text(&mut self) -> impl Future<Output = Result<String, crate::Error>> { | ||||
|         self.text_with_charset("utf-8") | ||||
|     } | ||||
|  | ||||
| @@ -147,7 +152,7 @@ impl Response { | ||||
|     pub fn text_with_charset( | ||||
|         &mut self, | ||||
|         default_encoding: &str, | ||||
|     ) -> impl Future<Item = String, Error = crate::Error> { | ||||
|     ) -> impl Future<Output = Result<String, crate::Error>> { | ||||
|         let body = mem::replace(&mut self.body, Decoder::empty()); | ||||
|         let content_type = self | ||||
|             .headers | ||||
| @@ -160,18 +165,18 @@ impl Response { | ||||
|             .unwrap_or(default_encoding); | ||||
|         let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8); | ||||
|         Text { | ||||
|             concat: body.concat2(), | ||||
|             concat: body.try_concat().boxed(), | ||||
|             encoding, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Try to deserialize the response body as JSON using `serde`. | ||||
|     #[inline] | ||||
|     pub fn json<T: DeserializeOwned>(&mut self) -> impl Future<Item = T, Error = crate::Error> { | ||||
|     pub fn json<T: DeserializeOwned>(&mut self) -> impl Future<Output = Result<T, crate::Error>> { | ||||
|         let body = mem::replace(&mut self.body, Decoder::empty()); | ||||
|  | ||||
|         Json { | ||||
|             concat: body.concat2(), | ||||
|             concat: body.try_concat().boxed(), | ||||
|             _marker: PhantomData, | ||||
|         } | ||||
|     } | ||||
| @@ -270,17 +275,27 @@ impl<T: Into<Body>> From<http::Response<T>> for Response { | ||||
|  | ||||
| /// A JSON object. | ||||
| struct Json<T> { | ||||
|     concat: Concat2<Decoder>, | ||||
|     concat: ConcatDecoder, | ||||
|     _marker: PhantomData<T>, | ||||
| } | ||||
|  | ||||
| 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 Item = T; | ||||
|     type Error = crate::Error; | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         let bytes = try_ready!(self.concat.poll()); | ||||
|         let t = try_!(serde_json::from_slice(&bytes)); | ||||
|         Ok(Async::Ready(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) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -290,29 +305,36 @@ impl<T> fmt::Debug for Json<T> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| //#[derive(Debug)] | ||||
| struct Text { | ||||
|     concat: Concat2<Decoder>, | ||||
|     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 Item = String; | ||||
|     type Error = crate::Error; | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         let bytes = try_ready!(self.concat.poll()); | ||||
|         // a block because of borrow checker | ||||
|         { | ||||
|             let (text, _, _) = self.encoding.decode(&bytes); | ||||
|             if let Cow::Owned(s) = text { | ||||
|                 return Ok(Async::Ready(s)); | ||||
|     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()))) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         unsafe { | ||||
|             // decoding returned Cow::Borrowed, meaning these bytes | ||||
|             // are already valid utf8 | ||||
|             Ok(Async::Ready(String::from_utf8_unchecked(bytes.to_vec()))) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user