Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add http2 feature #2162

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -103,9 +105,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"
Expand Down
87 changes: 61 additions & 26 deletions src/async_impl/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -80,6 +80,7 @@ pub struct ClientBuilder {

enum HttpVersionPref {
Http1,
#[cfg(feature = "http2")]
Http2,
#[cfg(feature = "http3")]
Http3,
Expand Down Expand Up @@ -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<u32>,
#[cfg(feature = "http2")]
http2_initial_connection_window_size: Option<u32>,
#[cfg(feature = "http2")]
http2_adaptive_window: bool,
#[cfg(feature = "http2")]
http2_max_frame_size: Option<u32>,
#[cfg(feature = "http2")]
http2_keep_alive_interval: Option<Duration>,
#[cfg(feature = "http2")]
http2_keep_alive_timeout: Option<Duration>,
#[cfg(feature = "http2")]
http2_keep_alive_while_idle: bool,
local_address: Option<IpAddr>,
nodelay: bool,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -349,6 +364,7 @@ impl ClientBuilder {
HttpVersionPref::Http1 => {
tls.request_alpns(&["http/1.1"]);
}
#[cfg(feature = "http2")]
HttpVersionPref::Http2 => {
tls.request_alpns(&["h2"]);
}
Expand Down Expand Up @@ -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()];
}
Expand Down Expand Up @@ -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"))]
Expand Down Expand Up @@ -1089,6 +1110,7 @@ impl ClientBuilder {
}

/// Only use HTTP/2.
#[cfg(feature = "http2")]
pub fn http2_prior_knowledge(mut self) -> ClientBuilder {
self.config.http_version_pref = HttpVersionPref::Http2;
self
Expand All @@ -1105,6 +1127,7 @@ 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")]
pub fn http2_initial_stream_window_size(mut self, sz: impl Into<Option<u32>>) -> ClientBuilder {
self.config.http2_initial_stream_window_size = sz.into();
self
Expand All @@ -1113,6 +1136,7 @@ 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")]
pub fn http2_initial_connection_window_size(
mut self,
sz: impl Into<Option<u32>>,
Expand All @@ -1125,6 +1149,7 @@ impl ClientBuilder {
///
/// Enabling this will override the limits set in `http2_initial_stream_window_size` and
/// `http2_initial_connection_window_size`.
#[cfg(feature = "http2")]
pub fn http2_adaptive_window(mut self, enabled: bool) -> ClientBuilder {
self.config.http2_adaptive_window = enabled;
self
Expand All @@ -1133,6 +1158,7 @@ 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")]
pub fn http2_max_frame_size(mut self, sz: impl Into<Option<u32>>) -> ClientBuilder {
self.config.http2_max_frame_size = sz.into();
self
Expand All @@ -1142,6 +1168,7 @@ impl ClientBuilder {
///
/// Pass `None` to disable HTTP2 keep-alive.
/// Default is currently disabled.
#[cfg(feature = "http2")]
pub fn http2_keep_alive_interval(
mut self,
interval: impl Into<Option<Duration>>,
Expand All @@ -1155,6 +1182,7 @@ 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")]
pub fn http2_keep_alive_timeout(mut self, timeout: Duration) -> ClientBuilder {
self.config.http2_keep_alive_timeout = Some(timeout);
self
Expand All @@ -1166,6 +1194,7 @@ 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")]
pub fn http2_keep_alive_while_idle(mut self, enabled: bool) -> ClientBuilder {
self.config.http2_keep_alive_while_idle = enabled;
self
Expand Down Expand Up @@ -1973,6 +2002,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);
}
Expand Down Expand Up @@ -2142,7 +2172,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;
}
Expand Down Expand Up @@ -2199,6 +2232,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() {
Expand Down Expand Up @@ -2276,6 +2310,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;
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,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`.
Expand Down
5 changes: 4 additions & 1 deletion tests/client.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#![cfg(not(target_arch = "wasm32"))]
mod support;

use support::delay_server;
use support::server;

#[cfg(feature = "json")]
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down