Skip to main content

iota_http/
config.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::time::Duration;
6
7const DEFAULT_HTTP2_KEEPALIVE_TIMEOUT_SECS: u64 = 20;
8/// Covers a round trip plus a few TCP retransmissions on a lossy link; an
9/// unloaded TLS 1.3 handshake completes in one round trip.
10const DEFAULT_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5);
11/// Concurrent handshakes only build up when peers are slow or silent, so this
12/// leaves ample room for a legitimate reconnect burst.
13const DEFAULT_MAX_PENDING_CONNECTIONS: usize = 512;
14
15#[derive(Debug, Clone)]
16pub struct Config {
17    init_stream_window_size: Option<u32>,
18    init_connection_window_size: Option<u32>,
19    max_concurrent_streams: Option<u32>,
20    pub(crate) tcp_keepalive: Option<Duration>,
21    pub(crate) tcp_nodelay: bool,
22    http2_keepalive_interval: Option<Duration>,
23    http2_keepalive_timeout: Option<Duration>,
24    http2_adaptive_window: Option<bool>,
25    http2_max_pending_accept_reset_streams: Option<usize>,
26    http2_max_header_list_size: Option<u32>,
27    max_frame_size: Option<u32>,
28    pub(crate) accept_http1: bool,
29    enable_connect_protocol: bool,
30    pub(crate) max_connection_age: Option<Duration>,
31    pub(crate) allow_insecure: bool,
32    pub(crate) handshake_timeout: Option<Duration>,
33    pub(crate) max_pending_connections: Option<usize>,
34}
35
36impl Default for Config {
37    fn default() -> Self {
38        Self {
39            init_stream_window_size: None,
40            init_connection_window_size: None,
41            max_concurrent_streams: None,
42            tcp_keepalive: None,
43            tcp_nodelay: true,
44            http2_keepalive_interval: None,
45            http2_keepalive_timeout: None,
46            http2_adaptive_window: None,
47            http2_max_pending_accept_reset_streams: None,
48            http2_max_header_list_size: None,
49            max_frame_size: None,
50            accept_http1: true,
51            enable_connect_protocol: true,
52            max_connection_age: None,
53            allow_insecure: false,
54            handshake_timeout: Some(DEFAULT_HANDSHAKE_TIMEOUT),
55            max_pending_connections: Some(DEFAULT_MAX_PENDING_CONNECTIONS),
56        }
57    }
58}
59
60impl Config {
61    /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2
62    /// stream-level flow control.
63    ///
64    /// Default is 65,535
65    ///
66    /// [spec]: https://httpwg.org/specs/rfc9113.html#InitialWindowSize
67    pub fn initial_stream_window_size(self, sz: impl Into<Option<u32>>) -> Self {
68        Self {
69            init_stream_window_size: sz.into(),
70            ..self
71        }
72    }
73
74    /// Sets the max connection-level flow control for HTTP2
75    ///
76    /// Default is 65,535
77    pub fn initial_connection_window_size(self, sz: impl Into<Option<u32>>) -> Self {
78        Self {
79            init_connection_window_size: sz.into(),
80            ..self
81        }
82    }
83
84    /// Sets the [`SETTINGS_MAX_CONCURRENT_STREAMS`][spec] option for HTTP2
85    /// connections.
86    ///
87    /// Default is no limit (`None`).
88    ///
89    /// [spec]: https://httpwg.org/specs/rfc9113.html#n-stream-concurrency
90    pub fn max_concurrent_streams(self, max: impl Into<Option<u32>>) -> Self {
91        Self {
92            max_concurrent_streams: max.into(),
93            ..self
94        }
95    }
96
97    /// Sets the maximum time option in milliseconds that a connection may exist
98    ///
99    /// Default is no limit (`None`).
100    pub fn max_connection_age(self, max_connection_age: Duration) -> Self {
101        Self {
102            max_connection_age: Some(max_connection_age),
103            ..self
104        }
105    }
106
107    /// Set whether HTTP2 Ping frames are enabled on accepted connections.
108    ///
109    /// If `None` is specified, HTTP2 keepalive is disabled, otherwise the
110    /// duration specified will be the time interval between HTTP2 Ping
111    /// frames. The timeout for receiving an acknowledgement of the
112    /// keepalive ping can be set with [`Config::http2_keepalive_timeout`].
113    ///
114    /// Default is no HTTP2 keepalive (`None`)
115    pub fn http2_keepalive_interval(self, http2_keepalive_interval: Option<Duration>) -> Self {
116        Self {
117            http2_keepalive_interval,
118            ..self
119        }
120    }
121
122    /// Sets a timeout for receiving an acknowledgement of the keepalive ping.
123    ///
124    /// If the ping is not acknowledged within the timeout, the connection will
125    /// be closed. Does nothing if http2_keep_alive_interval is disabled.
126    ///
127    /// Default is 20 seconds.
128    pub fn http2_keepalive_timeout(self, http2_keepalive_timeout: Option<Duration>) -> Self {
129        Self {
130            http2_keepalive_timeout,
131            ..self
132        }
133    }
134
135    /// Sets whether to use an adaptive flow control. Defaults to false.
136    /// Enabling this will override the limits set in
137    /// http2_initial_stream_window_size and
138    /// http2_initial_connection_window_size.
139    pub fn http2_adaptive_window(self, enabled: Option<bool>) -> Self {
140        Self {
141            http2_adaptive_window: enabled,
142            ..self
143        }
144    }
145
146    /// Configures the maximum number of pending reset streams allowed before a
147    /// GOAWAY will be sent.
148    ///
149    /// This will default to whatever the default in h2 is. As of v0.3.17, it is
150    /// 20.
151    ///
152    /// See <https://github.com/hyperium/hyper/issues/2877> for more information.
153    pub fn http2_max_pending_accept_reset_streams(self, max: Option<usize>) -> Self {
154        Self {
155            http2_max_pending_accept_reset_streams: max,
156            ..self
157        }
158    }
159
160    /// Set whether TCP keepalive messages are enabled on accepted connections.
161    ///
162    /// If `None` is specified, keepalive is disabled, otherwise the duration
163    /// specified will be the time to remain idle before sending TCP keepalive
164    /// probes.
165    ///
166    /// Default is no keepalive (`None`)
167    pub fn tcp_keepalive(self, tcp_keepalive: Option<Duration>) -> Self {
168        Self {
169            tcp_keepalive,
170            ..self
171        }
172    }
173
174    /// Set the value of `TCP_NODELAY` option for accepted connections. Enabled
175    /// by default.
176    pub fn tcp_nodelay(self, enabled: bool) -> Self {
177        Self {
178            tcp_nodelay: enabled,
179            ..self
180        }
181    }
182
183    /// Sets the max size of received header frames.
184    ///
185    /// This will default to whatever the default in hyper is. As of v1.4.1, it
186    /// is 16 KiB.
187    pub fn http2_max_header_list_size(self, max: impl Into<Option<u32>>) -> Self {
188        Self {
189            http2_max_header_list_size: max.into(),
190            ..self
191        }
192    }
193
194    /// Sets the maximum frame size to use for HTTP2.
195    ///
196    /// Passing `None` will do nothing.
197    ///
198    /// If not set, will default from underlying transport.
199    pub fn max_frame_size(self, frame_size: impl Into<Option<u32>>) -> Self {
200        Self {
201            max_frame_size: frame_size.into(),
202            ..self
203        }
204    }
205
206    /// Allow this accepting http1 requests.
207    ///
208    /// Default is `true`.
209    pub fn accept_http1(self, accept_http1: bool) -> Self {
210        Config {
211            accept_http1,
212            ..self
213        }
214    }
215
216    /// Allow accepting insecure connections when a tls_config is provided.
217    ///
218    /// This will allow clients to connect both using TLS as well as without TLS
219    /// on the same network interface.
220    ///
221    /// Default is `false`.
222    ///
223    /// NOTE: This presently will only work for `tokio::net::TcpStream` IO
224    /// connections
225    pub fn allow_insecure(self, allow_insecure: bool) -> Self {
226        Config {
227            allow_insecure,
228            ..self
229        }
230    }
231
232    /// Sets how long an accepted connection may take to complete its TLS
233    /// handshake before it is closed. The peer is unauthenticated for the whole
234    /// handshake, so without this a silent peer holds a task and a file
235    /// descriptor indefinitely.
236    ///
237    /// Default is 5 seconds. `None` disables the deadline.
238    pub fn handshake_timeout(self, handshake_timeout: Option<Duration>) -> Self {
239        Self {
240            handshake_timeout,
241            ..self
242        }
243    }
244
245    /// Sets how many accepted connections may be handshaking at the same time.
246    /// While the limit is reached the server stops accepting, leaving new
247    /// connections in the kernel backlog instead of holding file descriptors
248    /// for them.
249    ///
250    /// Default is 512. `None` removes the limit.
251    pub fn max_pending_connections(self, max_pending_connections: Option<usize>) -> Self {
252        Self {
253            max_pending_connections,
254            ..self
255        }
256    }
257
258    /// Rejects settings the accept loop cannot recover from.
259    pub(crate) fn validate(&self) -> Result<(), crate::BoxError> {
260        match self.max_pending_connections {
261            Some(0) => Err("'max_pending_connections' must be greater than zero, \
262                            a server that accepts no connection is never useful"
263                .into()),
264            // Reaching the limit stops accepting until a handshake finishes, so
265            // without a deadline enough silent peers stall the server for good.
266            Some(_) if self.handshake_timeout.is_none() => Err(
267                "'max_pending_connections' requires a 'handshake_timeout' to release its \
268                     slots"
269                    .into(),
270            ),
271            _ => Ok(()),
272        }
273    }
274
275    pub(crate) fn connection_builder(
276        &self,
277    ) -> hyper_util::server::conn::auto::Builder<hyper_util::rt::TokioExecutor> {
278        let mut builder =
279            hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
280
281        if !self.accept_http1 {
282            builder = builder.http2_only();
283        }
284
285        if self.enable_connect_protocol {
286            builder.http2().enable_connect_protocol();
287        }
288
289        let http2_keepalive_timeout = self
290            .http2_keepalive_timeout
291            .unwrap_or_else(|| Duration::new(DEFAULT_HTTP2_KEEPALIVE_TIMEOUT_SECS, 0));
292
293        builder
294            .http2()
295            .timer(hyper_util::rt::TokioTimer::new())
296            .initial_connection_window_size(self.init_connection_window_size)
297            .initial_stream_window_size(self.init_stream_window_size)
298            .max_concurrent_streams(self.max_concurrent_streams)
299            .keep_alive_interval(self.http2_keepalive_interval)
300            .keep_alive_timeout(http2_keepalive_timeout)
301            .adaptive_window(self.http2_adaptive_window.unwrap_or_default())
302            .max_pending_accept_reset_streams(self.http2_max_pending_accept_reset_streams)
303            .max_frame_size(self.max_frame_size);
304
305        if let Some(max_header_list_size) = self.http2_max_header_list_size {
306            builder.http2().max_header_list_size(max_header_list_size);
307        }
308
309        builder
310    }
311}