support async gzip decoding
This commit is contained in:
		
							
								
								
									
										202
									
								
								src/response.rs
									
									
									
									
									
								
							
							
						
						
									
										202
									
								
								src/response.rs
									
									
									
									
									
								
							| @@ -2,19 +2,18 @@ use std::fmt; | ||||
| use std::io::{self, Read}; | ||||
| use std::time::Duration; | ||||
|  | ||||
| use libflate::gzip; | ||||
| use futures::{Async, Poll, Stream}; | ||||
| use serde::de::DeserializeOwned; | ||||
| use serde_json; | ||||
|  | ||||
| use client::KeepCoreThreadAlive; | ||||
| use header::{Headers, ContentEncoding, ContentLength, Encoding, TransferEncoding}; | ||||
| use header::Headers; | ||||
| use {async_impl, StatusCode, Url, wait}; | ||||
|  | ||||
|  | ||||
| /// A Response to a submitted `Request`. | ||||
| pub struct Response { | ||||
|     body: Decoder, | ||||
|     inner: async_impl::Response, | ||||
|     body: async_impl::ReadableChunks<WaitBody>, | ||||
|     _thread_handle: KeepCoreThreadAlive, | ||||
| } | ||||
|  | ||||
| @@ -183,11 +182,11 @@ impl Response { | ||||
|     /// ``` | ||||
|     #[inline] | ||||
|     pub fn error_for_status(self) -> ::Result<Self> { | ||||
|         let Response { body, inner, _thread_handle } = self; | ||||
|         let Response { inner, body, _thread_handle } = self; | ||||
|         inner.error_for_status().map(move |inner| { | ||||
|             Response { | ||||
|                 body: body, | ||||
|                 inner: inner, | ||||
|                 body: body, | ||||
|                 _thread_handle: _thread_handle, | ||||
|             } | ||||
|         }) | ||||
| @@ -201,189 +200,40 @@ impl Read for Response { | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ReadableBody { | ||||
|     state: ReadState, | ||||
|     stream:  wait::WaitStream<async_impl::Body>, | ||||
| struct WaitBody { | ||||
|     inner: wait::WaitStream<async_impl::Decoder> | ||||
| } | ||||
|  | ||||
| enum ReadState { | ||||
|     Ready(async_impl::Chunk, usize), | ||||
|     NotReady, | ||||
|     Eof, | ||||
| } | ||||
| impl Stream for WaitBody { | ||||
|     type Item = <async_impl::Decoder as Stream>::Item; | ||||
|     type Error = <async_impl::Decoder as Stream>::Error; | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { | ||||
|         match self.inner.next() { | ||||
|             Some(Ok(chunk)) => Ok(Async::Ready(Some(chunk))), | ||||
|             Some(Err(e)) => { | ||||
|                 let req_err = match e { | ||||
|                     wait::Waited::TimedOut => ::error::timedout(None), | ||||
|                     wait::Waited::Err(e) => e, | ||||
|                 }; | ||||
|  | ||||
| impl Read for ReadableBody { | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         use std::cmp; | ||||
|  | ||||
|         loop { | ||||
|             let ret; | ||||
|             match self.state { | ||||
|                 ReadState::Ready(ref mut chunk, ref mut pos) => { | ||||
|                     let chunk_start = *pos; | ||||
|                     let len = cmp::min(buf.len(), chunk.len() - chunk_start); | ||||
|                     let chunk_end = chunk_start + len; | ||||
|                     buf[..len].copy_from_slice(&chunk[chunk_start..chunk_end]); | ||||
|                     *pos += len; | ||||
|                     if *pos == chunk.len() { | ||||
|                         ret = len; | ||||
|                     } else { | ||||
|                         return Ok(len); | ||||
|                     } | ||||
|                 }, | ||||
|                 ReadState::NotReady => { | ||||
|                     match self.stream.next() { | ||||
|                         Some(Ok(chunk)) => { | ||||
|                             self.state = ReadState::Ready(chunk, 0); | ||||
|                             continue; | ||||
|                         }, | ||||
|                         Some(Err(e)) => { | ||||
|                             let req_err = match e { | ||||
|                                 wait::Waited::TimedOut => ::error::timedout(None), | ||||
|                                 wait::Waited::Err(e) => e, | ||||
|                             }; | ||||
|                             return Err(::error::into_io(req_err)); | ||||
|                         }, | ||||
|                         None => { | ||||
|                             self.state = ReadState::Eof; | ||||
|                             return Ok(0); | ||||
|                         }, | ||||
|                     } | ||||
|                 }, | ||||
|                 ReadState::Eof => return Ok(0), | ||||
|             } | ||||
|             self.state = ReadState::NotReady; | ||||
|             return Ok(ret); | ||||
|                 Err(req_err) | ||||
|             }, | ||||
|             None => Ok(Async::Ready(None)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| enum Decoder { | ||||
|     /// A `PlainText` decoder just returns the response content as is. | ||||
|     PlainText(ReadableBody), | ||||
|     /// A `Gzip` decoder will uncompress the gziped response content before returning it. | ||||
|     Gzip(gzip::Decoder<Peeked>), | ||||
|     /// An error occured reading the Gzip header, so return that error | ||||
|     /// when the user tries to read on the `Response`. | ||||
|     Errored(Option<io::Error>), | ||||
| } | ||||
|  | ||||
| impl Decoder { | ||||
|     /// Constructs a Decoder from a hyper request. | ||||
|     /// | ||||
|     /// A decoder is just a wrapper around the hyper request that knows | ||||
|     /// how to decode the content body of the request. | ||||
|     /// | ||||
|     /// Uses the correct variant by inspecting the Content-Encoding header. | ||||
|     fn new(res: &mut async_impl::Response, check_gzip: bool, timeout: Option<Duration>) -> Self { | ||||
|         let body = async_impl::body::take(res.body_mut()); | ||||
|         let body = ReadableBody { | ||||
|             state: ReadState::NotReady, | ||||
|             stream: wait::stream(body, timeout), | ||||
|         }; | ||||
|  | ||||
|         if !check_gzip { | ||||
|             return Decoder::PlainText(body); | ||||
|         } | ||||
|         let content_encoding_gzip: bool; | ||||
|         let mut is_gzip = { | ||||
|             content_encoding_gzip = res.headers() | ||||
|                 .get::<ContentEncoding>() | ||||
|                 .map_or(false, |encs| encs.contains(&Encoding::Gzip)); | ||||
|             content_encoding_gzip || | ||||
|             res.headers() | ||||
|                 .get::<TransferEncoding>() | ||||
|                 .map_or(false, |encs| encs.contains(&Encoding::Gzip)) | ||||
|         }; | ||||
|         if is_gzip { | ||||
|             if let Some(content_length) = res.headers().get::<ContentLength>() { | ||||
|                 if content_length.0 == 0 { | ||||
|                     warn!("GZipped response with content-length of 0"); | ||||
|                     is_gzip = false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if content_encoding_gzip { | ||||
|             res.headers_mut().remove::<ContentEncoding>(); | ||||
|             res.headers_mut().remove::<ContentLength>(); | ||||
|         } | ||||
|         if is_gzip { | ||||
|             new_gzip(body) | ||||
|         } else { | ||||
|             Decoder::PlainText(body) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn new_gzip(mut body: ReadableBody) -> Decoder { | ||||
|     // libflate does a read_exact([0; 2]), so its impossible to tell | ||||
|     // if the stream was empty, or truly had an UnexpectedEof. | ||||
|     // Therefore, we need to peek a byte to make check for EOF first. | ||||
|     let mut peek = [0]; | ||||
|     match body.read(&mut peek) { | ||||
|         Ok(0) => return Decoder::PlainText(body), | ||||
|         Ok(n) => debug_assert_eq!(n, 1), | ||||
|         Err(e) => return Decoder::Errored(Some(e)), | ||||
|     } | ||||
|  | ||||
|     let reader = Peeked { | ||||
|         peeked: Some(peek[0]), | ||||
|         inner: body, | ||||
|     }; | ||||
|     match gzip::Decoder::new(reader) { | ||||
|         Ok(gzip) => Decoder::Gzip(gzip), | ||||
|         Err(e) => Decoder::Errored(Some(e)), | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct Peeked { | ||||
|     peeked: Option<u8>, | ||||
|     inner: ReadableBody, | ||||
| } | ||||
|  | ||||
| impl Read for Peeked { | ||||
|     #[inline] | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         if buf.is_empty() { | ||||
|             return Ok(0); | ||||
|         } | ||||
|         if let Some(byte) = self.peeked.take() { | ||||
|             buf[0] = byte; | ||||
|             Ok(1) | ||||
|         } else { | ||||
|             self.inner.read(buf) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Read for Decoder { | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         match *self { | ||||
|             Decoder::PlainText(ref mut body) => body.read(buf), | ||||
|             Decoder::Gzip(ref mut decoder) => decoder.read(buf), | ||||
|             Decoder::Errored(ref mut err) => { | ||||
|                 Err(err.take().unwrap_or_else(previously_errored)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[inline] | ||||
| fn previously_errored() -> io::Error { | ||||
|     io::Error::new(io::ErrorKind::Other, "permanently errored") | ||||
| } | ||||
|  | ||||
|  | ||||
| // pub(crate) | ||||
|  | ||||
| pub fn new(mut res: async_impl::Response, gzip: bool, timeout: Option<Duration>, thread: KeepCoreThreadAlive) -> Response { | ||||
| pub fn new(mut res: async_impl::Response, timeout: Option<Duration>, thread: KeepCoreThreadAlive) -> Response { | ||||
|     let body = async_impl::ReadableChunks::new(WaitBody { | ||||
|         inner: wait::stream(res.body(), timeout) | ||||
|     }); | ||||
|  | ||||
|     let decoder = Decoder::new(&mut res, gzip, timeout); | ||||
|     Response { | ||||
|         body: decoder, | ||||
|         inner: res, | ||||
|         body: body, | ||||
|         _thread_handle: thread, | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user