diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab38d8462..aabd79d16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,7 @@ jobs: - "feat.: default-tls and rustls-tls" - "feat.: cookies" - "feat.: blocking" + - "feat.: blocking only" - "feat.: gzip" - "feat.: brotli" - "feat.: deflate" @@ -138,6 +139,8 @@ jobs: features: "--features cookies" - name: "feat.: blocking" features: "--features blocking" + - name: "feat.: blocking only" + features: "--no-default-features --features blocking" - name: "feat.: gzip" features: "--features gzip,stream" - name: "feat.: brotli" diff --git a/Cargo.toml b/Cargo.toml index 240c28d61..7a88a2cc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,12 +27,14 @@ features = [ ] [features] -default = ["default-tls"] +default = ["default-tls", "http2"] # Note: this doesn't enable the 'native-tls' feature, which adds specific # functionality for it. default-tls = ["hyper-tls", "native-tls-crate", "__tls", "tokio-native-tls"] +http2 = ["h2", "hyper/http2", "hyper-util/http2"] + # Enables native-tls specific functionality not available by default. native-tls = ["default-tls"] native-tls-alpn = ["native-tls", "native-tls-crate/alpn", "hyper-tls/alpn"] @@ -105,9 +107,9 @@ mime_guess = { version = "2.0", default-features = false, optional = true } encoding_rs = "0.8" http-body = "1" http-body-util = "0.1" -hyper = { version = "1", features = ["http1", "http2", "client"] } -hyper-util = { version = "0.1.3", features = ["http1", "http2", "client", "client-legacy", "tokio"] } -h2 = "0.4" +hyper = { version = "1", features = ["http1", "client"] } +hyper-util = { version = "0.1.3", features = ["http1", "client", "client-legacy", "tokio"] } +h2 = { version = "0.4", optional = true } once_cell = "1" log = "0.4" mime = "0.3.16" diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 2011d7e3a..244fece38 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -46,7 +46,7 @@ use crate::Certificate; #[cfg(any(feature = "native-tls", feature = "__rustls"))] use crate::Identity; use crate::{IntoUrl, Method, Proxy, StatusCode, Url}; -use log::{debug, trace}; +use log::debug; #[cfg(feature = "http3")] use quinn::TransportConfig; #[cfg(feature = "http3")] @@ -80,6 +80,7 @@ pub struct ClientBuilder { enum HttpVersionPref { Http1, + #[cfg(feature = "http2")] Http2, #[cfg(feature = "http3")] Http3, @@ -126,12 +127,19 @@ struct Config { http1_allow_obsolete_multiline_headers_in_responses: bool, http1_ignore_invalid_headers_in_responses: bool, http1_allow_spaces_after_header_name_in_responses: bool, + #[cfg(feature = "http2")] http2_initial_stream_window_size: Option, + #[cfg(feature = "http2")] http2_initial_connection_window_size: Option, + #[cfg(feature = "http2")] http2_adaptive_window: bool, + #[cfg(feature = "http2")] http2_max_frame_size: Option, + #[cfg(feature = "http2")] http2_keep_alive_interval: Option, + #[cfg(feature = "http2")] http2_keep_alive_timeout: Option, + #[cfg(feature = "http2")] http2_keep_alive_while_idle: bool, local_address: Option, nodelay: bool, @@ -211,12 +219,19 @@ impl ClientBuilder { http1_allow_obsolete_multiline_headers_in_responses: false, http1_ignore_invalid_headers_in_responses: false, http1_allow_spaces_after_header_name_in_responses: false, + #[cfg(feature = "http2")] http2_initial_stream_window_size: None, + #[cfg(feature = "http2")] http2_initial_connection_window_size: None, + #[cfg(feature = "http2")] http2_adaptive_window: false, + #[cfg(feature = "http2")] http2_max_frame_size: None, + #[cfg(feature = "http2")] http2_keep_alive_interval: None, + #[cfg(feature = "http2")] http2_keep_alive_timeout: None, + #[cfg(feature = "http2")] http2_keep_alive_while_idle: false, local_address: None, nodelay: true, @@ -349,6 +364,7 @@ impl ClientBuilder { HttpVersionPref::Http1 => { tls.request_alpns(&["http/1.1"]); } + #[cfg(feature = "http2")] HttpVersionPref::Http2 => { tls.request_alpns(&["h2"]); } @@ -541,6 +557,7 @@ impl ClientBuilder { HttpVersionPref::Http1 => { tls.alpn_protocols = vec!["http/1.1".into()]; } + #[cfg(feature = "http2")] HttpVersionPref::Http2 => { tls.alpn_protocols = vec!["h2".into()]; } @@ -596,32 +613,36 @@ impl ClientBuilder { let mut builder = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()); - if matches!(config.http_version_pref, HttpVersionPref::Http2) { - builder.http2_only(true); - } - - 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 + #[cfg(feature = "http2")] { - builder.http2_initial_connection_window_size(http2_initial_connection_window_size); - } - if config.http2_adaptive_window { - builder.http2_adaptive_window(true); - } - if let Some(http2_max_frame_size) = config.http2_max_frame_size { - builder.http2_max_frame_size(http2_max_frame_size); - } - if let Some(http2_keep_alive_interval) = config.http2_keep_alive_interval { - builder.http2_keep_alive_interval(http2_keep_alive_interval); - } - if let Some(http2_keep_alive_timeout) = config.http2_keep_alive_timeout { - builder.http2_keep_alive_timeout(http2_keep_alive_timeout); - } - if config.http2_keep_alive_while_idle { - builder.http2_keep_alive_while_idle(true); + if matches!(config.http_version_pref, HttpVersionPref::Http2) { + builder.http2_only(true); + } + + 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 + { + builder.http2_initial_connection_window_size(http2_initial_connection_window_size); + } + if config.http2_adaptive_window { + builder.http2_adaptive_window(true); + } + if let Some(http2_max_frame_size) = config.http2_max_frame_size { + builder.http2_max_frame_size(http2_max_frame_size); + } + if let Some(http2_keep_alive_interval) = config.http2_keep_alive_interval { + builder.http2_keep_alive_interval(http2_keep_alive_interval); + } + if let Some(http2_keep_alive_timeout) = config.http2_keep_alive_timeout { + builder.http2_keep_alive_timeout(http2_keep_alive_timeout); + } + if config.http2_keep_alive_while_idle { + builder.http2_keep_alive_while_idle(true); + } } #[cfg(not(target_arch = "wasm32"))] @@ -1089,6 +1110,8 @@ impl ClientBuilder { } /// Only use HTTP/2. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_prior_knowledge(mut self) -> ClientBuilder { self.config.http_version_pref = HttpVersionPref::Http2; self @@ -1105,6 +1128,8 @@ impl ClientBuilder { /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP2 stream-level flow control. /// /// Default is currently 65,535 but may change internally to optimize for common uses. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_initial_stream_window_size(mut self, sz: impl Into>) -> ClientBuilder { self.config.http2_initial_stream_window_size = sz.into(); self @@ -1113,6 +1138,8 @@ 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. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_initial_connection_window_size( mut self, sz: impl Into>, @@ -1125,6 +1152,8 @@ impl ClientBuilder { /// /// Enabling this will override the limits set in `http2_initial_stream_window_size` and /// `http2_initial_connection_window_size`. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_adaptive_window(mut self, enabled: bool) -> ClientBuilder { self.config.http2_adaptive_window = enabled; self @@ -1133,6 +1162,8 @@ impl ClientBuilder { /// Sets the maximum frame size to use for HTTP2. /// /// Default is currently 16,384 but may change internally to optimize for common uses. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_max_frame_size(mut self, sz: impl Into>) -> ClientBuilder { self.config.http2_max_frame_size = sz.into(); self @@ -1142,6 +1173,8 @@ impl ClientBuilder { /// /// Pass `None` to disable HTTP2 keep-alive. /// Default is currently disabled. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_keep_alive_interval( mut self, interval: impl Into>, @@ -1155,6 +1188,8 @@ impl ClientBuilder { /// If the ping is not acknowledged within the timeout, the connection will be closed. /// Does nothing if `http2_keep_alive_interval` is disabled. /// Default is currently disabled. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_keep_alive_timeout(mut self, timeout: Duration) -> ClientBuilder { self.config.http2_keep_alive_timeout = Some(timeout); self @@ -1166,6 +1201,8 @@ impl ClientBuilder { /// If enabled, pings are also sent when no streams are active. /// Does nothing if `http2_keep_alive_interval` is disabled. /// Default is `false`. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_keep_alive_while_idle(mut self, enabled: bool) -> ClientBuilder { self.config.http2_keep_alive_while_idle = enabled; self @@ -2008,6 +2045,7 @@ impl Config { f.field("http1_only", &true); } + #[cfg(feature = "http2")] if matches!(self.http_version_pref, HttpVersionPref::Http2) { f.field("http2_prior_knowledge", &true); } @@ -2177,7 +2215,10 @@ impl PendingRequest { self.project().headers } + #[cfg(feature = "http2")] fn retry_error(mut self: Pin<&mut Self>, err: &(dyn std::error::Error + 'static)) -> bool { + use log::trace; + if !is_retryable_error(err) { return false; } @@ -2234,6 +2275,7 @@ impl PendingRequest { } } +#[cfg(feature = "http2")] fn is_retryable_error(err: &(dyn std::error::Error + 'static)) -> bool { // pop the legacy::Error let err = if let Some(err) = err.source() { @@ -2311,6 +2353,7 @@ impl Future for PendingRequest { let res = match self.as_mut().in_flight().get_mut() { ResponseFuture::Default(r) => match Pin::new(r).poll(cx) { Poll::Ready(Err(e)) => { + #[cfg(feature = "http2")] if self.as_mut().retry_error(&e) { continue; } diff --git a/src/blocking/client.rs b/src/blocking/client.rs index 80740ab67..7b2816ba7 100644 --- a/src/blocking/client.rs +++ b/src/blocking/client.rs @@ -445,6 +445,8 @@ impl ClientBuilder { } /// Only use HTTP/2. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_prior_knowledge(self) -> ClientBuilder { self.with_inner(|inner| inner.http2_prior_knowledge()) } @@ -452,6 +454,8 @@ impl ClientBuilder { /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP2 stream-level flow control. /// /// Default is currently 65,535 but may change internally to optimize for common uses. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_initial_stream_window_size(self, sz: impl Into>) -> ClientBuilder { self.with_inner(|inner| inner.http2_initial_stream_window_size(sz)) } @@ -459,6 +463,8 @@ 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. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_initial_connection_window_size(self, sz: impl Into>) -> ClientBuilder { self.with_inner(|inner| inner.http2_initial_connection_window_size(sz)) } @@ -467,6 +473,8 @@ impl ClientBuilder { /// /// Enabling this will override the limits set in `http2_initial_stream_window_size` and /// `http2_initial_connection_window_size`. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_adaptive_window(self, enabled: bool) -> ClientBuilder { self.with_inner(|inner| inner.http2_adaptive_window(enabled)) } @@ -474,6 +482,8 @@ impl ClientBuilder { /// Sets the maximum frame size to use for HTTP2. /// /// Default is currently 16,384 but may change internally to optimize for common uses. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn http2_max_frame_size(self, sz: impl Into>) -> ClientBuilder { self.with_inner(|inner| inner.http2_max_frame_size(sz)) } diff --git a/src/lib.rs b/src/lib.rs index 25965b181..2342fb948 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -178,6 +178,7 @@ //! The following are a list of [Cargo features][cargo-features] that can be //! enabled or disabled: //! +//! - **http2** *(enabled by default)*: Enables HTTP/2 support. //! - **default-tls** *(enabled by default)*: Provides TLS support to connect //! over HTTPS. //! - **native-tls**: Enables TLS functionality provided by `native-tls`. diff --git a/tests/blocking.rs b/tests/blocking.rs index 314b3e504..8a59250bf 100644 --- a/tests/blocking.rs +++ b/tests/blocking.rs @@ -368,6 +368,7 @@ fn blocking_update_json_content_type_if_set_manually() { } #[test] +#[cfg(feature = "__tls")] fn test_response_no_tls_info_for_http() { let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); diff --git a/tests/client.rs b/tests/client.rs index af1faecfd..02f32fc85 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -1,7 +1,6 @@ #![cfg(not(target_arch = "wasm32"))] mod support; -use support::delay_server; use support::server; #[cfg(feature = "json")] @@ -442,6 +441,7 @@ async fn test_tls_info() { // fail, because the only thread would block until `panic_rx` receives a // notification while the client needs to be driven to get the graceful shutdown // done. +#[cfg(feature = "http2")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn highly_concurrent_requests_to_http2_server_with_low_max_concurrent_streams() { let client = reqwest::Client::builder() @@ -472,8 +472,11 @@ async fn highly_concurrent_requests_to_http2_server_with_low_max_concurrent_stre futures_util::future::join_all(futs).await; } +#[cfg(feature = "http2")] #[tokio::test] async fn highly_concurrent_requests_to_slow_http2_server_with_low_max_concurrent_streams() { + use support::delay_server; + let client = reqwest::Client::builder() .http2_prior_knowledge() .build()