diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 68aaf78..6e3f713 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -60,6 +60,7 @@ jobs:
- "feat.: cookies"
- "feat.: blocking"
- "feat.: gzip"
+ - "feat.: brotli"
- "feat.: json"
- "feat.: stream"
- "feat.: socks/default-tls"
@@ -82,21 +83,21 @@ jobs:
- name: windows / stable-x86_64-msvc
os: windows-latest
target: x86_64-pc-windows-msvc
- features: "--features blocking,gzip,json"
+ features: "--features blocking,gzip,brotli,json"
- name: windows / stable-i686-msvc
os: windows-latest
target: i686-pc-windows-msvc
- features: "--features blocking,gzip,json"
+ features: "--features blocking,gzip,brotli,json"
- name: windows / stable-x86_64-gnu
os: windows-latest
rust: stable-x86_64-pc-windows-gnu
target: x86_64-pc-windows-gnu
- features: "--features blocking,gzip,json"
+ features: "--features blocking,gzip,brotli,json"
- name: windows / stable-i686-gnu
os: windows-latest
rust: stable-i686-pc-windows-gnu
target: i686-pc-windows-gnu
- features: "--features blocking,gzip,json"
+ features: "--features blocking,gzip,brotli,json"
- name: "feat.: default-tls disabled"
features: "--no-default-features"
@@ -112,6 +113,8 @@ jobs:
features: "--features blocking"
- name: "feat.: gzip"
features: "--features gzip"
+ - name: "feat.: brotli"
+ features: "--features brotli"
- name: "feat.: json"
features: "--features json"
- name: "feat.: stream"
diff --git a/Cargo.toml b/Cargo.toml
index 43d9169..3e747db 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -39,7 +39,9 @@ blocking = ["futures-util/io", "tokio/rt-threaded", "tokio/rt-core", "tokio/sync
cookies = ["cookie_crate", "cookie_store"]
-gzip = ["async-compression"]
+gzip = ["async-compression", "async-compression/gzip"]
+
+brotli = ["async-compression", "async-compression/brotli"]
json = ["serde_json"]
@@ -104,8 +106,8 @@ webpki-roots = { version = "0.17", optional = true }
cookie_crate = { version = "0.12", package = "cookie", optional = true }
cookie_store = { version = "0.10", optional = true }
-## gzip
-async-compression = { version = "0.2.0", default-features = false, features = ["gzip", "stream"], optional = true }
+## compression
+async-compression = { version = "0.3.0", default-features = false, features = ["stream"], optional = true }
## socks
@@ -119,6 +121,7 @@ env_logger = "0.6"
hyper = { version = "0.13", default-features = false, features = ["tcp", "stream"] }
serde = { version = "1.0", features = ["derive"] }
libflate = "0.1"
+brotli_crate = { package = "brotli", version = "3.3.0" }
doc-comment = "0.3"
tokio = { version = "0.2.0", default-features = false, features = ["macros"] }
@@ -177,3 +180,8 @@ required-features = ["cookies"]
name = "gzip"
path = "tests/gzip.rs"
required-features = ["gzip"]
+
+[[test]]
+name = "brotli"
+path = "tests/brotli.rs"
+required-features = ["brotli"]
\ No newline at end of file
diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs
index 231953e..0552537 100644
--- a/src/async_impl/client.rs
+++ b/src/async_impl/client.rs
@@ -11,8 +11,8 @@ use http::header::{
Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT,
};
-use http::Uri;
use http::uri::Scheme;
+use http::Uri;
use hyper::client::ResponseFuture;
#[cfg(feature = "native-tls-crate")]
use native_tls_crate::TlsConnector;
@@ -58,6 +58,7 @@ pub struct ClientBuilder {
struct Config {
// NOTE: When adding a new field, update `fmt::Debug for ClientBuilder`
gzip: bool,
+ brotli: bool,
headers: HeaderMap,
#[cfg(feature = "native-tls")]
hostname_verification: bool,
@@ -106,6 +107,7 @@ impl ClientBuilder {
config: Config {
error: None,
gzip: cfg!(feature = "gzip"),
+ brotli: cfg!(feature = "brotli"),
headers,
#[cfg(feature = "native-tls")]
hostname_verification: true,
@@ -179,7 +181,6 @@ impl ClientBuilder {
cert.add_to_native_tls(&mut tls);
}
-
#[cfg(feature = "native-tls")]
{
if let Some(id) = config.identity {
@@ -246,7 +247,9 @@ impl ClientBuilder {
if let Some(http2_initial_stream_window_size) = config.http2_initial_stream_window_size {
builder.http2_initial_stream_window_size(http2_initial_stream_window_size);
}
- if let Some(http2_initial_connection_window_size) = config.http2_initial_connection_window_size {
+ if let Some(http2_initial_connection_window_size) =
+ config.http2_initial_connection_window_size
+ {
builder.http2_initial_connection_window_size(http2_initial_connection_window_size);
}
@@ -265,6 +268,7 @@ impl ClientBuilder {
#[cfg(feature = "cookies")]
cookie_store: config.cookie_store.map(RwLock::new),
gzip: config.gzip,
+ brotli: config.brotli,
hyper: hyper_client,
headers: config.headers,
redirect_policy: config.redirect_policy,
@@ -278,7 +282,6 @@ impl ClientBuilder {
// Higher-level options
-
/// Sets the `User-Agent` header to be used by this client.
///
/// # Example
@@ -360,7 +363,6 @@ impl ClientBuilder {
self
}
-
/// Enable a persistent cookie store for the client.
///
/// Cookies received in responses will be preserved and included in
@@ -383,7 +385,7 @@ impl ClientBuilder {
/// Enable auto gzip decompression by checking the `Content-Encoding` response header.
///
- /// If auto gzip decompresson is turned on:
+ /// If auto gzip decompression is turned on:
///
/// - 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`.
@@ -403,6 +405,28 @@ impl ClientBuilder {
self
}
+ /// Enable auto brotli decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto brotli decompression is turned on:
+ ///
+ /// - 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 `br`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if it's headers contain a `Content-Encoding` value that
+ /// equals to `br`, both values `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `brotli` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `brotli` feature to be enabled
+ #[cfg(feature = "brotli")]
+ pub fn brotli(mut self, enable: bool) -> ClientBuilder {
+ self.config.brotli = enable;
+ self
+ }
+
/// Disable auto response body gzip decompression.
///
/// This method exists even if the optional `gzip` feature is not enabled.
@@ -420,6 +444,23 @@ impl ClientBuilder {
}
}
+ /// Disable auto response body brotli decompression.
+ ///
+ /// This method exists even if the optional `brotli` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use brotli decompression
+ /// even if another dependency were to enable the optional `brotli` feature.
+ pub fn no_brotli(self) -> ClientBuilder {
+ #[cfg(feature = "brotli")]
+ {
+ self.brotli(false)
+ }
+
+ #[cfg(not(feature = "brotli"))]
+ {
+ self
+ }
+ }
+
// Redirect options
/// Set a `RedirectPolicy` for this client.
@@ -534,7 +575,10 @@ impl ClientBuilder {
/// Sets the max connection-level flow control for HTTP2
///
/// Default is currently 65,535 but may change internally to optimize for common uses.
- pub fn http2_initial_connection_window_size(mut self, sz: impl Into