From 5ae6e6a44ee2df1c70fea7d457af4596eb86404a Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 18 Jul 2025 20:18:02 +0800 Subject: [PATCH 1/3] chore(core): format http1/http2 options wrapper --- examples/keylog.rs | 2 +- src/core/client/conn/http1.rs | 13 +++---- src/core/client/conn/http2.rs | 26 ++++++-------- src/core/client/mod.rs | 8 ++--- src/core/client/options/http2.rs | 58 +++++++++++++++++--------------- 5 files changed, 52 insertions(+), 55 deletions(-) diff --git a/examples/keylog.rs b/examples/keylog.rs index 9592cdd40..8c0857a58 100644 --- a/examples/keylog.rs +++ b/examples/keylog.rs @@ -11,6 +11,6 @@ async fn main() -> wreq::Result<()> { // Use the API you're already familiar with let resp = client.get("https://api.ip.sb/ip").send().await?; println!("{}", resp.text().await?); - + Ok(()) } diff --git a/src/core/client/conn/http1.rs b/src/core/client/conn/http1.rs index 016c0d446..edbedc01b 100644 --- a/src/core/client/conn/http1.rs +++ b/src/core/client/conn/http1.rs @@ -89,7 +89,7 @@ where /// are subject to change at any time. #[derive(Clone, Debug)] pub struct Builder { - config: Http1Options, + opts: Http1Options, } // ===== impl SendRequest @@ -228,13 +228,14 @@ impl Builder { #[inline] pub fn new() -> Builder { Builder { - config: Default::default(), + opts: Default::default(), } } - pub fn config(&mut self, opts: Option) { - if let Some(config) = opts { - self.config = config; + /// Provide a options configuration for the HTTP/1 connection. + pub fn options(&mut self, opts: Option) { + if let Some(opts) = opts { + self.opts = opts; } } @@ -253,7 +254,7 @@ impl Builder { B::Data: Send, B::Error: Into, { - let opts = self.config.clone(); + let opts = self.opts.clone(); async move { trace!("client handshake HTTP/1"); diff --git a/src/core/client/conn/http2.rs b/src/core/client/conn/http2.rs index 74268828c..5e9c93d0e 100644 --- a/src/core/client/conn/http2.rs +++ b/src/core/client/conn/http2.rs @@ -62,9 +62,9 @@ where /// are subject to change at any time. #[derive(Clone, Debug)] pub struct Builder { - pub(super) exec: Ex, - pub(super) timer: Time, - config: Http2Options, + exec: Ex, + timer: Time, + opts: Http2Options, } // ===== impl SendRequest @@ -194,7 +194,7 @@ where Builder { exec, timer: Time::Empty, - config: Default::default(), + opts: Default::default(), } } @@ -206,10 +206,10 @@ where self.timer = Time::Timer(Arc::new(timer)); } - /// Provide a configuration for HTTP/2. - pub fn config(&mut self, opts: Option) { - if let Some(config) = opts { - self.config = config; + /// Provide a options configuration for the HTTP/2 connection. + pub fn options(&mut self, opts: Option) { + if let Some(opts) = opts { + self.opts = opts; } } @@ -235,14 +235,8 @@ where trace!("client handshake HTTP/2"); let (tx, rx) = dispatch::channel(); - let h2 = proto::h2::client::handshake( - io, - rx, - &opts.config.h2_builder, - opts.exec, - opts.timer, - ) - .await?; + let h2 = proto::h2::client::handshake(io, rx, &opts.opts.config, opts.exec, opts.timer) + .await?; Ok(( SendRequest { dispatch: tx.unbound(), diff --git a/src/core/client/mod.rs b/src/core/client/mod.rs index c29376a7a..24d42cfed 100644 --- a/src/core/client/mod.rs +++ b/src/core/client/mod.rs @@ -295,8 +295,8 @@ where if let Some(opts) = transport_options { let (tls, http1, http2) = opts.into_parts(); tls_options = tls; - this.h1_builder.config(http1); - this.h2_builder.config(http2); + this.h1_builder.options(http1); + this.h2_builder.options(http2); } let conn_req = ConnRequest { @@ -1131,13 +1131,13 @@ impl Builder { /// Provide a configuration for HTTP/1. pub fn http1_options(&mut self, opts: Option) -> &mut Self { - self.h1_builder.config(opts); + self.h1_builder.options(opts); self } /// Provide a configuration for HTTP/2. pub fn http2_options(&mut self, opts: Option) -> &mut Self { - self.h2_builder.config(opts); + self.h2_builder.options(opts); self } diff --git a/src/core/client/options/http2.rs b/src/core/client/options/http2.rs index 6f081e1fe..bb61b59c5 100644 --- a/src/core/client/options/http2.rs +++ b/src/core/client/options/http2.rs @@ -15,7 +15,7 @@ use crate::core::proto::{ #[must_use] #[derive(Debug)] pub struct Http2OptionsBuilder { - config: Http2Options, + config: Config, } /// Configuration config for an HTTP/2 connection. @@ -24,7 +24,7 @@ pub struct Http2OptionsBuilder { /// including stream management, window sizes, frame limits, and header config. #[derive(Debug, Clone, Default)] pub struct Http2Options { - pub(crate) h2_builder: Config, + pub(crate) config: Config, } impl Http2OptionsBuilder { @@ -38,8 +38,8 @@ impl Http2OptionsBuilder { /// [spec]: https://httpwg.org/specs/rfc9113.html#SETTINGS_INITIAL_WINDOW_SIZE pub fn initial_stream_window_size(mut self, sz: impl Into>) -> Self { if let Some(sz) = sz.into() { - self.config.h2_builder.adaptive_window = false; - self.config.h2_builder.initial_stream_window_size = sz; + self.config.adaptive_window = false; + self.config.initial_stream_window_size = sz; } self } @@ -51,8 +51,8 @@ impl Http2OptionsBuilder { /// If not set, crate::core: will use a default. pub fn initial_connection_window_size(mut self, sz: impl Into>) -> Self { if let Some(sz) = sz.into() { - self.config.h2_builder.adaptive_window = false; - self.config.h2_builder.initial_conn_window_size = sz; + self.config.adaptive_window = false; + self.config.initial_conn_window_size = sz; } self } @@ -69,14 +69,14 @@ impl Http2OptionsBuilder { /// [connection preface]: https://httpwg.org/specs/rfc9113.html#preface pub fn initial_max_send_streams(mut self, initial: impl Into>) -> Self { if let Some(initial) = initial.into() { - self.config.h2_builder.initial_max_send_streams = initial; + self.config.initial_max_send_streams = initial; } self } /// Sets the initial stream id for the connection. pub fn initial_stream_id(mut self, id: impl Into>) -> Self { - self.config.h2_builder.initial_stream_id = id.into(); + self.config.initial_stream_id = id.into(); self } @@ -88,10 +88,10 @@ impl Http2OptionsBuilder { pub fn adaptive_window(mut self, enabled: bool) -> Self { use proto::h2::SPEC_WINDOW_SIZE; - self.config.h2_builder.adaptive_window = enabled; + self.config.adaptive_window = enabled; if enabled { - self.config.h2_builder.initial_conn_window_size = SPEC_WINDOW_SIZE; - self.config.h2_builder.initial_stream_window_size = SPEC_WINDOW_SIZE; + self.config.initial_conn_window_size = SPEC_WINDOW_SIZE; + self.config.initial_stream_window_size = SPEC_WINDOW_SIZE; } self } @@ -100,7 +100,7 @@ impl Http2OptionsBuilder { /// /// Default is currently 16KB, but can change. pub fn max_frame_size(mut self, sz: impl Into>) -> Self { - self.config.h2_builder.max_frame_size = sz.into(); + self.config.max_frame_size = sz.into(); self } @@ -108,7 +108,7 @@ impl Http2OptionsBuilder { /// /// Default is currently 16KB, but can change. pub fn max_header_list_size(mut self, max: u32) -> Self { - self.config.h2_builder.max_header_list_size = Some(max); + self.config.max_header_list_size = Some(max); self } @@ -120,7 +120,7 @@ impl Http2OptionsBuilder { /// /// The default value of crate `h2` is 4,096. pub fn header_table_size(mut self, size: impl Into>) -> Self { - self.config.h2_builder.header_table_size = size.into(); + self.config.header_table_size = size.into(); self } @@ -148,7 +148,7 @@ impl Http2OptionsBuilder { /// /// [Section 5.1.2]: https://http2.github.io/http2-spec/#rfc.section.5.1.2 pub fn max_concurrent_streams(mut self, max: impl Into>) -> Self { - self.config.h2_builder.max_concurrent_streams = max.into(); + self.config.max_concurrent_streams = max.into(); self } @@ -156,20 +156,20 @@ impl Http2OptionsBuilder { /// /// Passing `None` will do nothing. pub fn enable_push(mut self, opt: bool) -> Self { - self.config.h2_builder.enable_push = Some(opt); + self.config.enable_push = Some(opt); self } /// Sets the enable connect protocol. pub fn enable_connect_protocol(mut self, opt: bool) -> Self { - self.config.h2_builder.enable_connect_protocol = Some(opt); + self.config.enable_connect_protocol = Some(opt); self } /// Disable RFC 7540 Stream Priorities (set to `true` to disable). /// [RFC 9218]: pub fn no_rfc7540_priorities(mut self, opt: bool) -> Self { - self.config.h2_builder.no_rfc7540_priorities = Some(opt); + self.config.no_rfc7540_priorities = Some(opt); self } @@ -182,7 +182,7 @@ impl Http2OptionsBuilder { /// /// [`http2::client::Builder::max_concurrent_reset_streams`]: https://docs.rs/h2/client/struct.Builder.html#method.max_concurrent_reset_streams pub fn max_concurrent_reset_streams(mut self, max: usize) -> Self { - self.config.h2_builder.max_concurrent_reset_streams = Some(max); + self.config.max_concurrent_reset_streams = Some(max); self } @@ -195,7 +195,7 @@ impl Http2OptionsBuilder { /// The value must be no larger than `u32::MAX`. pub fn max_send_buf_size(mut self, max: usize) -> Self { assert!(max <= u32::MAX as usize); - self.config.h2_builder.max_send_buffer_size = max; + self.config.max_send_buffer_size = max; self } @@ -203,7 +203,7 @@ impl Http2OptionsBuilder { /// /// See for more information. pub fn max_pending_accept_reset_streams(mut self, max: impl Into>) -> Self { - self.config.h2_builder.max_pending_accept_reset_streams = max.into(); + self.config.max_pending_accept_reset_streams = max.into(); self } @@ -216,7 +216,7 @@ impl Http2OptionsBuilder { where T: Into>, { - self.config.h2_builder.headers_stream_dependency = stream_dependency.into(); + self.config.headers_stream_dependency = stream_dependency.into(); self } @@ -229,7 +229,7 @@ impl Http2OptionsBuilder { where T: Into>, { - self.config.h2_builder.headers_pseudo_order = headers_pseudo_order.into(); + self.config.headers_pseudo_order = headers_pseudo_order.into(); self } @@ -242,7 +242,7 @@ impl Http2OptionsBuilder { where T: Into>, { - self.config.h2_builder.experimental_settings = experimental_settings.into(); + self.config.experimental_settings = experimental_settings.into(); self } @@ -254,7 +254,7 @@ impl Http2OptionsBuilder { where T: Into>, { - self.config.h2_builder.settings_order = settings_order.into(); + self.config.settings_order = settings_order.into(); self } @@ -271,13 +271,15 @@ impl Http2OptionsBuilder { where T: Into>, { - self.config.h2_builder.priorities = priorities.into(); + self.config.priorities = priorities.into(); self } /// Builds the `Http2Options` instance. pub fn build(self) -> Http2Options { - self.config + Http2Options { + config: self.config, + } } } @@ -285,7 +287,7 @@ impl Http2Options { /// Creates a new `Http2OptionsBuilder` instance. pub fn builder() -> Http2OptionsBuilder { Http2OptionsBuilder { - config: Http2Options::default(), + config: Config::default(), } } } From 8bb9c84455764b0aeef0e552ae5ca9447e4d45bf Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 18 Jul 2025 20:20:50 +0800 Subject: [PATCH 2/3] chore(core): format `http1` and `http2` options wrappers --- src/core/client/conn/http2.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/client/conn/http2.rs b/src/core/client/conn/http2.rs index 5e9c93d0e..83fae5175 100644 --- a/src/core/client/conn/http2.rs +++ b/src/core/client/conn/http2.rs @@ -229,14 +229,20 @@ where B::Error: Into, Ex: Http2ClientConnExec + Unpin, { - let opts = self.clone(); + let builder = self.clone(); async move { trace!("client handshake HTTP/2"); let (tx, rx) = dispatch::channel(); - let h2 = proto::h2::client::handshake(io, rx, &opts.opts.config, opts.exec, opts.timer) - .await?; + let h2 = proto::h2::client::handshake( + io, + rx, + &builder.opts.config, + builder.exec, + builder.timer, + ) + .await?; Ok(( SendRequest { dispatch: tx.unbound(), From 7cb75b47711b932b04787517e05de270b86d05e6 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 18 Jul 2025 21:35:16 +0800 Subject: [PATCH 3/3] update --- examples/emulation_firefox.rs | 2 +- examples/emulation_twitter.rs | 2 +- examples/request_with_emulation.rs | 2 +- src/core/client/conn/http2.rs | 3 +- src/core/client/options/http1.rs | 28 ++--- src/core/client/options/http2.rs | 174 +++++++++++++++++++++++------ src/core/proto/h2/client.rs | 150 +------------------------ src/core/proto/h2/ping.rs | 24 +++- 8 files changed, 183 insertions(+), 202 deletions(-) diff --git a/examples/emulation_firefox.rs b/examples/emulation_firefox.rs index 970624346..1ed886f86 100644 --- a/examples/emulation_firefox.rs +++ b/examples/emulation_firefox.rs @@ -172,7 +172,7 @@ async fn main() -> wreq::Result<()> { Http2Options::builder() .initial_stream_id(15) .header_table_size(65536) - .initial_stream_window_size(131072) + .initial_window_size(131072) .max_frame_size(16384) .initial_connection_window_size(12517377 + 65535) .headers_stream_dependency(StreamDependency::new(StreamId::from(13), 41, false)) diff --git a/examples/emulation_twitter.rs b/examples/emulation_twitter.rs index efd68a1e0..62d424eea 100644 --- a/examples/emulation_twitter.rs +++ b/examples/emulation_twitter.rs @@ -53,7 +53,7 @@ async fn main() -> wreq::Result<()> { // HTTP/2 options config let http2 = Http2Options::builder() .initial_stream_id(3) - .initial_stream_window_size(16777216) + .initial_window_size(16777216) .initial_connection_window_size(16711681 + 65535) .headers_pseudo_order( PseudoOrder::builder() diff --git a/examples/request_with_emulation.rs b/examples/request_with_emulation.rs index 127b2d9f3..464d99454 100644 --- a/examples/request_with_emulation.rs +++ b/examples/request_with_emulation.rs @@ -53,7 +53,7 @@ async fn main() -> wreq::Result<()> { // HTTP/2 options config let http2 = Http2Options::builder() .initial_stream_id(3) - .initial_stream_window_size(16777216) + .initial_window_size(16777216) .initial_connection_window_size(16711681 + 65535) .headers_pseudo_order( PseudoOrder::builder() diff --git a/src/core/client/conn/http2.rs b/src/core/client/conn/http2.rs index 83fae5175..418a5cbcc 100644 --- a/src/core/client/conn/http2.rs +++ b/src/core/client/conn/http2.rs @@ -238,7 +238,8 @@ where let h2 = proto::h2::client::handshake( io, rx, - &builder.opts.config, + builder.opts.builder, + builder.opts.ping_config, builder.exec, builder.timer, ) diff --git a/src/core/client/options/http1.rs b/src/core/client/options/http1.rs index 3238d5324..f442dd6b6 100644 --- a/src/core/client/options/http1.rs +++ b/src/core/client/options/http1.rs @@ -8,7 +8,7 @@ use crate::core::proto; #[must_use] #[derive(Debug)] pub struct Http1OptionsBuilder { - config: Http1Options, + opts: Http1Options, } /// HTTP/1 protocol options for customizing connection behavior. @@ -29,7 +29,7 @@ pub struct Http1Options { impl Http1OptionsBuilder { /// Set the `http09_responses` field. pub fn http09_responses(mut self, enabled: bool) -> Self { - self.config.h09_responses = enabled; + self.opts.h09_responses = enabled; self } @@ -46,7 +46,7 @@ impl Http1OptionsBuilder { /// Default is `auto`. In this mode crate::core: will try to guess which /// mode to use pub fn writev(mut self, writev: Option) -> Self { - self.config.h1_writev = writev; + self.opts.h1_writev = writev; self } @@ -58,7 +58,7 @@ impl Http1OptionsBuilder { /// /// Default is false. pub fn preserve_header_case(mut self, preserve_header_case: bool) -> Self { - self.config.h1_preserve_header_case = preserve_header_case; + self.opts.h1_preserve_header_case = preserve_header_case; self } @@ -76,7 +76,7 @@ impl Http1OptionsBuilder { /// /// Default is 100. pub fn max_headers(mut self, max_headers: usize) -> Self { - self.config.h1_max_headers = Some(max_headers); + self.opts.h1_max_headers = Some(max_headers); self } @@ -86,8 +86,8 @@ impl Http1OptionsBuilder { /// /// Default is an adaptive read buffer. pub fn read_buf_exact_size(mut self, sz: Option) -> Self { - self.config.h1_read_buf_exact_size = sz; - self.config.h1_max_buf_size = None; + self.opts.h1_read_buf_exact_size = sz; + self.opts.h1_max_buf_size = None; self } @@ -107,8 +107,8 @@ impl Http1OptionsBuilder { "the max_buf_size cannot be smaller than the minimum that h1 specifies." ); - self.config.h1_max_buf_size = Some(max); - self.config.h1_read_buf_exact_size = None; + self.opts.h1_max_buf_size = Some(max); + self.opts.h1_read_buf_exact_size = None; self } @@ -130,7 +130,7 @@ impl Http1OptionsBuilder { /// /// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4 pub fn allow_spaces_after_header_name_in_responses(mut self, enabled: bool) -> Self { - self.config + self.opts .h1_parser_config .allow_spaces_after_header_name_in_responses(enabled); self @@ -144,7 +144,7 @@ impl Http1OptionsBuilder { /// /// Default is false. pub fn ignore_invalid_headers_in_responses(mut self, enabled: bool) -> Self { - self.config + self.opts .h1_parser_config .ignore_invalid_headers_in_responses(enabled); self @@ -155,7 +155,7 @@ impl Http1OptionsBuilder { mut self, allow_obsolete_multiline_headers_in_responses: bool, ) -> Self { - self.config + self.opts .h1_parser_config .allow_obsolete_multiline_headers_in_responses( allow_obsolete_multiline_headers_in_responses, @@ -165,7 +165,7 @@ impl Http1OptionsBuilder { /// Build the `Http1Options` instance. pub fn build(self) -> Http1Options { - self.config + self.opts } } @@ -173,7 +173,7 @@ impl Http1Options { /// Create a new `Http1OptionsBuilder`. pub fn builder() -> Http1OptionsBuilder { Http1OptionsBuilder { - config: Http1Options::default(), + opts: Http1Options::default(), } } } diff --git a/src/core/client/options/http2.rs b/src/core/client/options/http2.rs index bb61b59c5..87686802f 100644 --- a/src/core/client/options/http2.rs +++ b/src/core/client/options/http2.rs @@ -1,30 +1,51 @@ //! Re-export the `http2` module for HTTP/2 frame types and utilities. -use http2::frame::ExperimentalSettings; +use std::time::Duration; + pub use http2::frame::{ Priorities, PrioritiesBuilder, Priority, PseudoId, PseudoOrder, Setting, SettingId, SettingsOrder, SettingsOrderBuilder, StreamDependency, StreamId, }; +use http2::{client::Builder, frame::ExperimentalSettings}; -use crate::core::proto::{ - h2::client::Config, - {self}, -}; +use crate::core::proto::{self, h2::ping}; + +// Our defaults are chosen for the "majority" case, which usually are not +// resource constrained, and so the spec default of 64kb can be too limiting +// for performance. +const DEFAULT_CONN_WINDOW_SIZE: u32 = 1024 * 1024 * 5; // 5mb +const DEFAULT_WINDOW_SIZE: u32 = 1024 * 1024 * 2; // 2mb +const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 1024; // 1mb + +// The maximum number of concurrent streams that the client is allowed to open +// before it receives the initial SETTINGS frame from the server. +// This default value is derived from what the HTTP/2 spec recommends as the +// minimum value that endpoints advertise to their peers. It means that using +// this value will minimize the chance of the failure where the local endpoint +// attempts to open too many streams and gets rejected by the remote peer with +// the `REFUSED_STREAM` error. +const DEFAULT_INITIAL_MAX_SEND_STREAMS: usize = 100; /// Builder for `Http2Options`. #[must_use] #[derive(Debug)] pub struct Http2OptionsBuilder { - config: Config, + builder: Builder, + adaptive_window: bool, + initial_window_size: u32, + keep_alive_interval: Option, + keep_alive_timeout: Duration, + keep_alive_while_idle: bool, } /// Configuration config for an HTTP/2 connection. /// /// This struct defines various parameters to fine-tune the behavior of an HTTP/2 connection, /// including stream management, window sizes, frame limits, and header config. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct Http2Options { - pub(crate) config: Config, + pub(crate) builder: Builder, + pub(crate) ping_config: ping::Config, } impl Http2OptionsBuilder { @@ -36,10 +57,11 @@ impl Http2OptionsBuilder { /// If not set, crate::core: will use a default. /// /// [spec]: https://httpwg.org/specs/rfc9113.html#SETTINGS_INITIAL_WINDOW_SIZE - pub fn initial_stream_window_size(mut self, sz: impl Into>) -> Self { + pub fn initial_window_size(mut self, sz: impl Into>) -> Self { if let Some(sz) = sz.into() { - self.config.adaptive_window = false; - self.config.initial_stream_window_size = sz; + self.adaptive_window = false; + self.initial_window_size = sz; + self.builder.initial_window_size(sz); } self } @@ -51,8 +73,8 @@ impl Http2OptionsBuilder { /// If not set, crate::core: will use a default. pub fn initial_connection_window_size(mut self, sz: impl Into>) -> Self { if let Some(sz) = sz.into() { - self.config.adaptive_window = false; - self.config.initial_conn_window_size = sz; + self.adaptive_window = false; + self.builder.initial_connection_window_size(sz); } self } @@ -69,14 +91,16 @@ impl Http2OptionsBuilder { /// [connection preface]: https://httpwg.org/specs/rfc9113.html#preface pub fn initial_max_send_streams(mut self, initial: impl Into>) -> Self { if let Some(initial) = initial.into() { - self.config.initial_max_send_streams = initial; + self.builder.initial_max_send_streams(initial); } self } /// Sets the initial stream id for the connection. pub fn initial_stream_id(mut self, id: impl Into>) -> Self { - self.config.initial_stream_id = id.into(); + if let Some(id) = id.into() { + self.builder.initial_stream_id(id); + } self } @@ -88,10 +112,12 @@ impl Http2OptionsBuilder { pub fn adaptive_window(mut self, enabled: bool) -> Self { use proto::h2::SPEC_WINDOW_SIZE; - self.config.adaptive_window = enabled; + self.adaptive_window = enabled; if enabled { - self.config.initial_conn_window_size = SPEC_WINDOW_SIZE; - self.config.initial_stream_window_size = SPEC_WINDOW_SIZE; + self.initial_window_size = SPEC_WINDOW_SIZE; + self.builder + .initial_window_size(SPEC_WINDOW_SIZE) + .initial_connection_window_size(SPEC_WINDOW_SIZE); } self } @@ -100,7 +126,9 @@ impl Http2OptionsBuilder { /// /// Default is currently 16KB, but can change. pub fn max_frame_size(mut self, sz: impl Into>) -> Self { - self.config.max_frame_size = sz.into(); + if let Some(sz) = sz.into() { + self.builder.max_frame_size(sz); + } self } @@ -108,7 +136,7 @@ impl Http2OptionsBuilder { /// /// Default is currently 16KB, but can change. pub fn max_header_list_size(mut self, max: u32) -> Self { - self.config.max_header_list_size = Some(max); + self.builder.max_header_list_size(max); self } @@ -120,7 +148,9 @@ impl Http2OptionsBuilder { /// /// The default value of crate `h2` is 4,096. pub fn header_table_size(mut self, size: impl Into>) -> Self { - self.config.header_table_size = size.into(); + if let Some(size) = size.into() { + self.builder.header_table_size(size); + } self } @@ -148,7 +178,44 @@ impl Http2OptionsBuilder { /// /// [Section 5.1.2]: https://http2.github.io/http2-spec/#rfc.section.5.1.2 pub fn max_concurrent_streams(mut self, max: impl Into>) -> Self { - self.config.max_concurrent_streams = max.into(); + if let Some(max) = max.into() { + self.builder.max_concurrent_streams(max); + } + self + } + + /// Sets an interval for HTTP2 Ping frames should be sent to keep a + /// connection alive. + /// + /// Pass `None` to disable HTTP2 keep-alive. + /// + /// Default is currently disabled. + pub fn keep_alive_interval(&mut self, interval: impl Into>) -> &mut Self { + self.keep_alive_interval = interval.into(); + self + } + + /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. + /// + /// If the ping is not acknowledged within the timeout, the connection will + /// be closed. Does nothing if `keep_alive_interval` is disabled. + /// + /// Default is 20 seconds. + pub fn keep_alive_timeout(&mut self, timeout: Duration) -> &mut Self { + self.keep_alive_timeout = timeout; + self + } + + /// Sets whether HTTP2 keep-alive should apply while the connection is idle. + /// + /// If disabled, keep-alive pings are only sent while there are open + /// request/responses streams. If enabled, pings are also sent when no + /// streams are active. Does nothing if `keep_alive_interval` is + /// disabled. + /// + /// Default is `false`. + pub fn keep_alive_while_idle(&mut self, enabled: bool) -> &mut Self { + self.keep_alive_while_idle = enabled; self } @@ -156,20 +223,20 @@ impl Http2OptionsBuilder { /// /// Passing `None` will do nothing. pub fn enable_push(mut self, opt: bool) -> Self { - self.config.enable_push = Some(opt); + self.builder.enable_push(opt); self } /// Sets the enable connect protocol. pub fn enable_connect_protocol(mut self, opt: bool) -> Self { - self.config.enable_connect_protocol = Some(opt); + self.builder.enable_connect_protocol(opt); self } /// Disable RFC 7540 Stream Priorities (set to `true` to disable). /// [RFC 9218]: pub fn no_rfc7540_priorities(mut self, opt: bool) -> Self { - self.config.no_rfc7540_priorities = Some(opt); + self.builder.no_rfc7540_priorities(opt); self } @@ -182,7 +249,7 @@ impl Http2OptionsBuilder { /// /// [`http2::client::Builder::max_concurrent_reset_streams`]: https://docs.rs/h2/client/struct.Builder.html#method.max_concurrent_reset_streams pub fn max_concurrent_reset_streams(mut self, max: usize) -> Self { - self.config.max_concurrent_reset_streams = Some(max); + self.builder.max_concurrent_reset_streams(max); self } @@ -195,7 +262,7 @@ impl Http2OptionsBuilder { /// The value must be no larger than `u32::MAX`. pub fn max_send_buf_size(mut self, max: usize) -> Self { assert!(max <= u32::MAX as usize); - self.config.max_send_buffer_size = max; + self.builder.max_send_buffer_size(max); self } @@ -203,7 +270,9 @@ impl Http2OptionsBuilder { /// /// See for more information. pub fn max_pending_accept_reset_streams(mut self, max: impl Into>) -> Self { - self.config.max_pending_accept_reset_streams = max.into(); + if let Some(max) = max.into() { + self.builder.max_pending_accept_reset_streams(max); + } self } @@ -216,7 +285,9 @@ impl Http2OptionsBuilder { where T: Into>, { - self.config.headers_stream_dependency = stream_dependency.into(); + if let Some(stream_dependency) = stream_dependency.into() { + self.builder.headers_stream_dependency(stream_dependency); + } self } @@ -229,7 +300,9 @@ impl Http2OptionsBuilder { where T: Into>, { - self.config.headers_pseudo_order = headers_pseudo_order.into(); + if let Some(headers_pseudo_order) = headers_pseudo_order.into() { + self.builder.headers_pseudo_order(headers_pseudo_order); + } self } @@ -242,7 +315,9 @@ impl Http2OptionsBuilder { where T: Into>, { - self.config.experimental_settings = experimental_settings.into(); + if let Some(experimental_settings) = experimental_settings.into() { + self.builder.experimental_settings(experimental_settings); + } self } @@ -254,7 +329,9 @@ impl Http2OptionsBuilder { where T: Into>, { - self.config.settings_order = settings_order.into(); + if let Some(settings_order) = settings_order.into() { + self.builder.settings_order(settings_order); + } self } @@ -271,14 +348,23 @@ impl Http2OptionsBuilder { where T: Into>, { - self.config.priorities = priorities.into(); + if let Some(priorities) = priorities.into() { + self.builder.priorities(priorities); + } self } /// Builds the `Http2Options` instance. pub fn build(self) -> Http2Options { Http2Options { - config: self.config, + builder: self.builder, + ping_config: ping::Config::new( + self.adaptive_window, + self.initial_window_size, + self.keep_alive_interval, + self.keep_alive_timeout, + self.keep_alive_while_idle, + ), } } } @@ -286,8 +372,26 @@ impl Http2OptionsBuilder { impl Http2Options { /// Creates a new `Http2OptionsBuilder` instance. pub fn builder() -> Http2OptionsBuilder { + let mut builder = Builder::default(); + builder + .initial_window_size(DEFAULT_WINDOW_SIZE) + .initial_max_send_streams(DEFAULT_INITIAL_MAX_SEND_STREAMS) + .initial_connection_window_size(DEFAULT_CONN_WINDOW_SIZE) + .max_send_buffer_size(DEFAULT_MAX_SEND_BUF_SIZE); Http2OptionsBuilder { - config: Config::default(), + builder, + adaptive_window: false, + keep_alive_interval: None, + keep_alive_timeout: Duration::from_secs(20), + keep_alive_while_idle: false, + initial_window_size: DEFAULT_WINDOW_SIZE, } } } + +impl Default for Http2Options { + #[inline] + fn default() -> Self { + Http2Options::builder().build() + } +} diff --git a/src/core/proto/h2/client.rs b/src/core/proto/h2/client.rs index 1f8398cf2..c2c920be3 100644 --- a/src/core/proto/h2/client.rs +++ b/src/core/proto/h2/client.rs @@ -4,7 +4,6 @@ use std::{ marker::PhantomData, pin::Pin, task::{Context, Poll, ready}, - time::Duration, }; use bytes::Bytes; @@ -22,7 +21,6 @@ use http_body::Body; use http2::{ SendStream, client::{Builder, Connection, ResponseFuture, SendRequest}, - frame::{ExperimentalSettings, Priorities, PseudoOrder, SettingsOrder, StreamDependency}, }; use pin_project_lite::pin_project; @@ -52,151 +50,11 @@ type ConnDropRef = mpsc::Sender; ///// the "dispatch" task will be notified and can shutdown sooner. type ConnEof = oneshot::Receiver; -// Our defaults are chosen for the "majority" case, which usually are not -// resource constrained, and so the spec default of 64kb can be too limiting -// for performance. -const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024 * 5; // 5mb -const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024 * 2; // 2mb -const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 1024; // 1mb - -// The maximum number of concurrent streams that the client is allowed to open -// before it receives the initial SETTINGS frame from the server. -// This default value is derived from what the HTTP/2 spec recommends as the -// minimum value that endpoints advertise to their peers. It means that using -// this value will minimize the chance of the failure where the local endpoint -// attempts to open too many streams and gets rejected by the remote peer with -// the `REFUSED_STREAM` error. -const DEFAULT_INITIAL_MAX_SEND_STREAMS: usize = 100; - -#[derive(Clone, Debug)] -pub(crate) struct Config { - pub(crate) adaptive_window: bool, - pub(crate) initial_stream_id: Option, - pub(crate) initial_conn_window_size: u32, - pub(crate) initial_stream_window_size: u32, - pub(crate) initial_max_send_streams: usize, - pub(crate) max_frame_size: Option, - pub(crate) keep_alive_interval: Option, - pub(crate) keep_alive_timeout: Duration, - pub(crate) keep_alive_while_idle: bool, - pub(crate) max_concurrent_reset_streams: Option, - pub(crate) max_send_buffer_size: usize, - pub(crate) max_concurrent_streams: Option, - pub(crate) max_header_list_size: Option, - pub(crate) max_pending_accept_reset_streams: Option, - pub(crate) enable_push: Option, - pub(crate) header_table_size: Option, - pub(crate) enable_connect_protocol: Option, - pub(crate) no_rfc7540_priorities: Option, - pub(crate) headers_pseudo_order: Option, - pub(crate) headers_stream_dependency: Option, - pub(crate) experimental_settings: Option, - pub(crate) settings_order: Option, - pub(crate) priorities: Option, -} - -impl Default for Config { - fn default() -> Config { - Config { - adaptive_window: false, - initial_stream_id: None, - initial_conn_window_size: DEFAULT_CONN_WINDOW, - initial_stream_window_size: DEFAULT_STREAM_WINDOW, - initial_max_send_streams: DEFAULT_INITIAL_MAX_SEND_STREAMS, - max_frame_size: None, - max_header_list_size: None, - keep_alive_interval: None, - keep_alive_timeout: Duration::from_secs(20), - keep_alive_while_idle: false, - max_concurrent_reset_streams: None, - max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE, - max_pending_accept_reset_streams: None, - header_table_size: None, - max_concurrent_streams: None, - enable_push: None, - enable_connect_protocol: None, - no_rfc7540_priorities: None, - experimental_settings: None, - settings_order: None, - headers_pseudo_order: None, - headers_stream_dependency: None, - priorities: None, - } - } -} - -fn new_builder(config: &Config) -> Builder { - let mut builder = Builder::default(); - builder - .initial_max_send_streams(config.initial_max_send_streams) - .initial_window_size(config.initial_stream_window_size) - .initial_connection_window_size(config.initial_conn_window_size) - .max_send_buffer_size(config.max_send_buffer_size); - if let Some(id) = config.initial_stream_id { - builder.initial_stream_id(id); - } - if let Some(max) = config.max_pending_accept_reset_streams { - builder.max_pending_accept_reset_streams(max); - } - if let Some(max) = config.max_concurrent_reset_streams { - builder.max_concurrent_reset_streams(max); - } - if let Some(max) = config.max_concurrent_streams { - builder.max_concurrent_streams(max); - } - if let Some(max) = config.max_header_list_size { - builder.max_header_list_size(max); - } - if let Some(opt) = config.enable_push { - builder.enable_push(opt); - } - if let Some(max) = config.max_frame_size { - builder.max_frame_size(max); - } - if let Some(max) = config.header_table_size { - builder.header_table_size(max); - } - if let Some(v) = config.enable_connect_protocol { - builder.enable_connect_protocol(v); - } - if let Some(v) = config.no_rfc7540_priorities { - builder.no_rfc7540_priorities(v); - } - if let Some(ref order) = config.settings_order { - builder.settings_order(order.clone()); - } - if let Some(ref experimental_settings) = config.experimental_settings { - builder.experimental_settings(experimental_settings.clone()); - } - if let Some(stream_dependency) = config.headers_stream_dependency { - builder.headers_stream_dependency(stream_dependency); - } - if let Some(ref order) = config.headers_pseudo_order { - builder.headers_pseudo_order(order.clone()); - } - if let Some(ref priority) = config.priorities { - builder.priorities(priority.clone()); - } - builder -} - -fn new_ping_config(config: &Config) -> ping::Config { - ping::Config { - bdp_initial_window: if config.adaptive_window { - Some(config.initial_stream_window_size) - } else { - None - }, - keep_alive_interval: config.keep_alive_interval, - keep_alive_timeout: config.keep_alive_timeout, - keep_alive_while_idle: config.keep_alive_while_idle, - } -} - pub(crate) async fn handshake( io: T, req_rx: ClientRx, - config: &Config, + builder: Builder, + ping_config: ping::Config, mut exec: E, timer: Time, ) -> crate::core::Result> @@ -207,7 +65,7 @@ where E: Http2ClientConnExec + Unpin, B::Error: Into, { - let (h2_tx, mut conn) = new_builder(config) + let (h2_tx, mut conn) = builder .handshake::<_, SendBuf>(Compat::new(io)) .await .map_err(Error::new_h2)?; @@ -219,8 +77,6 @@ where let (conn_drop_ref, conn_drop_rx) = mpsc::channel(1); let (cancel_tx, conn_eof) = oneshot::channel(); - let ping_config = new_ping_config(config); - let (conn, ping) = if ping_config.is_enabled() { let pp = conn.ping_pong().expect("conn.ping_pong"); let (recorder, ponger) = ping::channel(pp, ping_config, timer); diff --git a/src/core/proto/h2/ping.rs b/src/core/proto/h2/ping.rs index ff5b9abba..43fa98f96 100644 --- a/src/core/proto/h2/ping.rs +++ b/src/core/proto/h2/ping.rs @@ -87,8 +87,8 @@ pub(super) fn channel(ping_pong: PingPong, config: Config, __timer: Time) -> (Re ) } -#[derive(Clone)] -pub(super) struct Config { +#[derive(Debug, Clone)] +pub(crate) struct Config { pub(super) bdp_initial_window: Option, /// If no frames are received in this amount of time, a PING frame is sent. pub(super) keep_alive_interval: Option, @@ -175,6 +175,26 @@ pub(super) struct KeepAliveTimedOut; // ===== impl Config ===== impl Config { + /// Creates a new `Config` with the specified parameters. + pub(crate) fn new( + adaptive_window: bool, + initial_window_size: u32, + keep_alive_interval: Option, + keep_alive_timeout: Duration, + keep_alive_while_idle: bool, + ) -> Self { + Config { + bdp_initial_window: if adaptive_window { + Some(initial_window_size) + } else { + None + }, + keep_alive_interval, + keep_alive_timeout, + keep_alive_while_idle, + } + } + pub(super) fn is_enabled(&self) -> bool { self.bdp_initial_window.is_some() || self.keep_alive_interval.is_some() }