Make gzip an optional feature (default off)
This commit is contained in:
		| @@ -15,5 +15,5 @@ install: | |||||||
|   - cargo -vV |   - cargo -vV | ||||||
| build: false | build: false | ||||||
| test_script: | test_script: | ||||||
|   - cargo test -- --test-threads=1 |   - cargo test --features blocking,gzip -- --test-threads=1 | ||||||
| skip_branch_with_pr: true | skip_branch_with_pr: true | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -32,6 +32,16 @@ matrix: | |||||||
|         - rust: nightly |         - rust: nightly | ||||||
|           env: FEATURES="--features cookies" |           env: FEATURES="--features cookies" | ||||||
|  |  | ||||||
|  |         # optional blocking | ||||||
|  |         #- rust: stable | ||||||
|  |         - rust: nightly | ||||||
|  |           env: FEATURES="--features blocking" | ||||||
|  |  | ||||||
|  |         # optional gzip | ||||||
|  |         #- rust: stable | ||||||
|  |         - rust: nightly | ||||||
|  |           env: FEATURES="--features gzip" | ||||||
|  |  | ||||||
|         # socks |         # socks | ||||||
|         #- rust: stable |         #- rust: stable | ||||||
|         #- rust: nightly |         #- rust: nightly | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								Cargo.toml
									
									
									
									
									
								
							| @@ -37,7 +37,6 @@ time = "0.1.42" | |||||||
|  |  | ||||||
| # TODO: candidates for optional features | # TODO: candidates for optional features | ||||||
|  |  | ||||||
| async-compression = { version = "0.1.0-alpha.4", default-features = false, features = ["gzip", "stream"] } |  | ||||||
|  |  | ||||||
| serde = "1.0" | serde = "1.0" | ||||||
| serde_json = "1.0" | serde_json = "1.0" | ||||||
| @@ -63,6 +62,9 @@ futures-channel-preview = { version = "=0.3.0-alpha.18", optional = true } | |||||||
| cookie_crate = { version = "0.12", package = "cookie", optional = true } | cookie_crate = { version = "0.12", package = "cookie", optional = true } | ||||||
| cookie_store = { version = "0.9", optional = true } | cookie_store = { version = "0.9", optional = true } | ||||||
|  |  | ||||||
|  | ## gzip | ||||||
|  | async-compression = { version = "0.1.0-alpha.4", default-features = false, features = ["gzip", "stream"], optional = true } | ||||||
|  |  | ||||||
| ## socks | ## socks | ||||||
| #socks = { version = "0.3.2", optional = true } | #socks = { version = "0.3.2", optional = true } | ||||||
|  |  | ||||||
| @@ -87,10 +89,12 @@ default-tls-vendored = ["default-tls", "native-tls/vendored"] | |||||||
|  |  | ||||||
| rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "tls"] | rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "tls"] | ||||||
|  |  | ||||||
| blocking = ["futures-channel-preview"] | blocking = ["futures-channel-preview", "futures-util-preview/io"] | ||||||
|  |  | ||||||
| cookies = ["cookie_crate", "cookie_store"] | cookies = ["cookie_crate", "cookie_store"] | ||||||
|  |  | ||||||
|  | gzip = ["async-compression"] | ||||||
|  |  | ||||||
| #trust-dns = ["trust-dns-resolver"] | #trust-dns = ["trust-dns-resolver"] | ||||||
|  |  | ||||||
| [target.'cfg(windows)'.dependencies] | [target.'cfg(windows)'.dependencies] | ||||||
| @@ -111,3 +115,8 @@ name = "cookie" | |||||||
| path = "tests/cookie.rs" | path = "tests/cookie.rs" | ||||||
| required-features = ["cookies"] | required-features = ["cookies"] | ||||||
|  |  | ||||||
|  | [[test]] | ||||||
|  | name = "gzip" | ||||||
|  | path = "tests/gzip.rs" | ||||||
|  | required-features = ["gzip"] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -67,6 +67,7 @@ impl Body { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[cfg(any(feature = "blocking", feature = "gzip",))] | ||||||
|     pub(crate) fn empty() -> Body { |     pub(crate) fn empty() -> Body { | ||||||
|         Body::wrap(hyper::Body::empty()) |         Body::wrap(hyper::Body::empty()) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -97,7 +97,7 @@ impl ClientBuilder { | |||||||
|  |  | ||||||
|         ClientBuilder { |         ClientBuilder { | ||||||
|             config: Config { |             config: Config { | ||||||
|                 gzip: true, |                 gzip: cfg!(feature = "gzip"), | ||||||
|                 headers, |                 headers, | ||||||
|                 #[cfg(feature = "default-tls")] |                 #[cfg(feature = "default-tls")] | ||||||
|                 hostname_verification: true, |                 hostname_verification: true, | ||||||
| @@ -312,22 +312,45 @@ impl ClientBuilder { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Enable auto gzip decompression by checking the ContentEncoding response header. |     /// Enable auto gzip decompression by checking the `Content-Encoding` response header. | ||||||
|     /// |     /// | ||||||
|     /// If auto gzip decompresson is turned on: |     /// If auto gzip decompresson is turned on: | ||||||
|  |     /// | ||||||
|     /// - When sending a request and if the request's headers do not already contain |     /// - When sending a request and if the request's headers do not already contain | ||||||
|     ///   an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`. |     ///   an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`. | ||||||
|     ///   The body is **not** automatically compressed. |     ///   The request body is **not** automatically compressed. | ||||||
|     /// - When receiving a response, if it's headers contain a `Content-Encoding` value that |     /// - When receiving a response, if it's headers contain a `Content-Encoding` value that | ||||||
|     ///   equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the |     ///   equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the | ||||||
|     ///   headers' set. The body is automatically decompressed. |     ///   headers' set. The response body is automatically decompressed. | ||||||
|     /// |     /// | ||||||
|     /// Default is enabled. |     /// If the `gzip` feature is turned on, the default option is enabled. | ||||||
|  |     /// | ||||||
|  |     /// # Optional | ||||||
|  |     /// | ||||||
|  |     /// This requires the optional `gzip` feature to be enabled | ||||||
|  |     #[cfg(feature = "gzip")] | ||||||
|     pub fn gzip(mut self, enable: bool) -> ClientBuilder { |     pub fn gzip(mut self, enable: bool) -> ClientBuilder { | ||||||
|         self.config.gzip = enable; |         self.config.gzip = enable; | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Disable auto response body gzip decompression. | ||||||
|  |     /// | ||||||
|  |     /// This method exists even if the optional `gzip` feature is not enabled. | ||||||
|  |     /// This can be used to ensure a `Client` doesn't use gzip decompression | ||||||
|  |     /// even if another dependency were to enable the optional `gzip` feature. | ||||||
|  |     pub fn no_gzip(self) -> ClientBuilder { | ||||||
|  |         #[cfg(feature = "gzip")] | ||||||
|  |         { | ||||||
|  |             self.gzip(false) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         #[cfg(not(feature = "gzip"))] | ||||||
|  |         { | ||||||
|  |             self | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Add a `Proxy` to the list of proxies the `Client` will use. |     /// Add a `Proxy` to the list of proxies the `Client` will use. | ||||||
|     pub fn proxy(mut self, proxy: Proxy) -> ClientBuilder { |     pub fn proxy(mut self, proxy: Proxy) -> ClientBuilder { | ||||||
|         self.config.proxies.push(proxy); |         self.config.proxies.push(proxy); | ||||||
|   | |||||||
| @@ -1,32 +1,64 @@ | |||||||
| /*! | pub(crate) use self::imp::Decoder; | ||||||
| A potentially non-blocking response decoder. |  | ||||||
|  |  | ||||||
| The decoder wraps a stream of chunks and produces a new stream of decompressed chunks. | #[cfg(not(feature = "gzip"))] | ||||||
| The decompressed chunks aren't guaranteed to align to the compressed ones. | mod imp { | ||||||
|  |  | ||||||
| If the response is plaintext then no additional work is carried out. |  | ||||||
| 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. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| use std::fmt; |  | ||||||
| use std::future::Future; |  | ||||||
| use std::mem; |  | ||||||
|     use std::pin::Pin; |     use std::pin::Pin; | ||||||
|     use std::task::{Context, Poll}; |     use std::task::{Context, Poll}; | ||||||
|  |  | ||||||
|  |     use bytes::Bytes; | ||||||
|  |     use futures_core::Stream; | ||||||
|  |     use http::HeaderMap; | ||||||
|  |  | ||||||
|  |     use super::super::Body; | ||||||
|  |     pub(crate) struct Decoder { | ||||||
|  |         inner: super::super::body::ImplStream, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Decoder { | ||||||
|  |         #[cfg(feature = "blocking")] | ||||||
|  |         pub(crate) fn empty() -> Decoder { | ||||||
|  |             Decoder::plain_text(Body::empty()) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// A plain text decoder. | ||||||
|  |         /// | ||||||
|  |         /// This decoder will emit the underlying chunks as-is. | ||||||
|  |         fn plain_text(body: Body) -> Decoder { | ||||||
|  |             Decoder { | ||||||
|  |                 inner: body.into_stream(), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn detect(_: &mut HeaderMap, body: Body, _: bool) -> Decoder { | ||||||
|  |             Decoder::plain_text(body) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Stream for Decoder { | ||||||
|  |         type Item = crate::Result<Bytes>; | ||||||
|  |  | ||||||
|  |         fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { | ||||||
|  |             Pin::new(&mut self.inner).poll_next(cx) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "gzip")] | ||||||
|  | mod imp { | ||||||
|  |     use std::future::Future; | ||||||
|  |     use std::pin::Pin; | ||||||
|  |     use std::task::{Context, Poll}; | ||||||
|  |     use std::{fmt, mem}; | ||||||
|  |  | ||||||
|     use async_compression::stream::GzipDecoder; |     use async_compression::stream::GzipDecoder; | ||||||
|     use bytes::Bytes; |     use bytes::Bytes; | ||||||
|     use futures_core::Stream; |     use futures_core::Stream; | ||||||
|     use futures_util::stream::Peekable; |     use futures_util::stream::Peekable; | ||||||
| use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; |     use http::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; | ||||||
| use hyper::HeaderMap; |     use http::HeaderMap; | ||||||
|  |  | ||||||
|     use log::warn; |     use log::warn; | ||||||
|  |  | ||||||
| use super::Body; |     use super::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. | ||||||
| @@ -38,7 +70,7 @@ pub(crate) struct Decoder { | |||||||
|  |  | ||||||
|     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(super::body::ImplStream), |         PlainText(super::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(GzipDecoder<Peekable<IoStream>>), |         Gzip(GzipDecoder<Peekable<IoStream>>), | ||||||
|         /// A decoder that doesn't have a value yet. |         /// A decoder that doesn't have a value yet. | ||||||
| @@ -48,7 +80,7 @@ enum Inner { | |||||||
|     /// 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(Peekable<IoStream>); |     struct Pending(Peekable<IoStream>); | ||||||
|  |  | ||||||
| struct IoStream(super::body::ImplStream); |     struct IoStream(super::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 { | ||||||
| @@ -57,9 +89,6 @@ impl fmt::Debug for Decoder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     impl Decoder { |     impl Decoder { | ||||||
|     /// An empty decoder. |  | ||||||
|     /// |  | ||||||
|     /// This decoder will produce a single 0 byte chunk. |  | ||||||
|         #[cfg(feature = "blocking")] |         #[cfg(feature = "blocking")] | ||||||
|         pub(crate) fn empty() -> Decoder { |         pub(crate) fn empty() -> Decoder { | ||||||
|             Decoder { |             Decoder { | ||||||
| @@ -137,7 +166,9 @@ impl Stream for Decoder { | |||||||
|             let new_value = match self.inner { |             let new_value = match self.inner { | ||||||
|                 Inner::Pending(ref mut future) => match Pin::new(future).poll(cx) { |                 Inner::Pending(ref mut future) => match Pin::new(future).poll(cx) { | ||||||
|                     Poll::Ready(Ok(inner)) => inner, |                     Poll::Ready(Ok(inner)) => inner, | ||||||
|                 Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(crate::error::decode_io(e)))), |                     Poll::Ready(Err(e)) => { | ||||||
|  |                         return Poll::Ready(Some(Err(crate::error::decode_io(e)))) | ||||||
|  |                     } | ||||||
|                     Poll::Pending => return Poll::Pending, |                     Poll::Pending => return Poll::Pending, | ||||||
|                 }, |                 }, | ||||||
|                 Inner::PlainText(ref mut body) => return Pin::new(body).poll_next(cx), |                 Inner::PlainText(ref mut body) => return Pin::new(body).poll_next(cx), | ||||||
| @@ -195,3 +226,4 @@ impl Stream for IoStream { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -53,7 +53,6 @@ pub struct Client { | |||||||
| /// use std::time::Duration; | /// use std::time::Duration; | ||||||
| /// | /// | ||||||
| /// let client = reqwest::blocking::Client::builder() | /// let client = reqwest::blocking::Client::builder() | ||||||
| ///     .gzip(true) |  | ||||||
| ///     .timeout(Duration::from_secs(10)) | ///     .timeout(Duration::from_secs(10)) | ||||||
| ///     .build()?; | ///     .build()?; | ||||||
| /// # Ok(()) | /// # Ok(()) | ||||||
| @@ -256,21 +255,36 @@ impl ClientBuilder { | |||||||
|         self.with_inner(move |inner| inner.default_headers(headers)) |         self.with_inner(move |inner| inner.default_headers(headers)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Enable auto gzip decompression by checking the ContentEncoding response header. |     /// Enable auto gzip decompression by checking the `Content-Encoding` response header. | ||||||
|     /// |     /// | ||||||
|     /// If auto gzip decompresson is turned on: |     /// If auto gzip decompresson is turned on: | ||||||
|  |     /// | ||||||
|     /// - When sending a request and if the request's headers do not already contain |     /// - When sending a request and if the request's headers do not already contain | ||||||
|     ///   an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`. |     ///   an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`. | ||||||
|     ///   The body is **not** automatically compressed. |     ///   The request body is **not** automatically compressed. | ||||||
|     /// - When receiving a response, if it's headers contain a `Content-Encoding` value that |     /// - When receiving a response, if it's headers contain a `Content-Encoding` value that | ||||||
|     ///   equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the |     ///   equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the | ||||||
|     ///   headers' set. The body is automatically decompressed. |     ///   headers' set. The response body is automatically decompressed. | ||||||
|     /// |     /// | ||||||
|     /// Default is enabled. |     /// If the `gzip` feature is turned on, the default option is enabled. | ||||||
|  |     /// | ||||||
|  |     /// # Optional | ||||||
|  |     /// | ||||||
|  |     /// This requires the optional `gzip` feature to be enabled | ||||||
|  |     #[cfg(feature = "gzip")] | ||||||
|     pub fn gzip(self, enable: bool) -> ClientBuilder { |     pub fn gzip(self, enable: bool) -> ClientBuilder { | ||||||
|         self.with_inner(|inner| inner.gzip(enable)) |         self.with_inner(|inner| inner.gzip(enable)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Disable auto response body gzip decompression. | ||||||
|  |     /// | ||||||
|  |     /// This method exists even if the optional `gzip` feature is not enabled. | ||||||
|  |     /// This can be used to ensure a `Client` doesn't use gzip decompression | ||||||
|  |     /// even if another dependency were to enable the optional `gzip` feature. | ||||||
|  |     pub fn no_gzip(self) -> ClientBuilder { | ||||||
|  |         self.with_inner(|inner| inner.no_gzip()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Add a `Proxy` to the list of proxies the `Client` will use. |     /// Add a `Proxy` to the list of proxies the `Client` will use. | ||||||
|     pub fn proxy(self, proxy: Proxy) -> ClientBuilder { |     pub fn proxy(self, proxy: Proxy) -> ClientBuilder { | ||||||
|         self.with_inner(move |inner| inner.proxy(proxy)) |         self.with_inner(move |inner| inner.proxy(proxy)) | ||||||
|   | |||||||
| @@ -99,6 +99,7 @@ impl Error { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[allow(unused)] | ||||||
|     pub(crate) fn into_io(self) -> io::Error { |     pub(crate) fn into_io(self) -> io::Error { | ||||||
|         io::Error::new(io::ErrorKind::Other, self) |         io::Error::new(io::ErrorKind::Other, self) | ||||||
|     } |     } | ||||||
| @@ -214,11 +215,12 @@ pub(crate) fn url_bad_scheme(url: Url) -> Error { | |||||||
|  |  | ||||||
| // io::Error helpers | // io::Error helpers | ||||||
|  |  | ||||||
| #[cfg(feature = "blocking")] | #[allow(unused)] | ||||||
| pub(crate) fn into_io(e: Error) -> io::Error { | pub(crate) fn into_io(e: Error) -> io::Error { | ||||||
|     e.into_io() |     e.into_io() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[allow(unused)] | ||||||
| pub(crate) fn decode_io(e: io::Error) -> Error { | pub(crate) fn decode_io(e: io::Error) -> Error { | ||||||
|     if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) { |     if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) { | ||||||
|         *e.into_inner() |         *e.into_inner() | ||||||
|   | |||||||
| @@ -158,6 +158,7 @@ | |||||||
| //! - **rustls-tls**: Provides TLS support via the `rustls` library. | //! - **rustls-tls**: Provides TLS support via the `rustls` library. | ||||||
| //! - **blocking**: Provides the [blocking][] client API. | //! - **blocking**: Provides the [blocking][] client API. | ||||||
| //! - **cookies**: Provides cookie session support. | //! - **cookies**: Provides cookie session support. | ||||||
|  | //! - **gzip**: Provides response body gzip decompression. | ||||||
| //! | //! | ||||||
| //! | //! | ||||||
| //! [hyper]: http://hyper.rs | //! [hyper]: http://hyper.rs | ||||||
|   | |||||||
| @@ -11,7 +11,9 @@ async fn auto_headers() { | |||||||
|  |  | ||||||
|             assert_eq!(req.headers()["accept"], "*/*"); |             assert_eq!(req.headers()["accept"], "*/*"); | ||||||
|             assert_eq!(req.headers()["user-agent"], DEFAULT_USER_AGENT); |             assert_eq!(req.headers()["user-agent"], DEFAULT_USER_AGENT); | ||||||
|  |             if cfg!(feature = "gzip") { | ||||||
|                 assert_eq!(req.headers()["accept-encoding"], "gzip"); |                 assert_eq!(req.headers()["accept-encoding"], "gzip"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             http::Response::default() |             http::Response::default() | ||||||
|         } |         } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user