diff --git a/src/async_impl/decoder.rs b/src/async_impl/decoder.rs index 084863d..1ec034e 100644 --- a/src/async_impl/decoder.rs +++ b/src/async_impl/decoder.rs @@ -217,28 +217,30 @@ impl Stream for Gzip { // // 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 = { - let mut buf = unsafe { self.buf.bytes_mut() }; - self.inner.read(&mut buf) - }; + let read = try_io!(self.inner.read(unsafe { self.buf.bytes_mut() })); - match read { - Ok(read) if read == 0 => match self.inner.get_mut().read(&mut [0]) { - Ok(0) => Ok(Async::Ready(None)), - Ok(_) => Err(error::from(io::Error::new(io::ErrorKind::InvalidData, "Unexpected Data"))), - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(Async::NotReady), - Err(e) => Err(error::from(e)) - }, - Ok(read) => { - unsafe { self.buf.advance_mut(read) }; - let chunk = Chunk::from_chunk(self.buf.split_to(read).freeze()); + 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))) - }, - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - Ok(Async::NotReady) - }, - Err(e) => Err(error::from(e)) + Ok(Async::Ready(Some(chunk))) } } } diff --git a/src/error.rs b/src/error.rs index a9241e6..0f11a6b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -484,6 +484,18 @@ pub(crate) fn into_io(e: Error) -> io::Error { } } +pub(crate) fn from_io(e: io::Error) -> Error { + if e.get_ref().map(|r| r.is::()).unwrap_or(false) { + *e + .into_inner() + .expect("io::Error::get_ref was Some(_)") + .downcast::() + .expect("StdError::is() was true") + } else { + from(e) + } +} + macro_rules! try_ { ($e:expr) => ( @@ -504,6 +516,20 @@ macro_rules! try_ { ) } +macro_rules! try_io { + ($e:expr) => ( + match $e { + Ok(v) => v, + Err(ref err) if err.kind() == ::std::io::ErrorKind::WouldBlock => { + return Ok(::futures::Async::NotReady); + } + Err(err) => { + return Err(::error::from_io(err)); + } + } + ) +} + pub(crate) fn loop_detected(url: Url) -> Error { Error::new(Kind::RedirectLoop, Some(url)) } @@ -594,4 +620,18 @@ mod tests { use std::mem::size_of; assert_eq!(size_of::(), size_of::()); } + + #[test] + fn roundtrip_io_error() { + let orig = unknown_proxy_scheme(); + // Convert reqwest::Error into an io::Error... + let io = into_io(orig); + // Convert that io::Error back into a reqwest::Error... + let err = from_io(io); + // It should have pulled out the original, not nested it... + match err.inner.kind { + Kind::UnknownProxyScheme => (), + _ => panic!("{:?}", err), + } + } }