From 6cd86070ed5a9af8e64683c97f940419bf1f41a0 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Sat, 20 Jan 2024 02:08:38 +0900 Subject: [PATCH 1/3] feat(http2): add `initial_max_send_streams` method to HTTP/2 client builder (#3524) --- src/client/conn/http2.rs | 13 +++++++++++++ src/proto/h2/client.rs | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/src/client/conn/http2.rs b/src/client/conn/http2.rs index 5697e9ee47..b8a23ab329 100644 --- a/src/client/conn/http2.rs +++ b/src/client/conn/http2.rs @@ -293,6 +293,19 @@ impl Builder { self } + /// Sets the initial maximum of locally initiated (send) streams. + /// + /// This value will be overwritten by the value included in the initial + /// SETTINGS frame received from the peer as part of a [connection preface]. + /// + /// The default value is determined by the `h2` crate, but may change. + /// + /// [connection preface]: https://httpwg.org/specs/rfc9113.html#preface + pub fn initial_max_send_streams(&mut self, initial: impl Into>) -> &mut Self { + self.h2_builder.initial_max_send_streams = initial.into(); + self + } + /// Sets whether to use an adaptive flow control. /// /// Enabling this will override the limits set in diff --git a/src/proto/h2/client.rs b/src/proto/h2/client.rs index 8c2a4d2e0f..60522e26d8 100644 --- a/src/proto/h2/client.rs +++ b/src/proto/h2/client.rs @@ -52,6 +52,7 @@ pub(crate) struct Config { pub(crate) adaptive_window: bool, pub(crate) initial_conn_window_size: u32, pub(crate) initial_stream_window_size: u32, + pub(crate) initial_max_send_streams: Option, pub(crate) max_frame_size: u32, #[cfg(feature = "runtime")] pub(crate) keep_alive_interval: Option, @@ -69,6 +70,7 @@ impl Default for Config { adaptive_window: false, initial_conn_window_size: DEFAULT_CONN_WINDOW, initial_stream_window_size: DEFAULT_STREAM_WINDOW, + initial_max_send_streams: None, max_frame_size: DEFAULT_MAX_FRAME_SIZE, #[cfg(feature = "runtime")] keep_alive_interval: None, @@ -90,6 +92,9 @@ fn new_builder(config: &Config) -> Builder { .max_frame_size(config.max_frame_size) .max_send_buffer_size(config.max_send_buffer_size) .enable_push(false); + if let Some(initial_max_send_streams) = config.initial_max_send_streams { + builder.initial_max_send_streams(initial_max_send_streams); + } if let Some(max) = config.max_concurrent_reset_streams { builder.max_concurrent_reset_streams(max); } From dabcd78f802da2e52882937a691a9ce272c310ac Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Mon, 2 Jun 2025 19:50:50 -0700 Subject: [PATCH 2/3] feat(http2): add initial_max_send_streams method to client builder --- src/client/client.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/client/client.rs b/src/client/client.rs index 8195554bd7..f7d06933f0 100644 --- a/src/client/client.rs +++ b/src/client/client.rs @@ -1228,6 +1228,26 @@ impl Builder { self } + /// Sets the initial maximum of locally initiated (send) streams.Add commentMore actions + /// + /// This value will be overwritten by the value included in the initial + /// SETTINGS frame received from the peer as part of a [connection preface]. + /// + /// Passing `None` will do nothing. + /// + /// If not set, hyper will use a default. + /// + /// [connection preface]: https://httpwg.org/specs/rfc9113.html#preface + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] + pub fn http2_initial_max_send_streams( + &mut self, + initial: impl Into>, + ) -> &mut Self { + self.h2_builder.initial_max_send_streams(initial); + self + } + /// Sets whether to use an adaptive flow control. /// /// Enabling this will override the limits set in From d8ad01a6d5c893133b23ddc13862ca2c0c05e592 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka Date: Sat, 13 Jan 2024 17:47:20 +0900 Subject: [PATCH 3/3] fix(http2): `initial_max_send_streams` defaults to 100 --- src/client/conn/http2.rs | 8 ++++++-- src/proto/h2/client.rs | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/client/conn/http2.rs b/src/client/conn/http2.rs index b8a23ab329..dee857f3a6 100644 --- a/src/client/conn/http2.rs +++ b/src/client/conn/http2.rs @@ -298,11 +298,15 @@ impl Builder { /// This value will be overwritten by the value included in the initial /// SETTINGS frame received from the peer as part of a [connection preface]. /// - /// The default value is determined by the `h2` crate, but may change. + /// Passing `None` will do nothing. + /// + /// If not set, hyper will use a default. /// /// [connection preface]: https://httpwg.org/specs/rfc9113.html#preface pub fn initial_max_send_streams(&mut self, initial: impl Into>) -> &mut Self { - self.h2_builder.initial_max_send_streams = initial.into(); + if let Some(initial) = initial.into() { + self.h2_builder.initial_max_send_streams = initial; + } self } diff --git a/src/proto/h2/client.rs b/src/proto/h2/client.rs index 60522e26d8..c5599e2096 100644 --- a/src/proto/h2/client.rs +++ b/src/proto/h2/client.rs @@ -47,12 +47,21 @@ const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024 * 2; // 2mb const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb 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_conn_window_size: u32, pub(crate) initial_stream_window_size: u32, - pub(crate) initial_max_send_streams: Option, + pub(crate) initial_max_send_streams: usize, pub(crate) max_frame_size: u32, #[cfg(feature = "runtime")] pub(crate) keep_alive_interval: Option, @@ -70,7 +79,7 @@ impl Default for Config { adaptive_window: false, initial_conn_window_size: DEFAULT_CONN_WINDOW, initial_stream_window_size: DEFAULT_STREAM_WINDOW, - initial_max_send_streams: None, + initial_max_send_streams: DEFAULT_INITIAL_MAX_SEND_STREAMS, max_frame_size: DEFAULT_MAX_FRAME_SIZE, #[cfg(feature = "runtime")] keep_alive_interval: None, @@ -87,14 +96,12 @@ impl Default for Config { 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_frame_size(config.max_frame_size) .max_send_buffer_size(config.max_send_buffer_size) .enable_push(false); - if let Some(initial_max_send_streams) = config.initial_max_send_streams { - builder.initial_max_send_streams(initial_max_send_streams); - } if let Some(max) = config.max_concurrent_reset_streams { builder.max_concurrent_reset_streams(max); }